From e4565fa8b1b68b0a9f26e63b84f3759e8f72cb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Sat, 17 Oct 2026 16:06:56 -0400 Subject: [PATCH] MA0008: do not overflow the stack on cyclic struct layouts The fields of an invalid struct can cycle back to the struct (CS0523), and generic structs can expand into an infinite number of types. IsBlittableType from Meziantou.Framework.Roslyn recursed without limit and crashed the compiler. Use a local check with a depth limit instead. --- .../Rules/UseStructLayoutAttributeAnalyzer.cs | 46 ++++++++++++++++++- .../UseStructLayoutAttributeAnalyzerTests.cs | 30 ++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/Meziantou.Analyzer/Rules/UseStructLayoutAttributeAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseStructLayoutAttributeAnalyzer.cs index 6a9b5b398..870e6baa1 100644 --- a/src/Meziantou.Analyzer/Rules/UseStructLayoutAttributeAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseStructLayoutAttributeAnalyzer.cs @@ -3,6 +3,11 @@ namespace Meziantou.Analyzer.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseStructLayoutAttributeAnalyzer : DiagnosticAnalyzer { + // The fields of an invalid struct can cycle back to the struct (CS0523), directly or through other types, + // and the generic structs can create an infinite number of types (struct S { S> field; }). + // The depth is limited, so these types are not blittable instead of overflowing the stack. + private const int MaxBlittableTypeDepth = 32; + private static readonly DiagnosticDescriptor Rule = new( RuleIdentifiers.MissingStructLayoutAttribute, title: "Add StructLayoutAttribute", @@ -45,7 +50,7 @@ private static void Analyze(SymbolAnalysisContext context, INamedTypeSymbol attr if (member.IsConst || member.IsStatic) continue; - if (!member.Type.IsBlittableType()) + if (!IsBlittableType(member.Type, depth: 0)) return; memberCount++; @@ -56,4 +61,43 @@ private static void Analyze(SymbolAnalysisContext context, INamedTypeSymbol attr context.ReportDiagnostic(Rule, symbol); } } + + private static bool IsBlittableType(ITypeSymbol symbol, int depth) + { + if (symbol.SpecialType is SpecialType.System_Byte or SpecialType.System_SByte + or SpecialType.System_Int16 or SpecialType.System_UInt16 + or SpecialType.System_Int32 or SpecialType.System_UInt32 + or SpecialType.System_Int64 or SpecialType.System_UInt64 + or SpecialType.System_Single or SpecialType.System_Double + or SpecialType.System_IntPtr or SpecialType.System_UIntPtr) + { + return true; + } + + if (symbol.SpecialType is not SpecialType.None) + return false; + + if (symbol.TypeKind is TypeKind.Pointer) + return true; + + if (symbol is not INamedTypeSymbol namedType) + return false; + + if (namedType.EnumUnderlyingType is not null) + return true; + + if (!namedType.IsValueType || depth >= MaxBlittableTypeDepth) + return false; + + foreach (var member in namedType.GetMembers()) + { + if (member is not IFieldSymbol field || field.IsConst || field.IsStatic) + continue; + + if (!IsBlittableType(field.Type, depth + 1)) + return false; + } + + return true; + } } diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseStructLayoutAttributeAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseStructLayoutAttributeAnalyzerTests.cs index 07378085e..ec2dd4039 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/UseStructLayoutAttributeAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/UseStructLayoutAttributeAnalyzerTests.cs @@ -321,6 +321,36 @@ struct TypeName return test.RunAsync(); } + [Fact] + public Task WithCyclicStruct_ShouldNotReportDiagnostic() + { + var test = CreateTest(); + test.TestCode = """ + struct A + { + B {|CS0523:b|}; + int c; + } + struct B + { + A {|CS0523:a|}; + int c; + } + struct C + { + C {|CS0523:c|}; + int d; + } + struct D + { + D> {|CS0523:d|}; + int e; + } + """; + + return test.RunAsync(); + } + [Fact] public Task RecordStruct() {