Skip to content

Commit 7687323

Browse files
authored
Fix type parameter mapping for static closures (#118607)
Roslyn can generate code (specifically for nested async lambda state machines) where a static closure environment is referenced from a nested type of the display class. Handle this case by detecting nested classes, and never tracking type parameter instantiations from nested classes to outer classes. This fixes a reported stack overflow in ILLink and ILC.
1 parent 209778f commit 7687323

File tree

3 files changed

+83
-8
lines changed

3 files changed

+83
-8
lines changed

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ void ProcessMethod(MethodDesc method)
175175
// Find calls to state machine constructors that occur outside the type
176176
if (referencedMethod.IsConstructor &&
177177
referencedMethod.OwningType is MetadataType generatedType &&
178-
// Don't consider calls in the same type, like inside a static constructor
179-
method.OwningType != generatedType &&
178+
// Don't consider calls in the same/nested type, like inside a static constructor
179+
!IsSameOrNestedType(method.OwningType, generatedType) &&
180180
CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
181181
{
182182
Debug.Assert(generatedType.IsTypeDefinition);
@@ -216,8 +216,8 @@ referencedMethod.OwningType is MetadataType generatedType &&
216216
field = field.GetTypicalFieldDefinition();
217217

218218
if (field.OwningType is MetadataType generatedType &&
219-
// Don't consider field accesses in the same type, like inside a static constructor
220-
method.OwningType != generatedType &&
219+
// Don't consider field accesses in the same/nested type, like inside a static constructor
220+
!IsSameOrNestedType(method.OwningType, generatedType) &&
221221
CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
222222
{
223223
Debug.Assert(generatedType.IsTypeDefinition);
@@ -259,6 +259,22 @@ referencedMethod.OwningType is MetadataType generatedType &&
259259
// Fill in null for argument providers now, the real providers will be filled in later
260260
generatedTypeToTypeArgs[stateMachineType] = new TypeArgumentInfo(method, null);
261261
}
262+
263+
static bool IsSameOrNestedType(TypeDesc type, TypeDesc potentialOuterType)
264+
{
265+
do
266+
{
267+
if (type == potentialOuterType)
268+
return true;
269+
270+
if (type is not EcmaType ecmaType)
271+
return false;
272+
273+
type = ecmaType.ContainingType;
274+
} while (type != null);
275+
276+
return false;
277+
}
262278
}
263279

264280
// Look for state machine methods, and methods which call local functions.

src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ void ProcessMethod(MethodDefinition method)
180180
// Find calls to state machine constructors that occur outside the type
181181
if (referencedMethod.IsConstructor &&
182182
referencedMethod.DeclaringType is var generatedType &&
183-
// Don't consider calls in the same type, like inside a static constructor
184-
method.DeclaringType != generatedType &&
183+
// Don't consider calls in the same/nested type, like inside a static constructor
184+
!IsSameOrNestedType(method.DeclaringType, generatedType) &&
185185
CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
186186
{
187187
// fill in null for now, attribute providers will be filled in later
@@ -219,8 +219,8 @@ referencedMethod.DeclaringType is var generatedType &&
219219
continue;
220220

221221
if (field.DeclaringType is var generatedType &&
222-
// Don't consider field accesses in the same type, like inside a static constructor
223-
method.DeclaringType != generatedType &&
222+
// Don't consider field accesses in the same/nested type, like inside a static constructor
223+
!IsSameOrNestedType(method.DeclaringType, generatedType) &&
224224
CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
225225
{
226226
if (!generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null)))
@@ -253,6 +253,19 @@ referencedMethod.DeclaringType is var generatedType &&
253253
// Fill in null for argument providers now, the real providers will be filled in later
254254
generatedTypeToTypeArgs[stateMachineType] = new TypeArgumentInfo(method, null);
255255
}
256+
257+
static bool IsSameOrNestedType(TypeDefinition type, TypeDefinition potentialOuterType)
258+
{
259+
do
260+
{
261+
if (type == potentialOuterType)
262+
return true;
263+
264+
type = type.DeclaringType;
265+
} while (type != null);
266+
267+
return false;
268+
}
256269
}
257270

258271
// Look for state machine methods, and methods which call local functions.

src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public static void Main()
4848
CapturingLocalFunctionInsideIterator<int>();
4949
LambdaInsideAsync<int>();
5050
LocalFunctionInsideAsync<int>();
51+
NestedAsyncLambda.Test<int>();
52+
NestedAsyncLocalFunction.Test<int>();
5153
NestedStaticLambda.Test<int>();
5254
}
5355

@@ -423,6 +425,50 @@ void LocalFunction()
423425
LocalFunction();
424426
}
425427

428+
class NestedAsyncLambda
429+
{
430+
public static async void Test<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
431+
{
432+
var outer = async () =>
433+
{
434+
var inner =
435+
[ExpectedWarning("IL2090", "T", nameof(DynamicallyAccessedMemberTypes.PublicProperties))]
436+
() =>
437+
{
438+
_ = typeof(T).GetMethods();
439+
_ = typeof(T).GetProperties();
440+
};
441+
442+
inner();
443+
};
444+
445+
outer().Wait();
446+
}
447+
}
448+
449+
class NestedAsyncLocalFunction
450+
{
451+
public static void Test<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
452+
{
453+
Local1();
454+
455+
#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS
456+
[UnexpectedWarning("IL2091", "T", nameof(DynamicallyAccessedMemberTypes.PublicMethods), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)]
457+
#endif
458+
static async Task Local1()
459+
{
460+
Local2();
461+
462+
[ExpectedWarning("IL2090", "T", nameof(DynamicallyAccessedMemberTypes.PublicProperties))]
463+
static void Local2()
464+
{
465+
_ = typeof(T).GetMethods();
466+
_ = typeof(T).GetProperties();
467+
};
468+
};
469+
}
470+
}
471+
426472
class NestedStaticLambda
427473
{
428474
public static class Container<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>

0 commit comments

Comments
 (0)