From 7ecb19f91888b11a65ff3e42a631d40fd294af2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 09:06:17 +0000 Subject: [PATCH 1/4] Handle primary constructors in ILLink analyzer dataflow Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/0a2f12ac-4c67-4296-8c19-f96183fc6dc4 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DataFlow/LocalDataFlowAnalysis.cs | 45 ++++++++++++++++--- .../DataFlow/MethodBodyValue.cs | 4 +- .../AnnotatedMembersAccessedViaReflection.cs | 7 +++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs index 730e43e77560de..0361f43c32ca61 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.Operations; @@ -80,15 +81,12 @@ public bool InterproceduralAnalyze() return succeeded; } - // Delegate types with default parameter values produce operation blocks whose - // OwningSymbol is the delegate INamedTypeSymbol. These blocks contain only - // simple constant initializers with no interesting dataflow, so skip them. - if (Context.OwningSymbol is INamedTypeSymbol { TypeKind: TypeKind.Delegate }) + if (!TryGetDataFlowOwningSymbol(Context.OwningSymbol, out ISymbol owningSymbol)) return succeeded; - Debug.Assert(Context.OwningSymbol is not IMethodSymbol methodSymbol || + Debug.Assert(owningSymbol is not IMethodSymbol methodSymbol || methodSymbol.MethodKind is not (MethodKind.LambdaMethod or MethodKind.LocalFunction)); - var startMethod = new MethodBodyValue(Context.OwningSymbol, Context.GetControlFlowGraph(OperationBlock)); + var startMethod = new MethodBodyValue(owningSymbol, Context.GetControlFlowGraph(OperationBlock)); interproceduralState.TrackMethod(startMethod); while (!interproceduralState.Equals(oldInterproceduralState)) @@ -104,6 +102,41 @@ public bool InterproceduralAnalyze() return succeeded; } + private static bool TryGetDataFlowOwningSymbol(ISymbol owningSymbol, out ISymbol dataFlowOwningSymbol) + { + dataFlowOwningSymbol = owningSymbol; + + if (owningSymbol is not INamedTypeSymbol namedType) + return true; + + // Delegate types with default parameter values produce operation blocks whose + // OwningSymbol is the delegate INamedTypeSymbol. These blocks contain only + // simple constant initializers with no interesting dataflow, so skip them. + if (namedType.TypeKind == TypeKind.Delegate) + return false; + + // Primary constructors produce operation blocks whose OwningSymbol is the + // containing INamedTypeSymbol. Analyze those blocks as the primary constructor + // so that parameters and instance references are handled like normal constructors. + foreach (IMethodSymbol constructor in namedType.InstanceConstructors) + { + if (constructor.IsImplicitlyDeclared) + continue; + + foreach (SyntaxReference syntaxReference in constructor.DeclaringSyntaxReferences) + { + if (syntaxReference.GetSyntax() is TypeDeclarationSyntax { ParameterList: not null }) + { + dataFlowOwningSymbol = constructor; + return true; + } + } + } + + Debug.Fail($"Unsupported operation block owning symbol: '{owningSymbol.GetDisplayName()}' ({owningSymbol.Kind}, {owningSymbol.GetType().FullName})"); + return false; + } + private bool AnalyzeAttribute(ISymbol owningSymbol, IAttributeOperation attribute) { var cfg = Context.GetControlFlowGraph(attribute); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs index a504db87353f34..301c8ace81c254 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs @@ -20,7 +20,9 @@ namespace ILLink.RoslynAnalyzer.DataFlow public MethodBodyValue(ISymbol owningSymbol, ControlFlowGraph cfg) { - Debug.Assert(owningSymbol is (IMethodSymbol or IFieldSymbol or IPropertySymbol)); + Debug.Assert(owningSymbol is (IMethodSymbol or IFieldSymbol or IPropertySymbol), + $"Unexpected owning symbol for method body value: '{owningSymbol.GetDisplayName()}' ({owningSymbol.Kind}, {owningSymbol.GetType().FullName}); " + + $"original operation: {cfg.OriginalOperation?.Kind} at {cfg.OriginalOperation?.Syntax.GetLocation().GetLineSpan()}"); OwningSymbol = owningSymbol; ControlFlowGraph = cfg; } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs index 4daf4aec7f545e..c977e12f03e121 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs @@ -895,6 +895,13 @@ class DelegateCreation // is the delegate type (INamedTypeSymbol), not a method. delegate void DelegateWithDefaultParameter(Type type = null); + // Primary constructors generate operation blocks whose OwningSymbol + // is the containing type (INamedTypeSymbol), not the constructor. + class PrimaryConstructor(Type type) + { + Type _type = type; + } + static void TestField() { var d = new UnannotatedDelegate(field); From ad5f264aca7d3576da12f0afb00376eece21ae2d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 09:17:59 +0000 Subject: [PATCH 2/4] Add primary constructor dataflow regression test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/0a2f12ac-4c67-4296-8c19-f96183fc6dc4 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DataFlow/ConstructorDataFlow.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs index 60309e3acdc96f..88d1a4fc10c8d8 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs @@ -18,6 +18,7 @@ public class ConstructorDataFlow public static void Main() { DataFlowInConstructor.Test(); + DataFlowInPrimaryConstructor.Test(); DataFlowInStaticConstructor.Test(); } @@ -95,6 +96,18 @@ public static void Test() } } + class DataFlowInPrimaryConstructor(Type type) + { + [ExpectedWarning("IL2069", nameof(annotatedField), nameof(type))] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type annotatedField = type; + + public static void Test() + { + new DataFlowInPrimaryConstructor(null); + } + } + class DataFlowInStaticConstructor { [ExpectedWarning("IL2072", nameof(GetUnknown), nameof(RequireAll))] From 0aca39282032e380cf9087d578890d8213c46ebb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 09:20:52 +0000 Subject: [PATCH 3/4] Preserve analyzer assertion for unexpected named type owners Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/0a2f12ac-4c67-4296-8c19-f96183fc6dc4 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs index 0361f43c32ca61..366327efc145d4 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs @@ -133,8 +133,9 @@ private static bool TryGetDataFlowOwningSymbol(ISymbol owningSymbol, out ISymbol } } - Debug.Fail($"Unsupported operation block owning symbol: '{owningSymbol.GetDisplayName()}' ({owningSymbol.Kind}, {owningSymbol.GetType().FullName})"); - return false; + // Let MethodBodyValue assert with the control-flow graph context if Roslyn + // introduces another named-type-owned operation block shape. + return true; } private bool AnalyzeAttribute(ISymbol owningSymbol, IAttributeOperation attribute) From 9c8ea0b8db7907775084a46612875e85b66c28cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 00:54:00 +0000 Subject: [PATCH 4/4] Scope analyzer assertion repro to test only Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/182a0f53-6614-4321-b6d7-397ad71530ae Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DataFlow/LocalDataFlowAnalysis.cs | 46 +++---------------- .../DataFlow/MethodBodyValue.cs | 4 +- .../DataFlow/ConstructorDataFlow.cs | 13 ------ 3 files changed, 7 insertions(+), 56 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs index 366327efc145d4..730e43e77560de 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.Operations; @@ -81,12 +80,15 @@ public bool InterproceduralAnalyze() return succeeded; } - if (!TryGetDataFlowOwningSymbol(Context.OwningSymbol, out ISymbol owningSymbol)) + // Delegate types with default parameter values produce operation blocks whose + // OwningSymbol is the delegate INamedTypeSymbol. These blocks contain only + // simple constant initializers with no interesting dataflow, so skip them. + if (Context.OwningSymbol is INamedTypeSymbol { TypeKind: TypeKind.Delegate }) return succeeded; - Debug.Assert(owningSymbol is not IMethodSymbol methodSymbol || + Debug.Assert(Context.OwningSymbol is not IMethodSymbol methodSymbol || methodSymbol.MethodKind is not (MethodKind.LambdaMethod or MethodKind.LocalFunction)); - var startMethod = new MethodBodyValue(owningSymbol, Context.GetControlFlowGraph(OperationBlock)); + var startMethod = new MethodBodyValue(Context.OwningSymbol, Context.GetControlFlowGraph(OperationBlock)); interproceduralState.TrackMethod(startMethod); while (!interproceduralState.Equals(oldInterproceduralState)) @@ -102,42 +104,6 @@ public bool InterproceduralAnalyze() return succeeded; } - private static bool TryGetDataFlowOwningSymbol(ISymbol owningSymbol, out ISymbol dataFlowOwningSymbol) - { - dataFlowOwningSymbol = owningSymbol; - - if (owningSymbol is not INamedTypeSymbol namedType) - return true; - - // Delegate types with default parameter values produce operation blocks whose - // OwningSymbol is the delegate INamedTypeSymbol. These blocks contain only - // simple constant initializers with no interesting dataflow, so skip them. - if (namedType.TypeKind == TypeKind.Delegate) - return false; - - // Primary constructors produce operation blocks whose OwningSymbol is the - // containing INamedTypeSymbol. Analyze those blocks as the primary constructor - // so that parameters and instance references are handled like normal constructors. - foreach (IMethodSymbol constructor in namedType.InstanceConstructors) - { - if (constructor.IsImplicitlyDeclared) - continue; - - foreach (SyntaxReference syntaxReference in constructor.DeclaringSyntaxReferences) - { - if (syntaxReference.GetSyntax() is TypeDeclarationSyntax { ParameterList: not null }) - { - dataFlowOwningSymbol = constructor; - return true; - } - } - } - - // Let MethodBodyValue assert with the control-flow graph context if Roslyn - // introduces another named-type-owned operation block shape. - return true; - } - private bool AnalyzeAttribute(ISymbol owningSymbol, IAttributeOperation attribute) { var cfg = Context.GetControlFlowGraph(attribute); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs index 301c8ace81c254..a504db87353f34 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/MethodBodyValue.cs @@ -20,9 +20,7 @@ namespace ILLink.RoslynAnalyzer.DataFlow public MethodBodyValue(ISymbol owningSymbol, ControlFlowGraph cfg) { - Debug.Assert(owningSymbol is (IMethodSymbol or IFieldSymbol or IPropertySymbol), - $"Unexpected owning symbol for method body value: '{owningSymbol.GetDisplayName()}' ({owningSymbol.Kind}, {owningSymbol.GetType().FullName}); " + - $"original operation: {cfg.OriginalOperation?.Kind} at {cfg.OriginalOperation?.Syntax.GetLocation().GetLineSpan()}"); + Debug.Assert(owningSymbol is (IMethodSymbol or IFieldSymbol or IPropertySymbol)); OwningSymbol = owningSymbol; ControlFlowGraph = cfg; } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs index 88d1a4fc10c8d8..60309e3acdc96f 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructorDataFlow.cs @@ -18,7 +18,6 @@ public class ConstructorDataFlow public static void Main() { DataFlowInConstructor.Test(); - DataFlowInPrimaryConstructor.Test(); DataFlowInStaticConstructor.Test(); } @@ -96,18 +95,6 @@ public static void Test() } } - class DataFlowInPrimaryConstructor(Type type) - { - [ExpectedWarning("IL2069", nameof(annotatedField), nameof(type))] - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - Type annotatedField = type; - - public static void Test() - { - new DataFlowInPrimaryConstructor(null); - } - } - class DataFlowInStaticConstructor { [ExpectedWarning("IL2072", nameof(GetUnknown), nameof(RequireAll))]