From 37da9ad326651ad38237f2109e3ab7f3d0ffbe52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 23:17:12 +0000 Subject: [PATCH 1/3] Initial plan From 84f034efdec178cbf0a52b3eb181cdc99e58b3f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 00:11:56 +0000 Subject: [PATCH 2/3] [ILLink] Fix KeyNotFoundException crash for async partial methods with capturing lambdas Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/2e76c507-3b5f-49ad-9dc8-d7865f98a645 Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../Linker.Dataflow/CompilerGeneratedState.cs | 93 ++++++++++++++----- .../DataFlow/CompilerGeneratedTypes.cs | 31 +++++++ 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index 47d5c5c075a2b0..8c180f87f252fc 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -163,8 +163,31 @@ void ProcessMethod(MethodDefinition method) Debug.Assert(!isStateMachineMember); } + // Discover state machine types via attributes such as [AsyncStateMachineAttribute]. + // This is done before scanning the body so that if the attribute and the body scan + // both find the same type, the attribute-based registration takes precedence and + // the body scan's TryAdd is a no-op (preventing spurious duplicate warnings). + if (TryGetStateMachineType(method, out TypeDefinition? stateMachineType)) + { + Debug.Assert(stateMachineType.DeclaringType == type || + CompilerGeneratedNames.IsStateMachineOrDisplayClass(stateMachineType.DeclaringType.Name) && + stateMachineType.DeclaringType.DeclaringType == type); + callGraph.TrackCall(method, stateMachineType); + + if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) + { + var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; + AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); + } + // Already warned above if multiple methods map to the same type + // Fill in null for argument providers now, the real providers will be filled in later + generatedTypeToTypeArgs[stateMachineType] = new TypeArgumentInfo(method, null); + } + // Discover calls or references to lambdas or local functions. This includes // calls to local functions, and lambda assignments (which use ldftn). + // Also detect state machine types that are missing [AsyncStateMachineAttribute] + // (e.g., async partial methods, where Roslyn does not emit the attribute). if (method.Body != null) { foreach (var instruction in method.Body.Instructions(_context)) @@ -181,16 +204,31 @@ void ProcessMethod(MethodDefinition method) if (referencedMethod.IsConstructor && referencedMethod.DeclaringType is var generatedType && // Don't consider calls in the same/nested type, like inside a static constructor - !IsSameOrNestedType(method.DeclaringType, generatedType) && - CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) + !IsSameOrNestedType(method.DeclaringType, generatedType)) { - // fill in null for now, attribute providers will be filled in later - if (!generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null))) + if (CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { - var alreadyAssociatedMethod = generatedTypeToTypeArgs[generatedType].CreatingMethod; - AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); + // fill in null for now, attribute providers will be filled in later + if (!generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null))) + { + var alreadyAssociatedMethod = generatedTypeToTypeArgs[generatedType].CreatingMethod; + AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); + } + continue; + } + + // Detect class-based state machines (e.g. async partial methods in older codegen) + // via newobj of the state machine constructor. TryAdd is used because the state + // machine may already have been registered via TryGetStateMachineType above. + if (CompilerGeneratedNames.IsStateMachineType(generatedType.Name)) + { + if (generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null))) + { + _compilerGeneratedTypeToUserCodeMethod.TryAdd(generatedType, method); + callGraph.TrackCall(method, generatedType); + } + continue; } - continue; } if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(referencedMethod.Name)) @@ -233,25 +271,32 @@ referencedMethod.DeclaringType is var generatedType && } } break; - } - } - } - - if (TryGetStateMachineType(method, out TypeDefinition? stateMachineType)) - { - Debug.Assert(stateMachineType.DeclaringType == type || - CompilerGeneratedNames.IsStateMachineOrDisplayClass(stateMachineType.DeclaringType.Name) && - stateMachineType.DeclaringType.DeclaringType == type); - callGraph.TrackCall(method, stateMachineType); - if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) - { - var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; - AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); + case OperandType.InlineType: + { + // Detect struct-based state machines via initobj (the common case in modern .NET). + // Roslyn does not emit [AsyncStateMachineAttribute] on async partial methods, so + // TryGetStateMachineType above will miss them. We fall back to detecting the state + // machine type from the initobj instruction in the method body. + // TryAdd is used because the state machine may already have been registered via + // TryGetStateMachineType (for non-partial async methods that do have the attribute). + if (!isStateMachineMember && + instruction.OpCode.Code == Code.Initobj && + instruction.Operand is TypeReference typeRef && + CompilerGeneratedNames.IsStateMachineType(typeRef.Name) && + _context.TryResolve(typeRef) is TypeDefinition smTypeDef && + !IsSameOrNestedType(method.DeclaringType, smTypeDef)) + { + if (generatedTypeToTypeArgs.TryAdd(smTypeDef, new TypeArgumentInfo(method, null))) + { + _compilerGeneratedTypeToUserCodeMethod.TryAdd(smTypeDef, method); + callGraph.TrackCall(method, smTypeDef); + } + } + } + break; + } } - // Already warned above if multiple methods map to the same type - // Fill in null for argument providers now, the real providers will be filled in later - generatedTypeToTypeArgs[stateMachineType] = new TypeArgumentInfo(method, null); } static bool IsSameOrNestedType(TypeDefinition type, TypeDefinition potentialOuterType) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs index c051625d3f767f..0e9968f3d3c0a8 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs @@ -50,6 +50,9 @@ public static void Main() NestedAsyncLambda.Test(); NestedAsyncLocalFunction.Test(); NestedStaticLambda.Test(); + + // Partial methods + PartialAsyncMethodWithLambda.Test(); } private static void UseIterator() @@ -433,5 +436,33 @@ public static void Test() Container.NestedLambda((T t) => t)(default(T)); } } + + // Regression test for https://github.com/dotnet/runtime/issues/122800 + // Roslyn does not emit [AsyncStateMachineAttribute] on async partial methods, + // so ILLink must detect the state machine type from the method body instead. + partial class PartialAsyncMethodWithLambda + { + public static void Test() + { + new PartialAsyncMethodWithLambda().GetData(); + } + + public partial Task GetData() where T : new(); + } + + partial class PartialAsyncMethodWithLambda + { + public async partial Task GetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>() where T : new() + { + var tcs = new TaskCompletionSource(); + Action callback = (result) => + { + _ = typeof(T).GetMethods(); + tcs.TrySetResult(result); + }; + callback(new T()); + return await tcs.Task; + } + } } } From a8f7794f37560586904b57c158e04b8672c32808 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 01:34:57 +0000 Subject: [PATCH 3/3] Revert ILLink fix, keep regression test for async partial method crash Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/18a5443f-c80e-4ac5-931b-d8ebe7171db3 Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../Linker.Dataflow/CompilerGeneratedState.cs | 93 +++++-------------- .../DataFlow/CompilerGeneratedTypes.cs | 3 +- 2 files changed, 25 insertions(+), 71 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index 8c180f87f252fc..47d5c5c075a2b0 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -163,31 +163,8 @@ void ProcessMethod(MethodDefinition method) Debug.Assert(!isStateMachineMember); } - // Discover state machine types via attributes such as [AsyncStateMachineAttribute]. - // This is done before scanning the body so that if the attribute and the body scan - // both find the same type, the attribute-based registration takes precedence and - // the body scan's TryAdd is a no-op (preventing spurious duplicate warnings). - if (TryGetStateMachineType(method, out TypeDefinition? stateMachineType)) - { - Debug.Assert(stateMachineType.DeclaringType == type || - CompilerGeneratedNames.IsStateMachineOrDisplayClass(stateMachineType.DeclaringType.Name) && - stateMachineType.DeclaringType.DeclaringType == type); - callGraph.TrackCall(method, stateMachineType); - - if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) - { - var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; - AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); - } - // Already warned above if multiple methods map to the same type - // Fill in null for argument providers now, the real providers will be filled in later - generatedTypeToTypeArgs[stateMachineType] = new TypeArgumentInfo(method, null); - } - // Discover calls or references to lambdas or local functions. This includes // calls to local functions, and lambda assignments (which use ldftn). - // Also detect state machine types that are missing [AsyncStateMachineAttribute] - // (e.g., async partial methods, where Roslyn does not emit the attribute). if (method.Body != null) { foreach (var instruction in method.Body.Instructions(_context)) @@ -204,31 +181,16 @@ void ProcessMethod(MethodDefinition method) if (referencedMethod.IsConstructor && referencedMethod.DeclaringType is var generatedType && // Don't consider calls in the same/nested type, like inside a static constructor - !IsSameOrNestedType(method.DeclaringType, generatedType)) + !IsSameOrNestedType(method.DeclaringType, generatedType) && + CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { - if (CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) - { - // fill in null for now, attribute providers will be filled in later - if (!generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null))) - { - var alreadyAssociatedMethod = generatedTypeToTypeArgs[generatedType].CreatingMethod; - AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); - } - continue; - } - - // Detect class-based state machines (e.g. async partial methods in older codegen) - // via newobj of the state machine constructor. TryAdd is used because the state - // machine may already have been registered via TryGetStateMachineType above. - if (CompilerGeneratedNames.IsStateMachineType(generatedType.Name)) + // fill in null for now, attribute providers will be filled in later + if (!generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { - if (generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null))) - { - _compilerGeneratedTypeToUserCodeMethod.TryAdd(generatedType, method); - callGraph.TrackCall(method, generatedType); - } - continue; + var alreadyAssociatedMethod = generatedTypeToTypeArgs[generatedType].CreatingMethod; + AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); } + continue; } if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(referencedMethod.Name)) @@ -271,34 +233,27 @@ referencedMethod.DeclaringType is var generatedType && } } break; - - case OperandType.InlineType: - { - // Detect struct-based state machines via initobj (the common case in modern .NET). - // Roslyn does not emit [AsyncStateMachineAttribute] on async partial methods, so - // TryGetStateMachineType above will miss them. We fall back to detecting the state - // machine type from the initobj instruction in the method body. - // TryAdd is used because the state machine may already have been registered via - // TryGetStateMachineType (for non-partial async methods that do have the attribute). - if (!isStateMachineMember && - instruction.OpCode.Code == Code.Initobj && - instruction.Operand is TypeReference typeRef && - CompilerGeneratedNames.IsStateMachineType(typeRef.Name) && - _context.TryResolve(typeRef) is TypeDefinition smTypeDef && - !IsSameOrNestedType(method.DeclaringType, smTypeDef)) - { - if (generatedTypeToTypeArgs.TryAdd(smTypeDef, new TypeArgumentInfo(method, null))) - { - _compilerGeneratedTypeToUserCodeMethod.TryAdd(smTypeDef, method); - callGraph.TrackCall(method, smTypeDef); - } - } - } - break; } } } + if (TryGetStateMachineType(method, out TypeDefinition? stateMachineType)) + { + Debug.Assert(stateMachineType.DeclaringType == type || + CompilerGeneratedNames.IsStateMachineOrDisplayClass(stateMachineType.DeclaringType.Name) && + stateMachineType.DeclaringType.DeclaringType == type); + callGraph.TrackCall(method, stateMachineType); + + if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) + { + var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; + AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); + } + // Already warned above if multiple methods map to the same type + // Fill in null for argument providers now, the real providers will be filled in later + generatedTypeToTypeArgs[stateMachineType] = new TypeArgumentInfo(method, null); + } + static bool IsSameOrNestedType(TypeDefinition type, TypeDefinition potentialOuterType) { do diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs index 0e9968f3d3c0a8..719c95bc2d2a53 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs @@ -438,8 +438,7 @@ public static void Test() } // Regression test for https://github.com/dotnet/runtime/issues/122800 - // Roslyn does not emit [AsyncStateMachineAttribute] on async partial methods, - // so ILLink must detect the state machine type from the method body instead. + // Roslyn does not emit [AsyncStateMachineAttribute] on async partial methods. partial class PartialAsyncMethodWithLambda { public static void Test()