1- // Licensed to the .NET Foundation under one or more agreements.
1+ // Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Collections . Generic ;
5+ using System . Diagnostics ;
6+ using System . Diagnostics . CodeAnalysis ;
57using ILLink . Shared ;
68using Mono . Cecil ;
9+ using Mono . Cecil . Cil ;
710
811namespace Mono . Linker
912{
@@ -12,27 +15,68 @@ public class CompilerGeneratedState
1215 {
1316 readonly LinkContext _context ;
1417 readonly Dictionary < TypeDefinition , MethodDefinition > _compilerGeneratedTypeToUserCodeMethod ;
18+ readonly Dictionary < MethodDefinition , MethodDefinition > _compilerGeneratedMethodToUserCodeMethod ;
1519 readonly HashSet < TypeDefinition > _typesWithPopulatedCache ;
1620
1721 public CompilerGeneratedState ( LinkContext context )
1822 {
1923 _context = context ;
2024 _compilerGeneratedTypeToUserCodeMethod = new Dictionary < TypeDefinition , MethodDefinition > ( ) ;
25+ _compilerGeneratedMethodToUserCodeMethod = new Dictionary < MethodDefinition , MethodDefinition > ( ) ;
2126 _typesWithPopulatedCache = new HashSet < TypeDefinition > ( ) ;
2227 }
2328
24- static bool HasRoslynCompilerGeneratedName ( TypeDefinition type ) =>
25- type . Name . Contains ( '<' ) || ( type . DeclaringType != null && HasRoslynCompilerGeneratedName ( type . DeclaringType ) ) ;
29+ static IEnumerable < TypeDefinition > GetCompilerGeneratedNestedTypes ( TypeDefinition type )
30+ {
31+ foreach ( var nestedType in type . NestedTypes ) {
32+ if ( ! CompilerGeneratedNames . IsGeneratedMemberName ( nestedType . Name ) )
33+ continue ;
34+
35+ yield return nestedType ;
36+
37+ foreach ( var recursiveNestedType in GetCompilerGeneratedNestedTypes ( nestedType ) )
38+ yield return recursiveNestedType ;
39+ }
40+ }
2641
2742 void PopulateCacheForType ( TypeDefinition type )
2843 {
2944 // Avoid repeat scans of the same type
3045 if ( ! _typesWithPopulatedCache . Add ( type ) )
3146 return ;
3247
33- foreach ( MethodDefinition method in type . Methods ) {
48+ var callGraph = new CallGraph ( ) ;
49+ var callingMethods = new HashSet < MethodDefinition > ( ) ;
50+
51+ void ProcessMethod ( MethodDefinition method )
52+ {
53+ if ( ! CompilerGeneratedNames . IsLambdaOrLocalFunction ( method . Name ) ) {
54+ // If it's not a nested function, track as an entry point to the call graph.
55+ var added = callingMethods . Add ( method ) ;
56+ Debug . Assert ( added ) ;
57+ }
58+
59+ // Discover calls or references to lambdas or local functions. This includes
60+ // calls to local functions, and lambda assignments (which use ldftn).
61+ if ( method . Body != null ) {
62+ foreach ( var instruction in method . Body . Instructions ) {
63+ if ( instruction . OpCode . OperandType != OperandType . InlineMethod )
64+ continue ;
65+
66+ MethodDefinition ? lambdaOrLocalFunction = _context . TryResolve ( ( MethodReference ) instruction . Operand ) ;
67+ if ( lambdaOrLocalFunction == null )
68+ continue ;
69+
70+ if ( ! CompilerGeneratedNames . IsLambdaOrLocalFunction ( lambdaOrLocalFunction . Name ) )
71+ continue ;
72+
73+ callGraph . TrackCall ( method , lambdaOrLocalFunction ) ;
74+ }
75+ }
76+
77+ // Discover state machine methods.
3478 if ( ! method . HasCustomAttributes )
35- continue ;
79+ return ;
3680
3781 foreach ( var attribute in method . CustomAttributes ) {
3882 if ( attribute . AttributeType . Namespace != "System.Runtime.CompilerServices" )
@@ -43,17 +87,53 @@ void PopulateCacheForType (TypeDefinition type)
4387 case "AsyncStateMachineAttribute" :
4488 case "IteratorStateMachineAttribute" :
4589 TypeDefinition ? stateMachineType = GetFirstConstructorArgumentAsType ( attribute ) ;
46- if ( stateMachineType != null ) {
47- if ( ! _compilerGeneratedTypeToUserCodeMethod . TryAdd ( stateMachineType , method ) ) {
48- var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod [ stateMachineType ] ;
49- _context . LogWarning ( new MessageOrigin ( method ) , DiagnosticId . MethodsAreAssociatedWithStateMachine , method . GetDisplayName ( ) , alreadyAssociatedMethod . GetDisplayName ( ) , stateMachineType . GetDisplayName ( ) ) ;
50- }
90+ if ( stateMachineType == null )
91+ break ;
92+ Debug . Assert ( stateMachineType . DeclaringType == type ||
93+ ( CompilerGeneratedNames . IsGeneratedMemberName ( stateMachineType . DeclaringType . Name ) &&
94+ stateMachineType . DeclaringType . DeclaringType == type ) ) ;
95+ if ( ! _compilerGeneratedTypeToUserCodeMethod . TryAdd ( stateMachineType , method ) ) {
96+ var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod [ stateMachineType ] ;
97+ _context . LogWarning ( new MessageOrigin ( method ) , DiagnosticId . MethodsAreAssociatedWithStateMachine , method . GetDisplayName ( ) , alreadyAssociatedMethod . GetDisplayName ( ) , stateMachineType . GetDisplayName ( ) ) ;
5198 }
5299
53100 break ;
54101 }
55102 }
56103 }
104+
105+ // Look for state machine methods, and methods which call local functions.
106+ foreach ( MethodDefinition method in type . Methods )
107+ ProcessMethod ( method ) ;
108+
109+ // Also scan compiler-generated state machine methods (in case they have calls to nested functions),
110+ // and nested functions inside compiler-generated closures (in case they call other nested functions).
111+
112+ // State machines can be emitted into lambda display classes, so we need to go down at least two
113+ // levels to find calls from iterator nested functions to other nested functions. We just recurse into
114+ // all compiler-generated nested types to avoid depending on implementation details.
115+
116+ foreach ( var nestedType in GetCompilerGeneratedNestedTypes ( type ) ) {
117+ foreach ( var method in nestedType . Methods )
118+ ProcessMethod ( method ) ;
119+ }
120+
121+ // Now we've discovered the call graphs for calls to nested functions.
122+ // Use this to map back from nested functions to the declaring user methods.
123+
124+ // Note: This maps all nested functions back to the user code, not to the immediately
125+ // declaring local function. The IL doesn't contain enough information in general for
126+ // us to determine the nesting of local functions and lambdas.
127+
128+ // Note: this only discovers nested functions which are referenced from the user
129+ // code or its referenced nested functions. There is no reliable way to determine from
130+ // IL which user code an unused nested function belongs to.
131+ foreach ( var userDefinedMethod in callingMethods ) {
132+ foreach ( var nestedFunction in callGraph . GetReachableMethods ( userDefinedMethod ) ) {
133+ Debug . Assert ( CompilerGeneratedNames . IsLambdaOrLocalFunction ( nestedFunction . Name ) ) ;
134+ _compilerGeneratedMethodToUserCodeMethod . Add ( nestedFunction , userDefinedMethod ) ;
135+ }
136+ }
57137 }
58138
59139 static TypeDefinition ? GetFirstConstructorArgumentAsType ( CustomAttribute attribute )
@@ -64,27 +144,54 @@ void PopulateCacheForType (TypeDefinition type)
64144 return attribute . ConstructorArguments [ 0 ] . Value as TypeDefinition ;
65145 }
66146
67- public MethodDefinition ? GetUserDefinedMethodForCompilerGeneratedMember ( IMemberDefinition sourceMember )
147+ // For state machine types/members, maps back to the state machine method.
148+ // For local functions and lambdas, maps back to the owning method in user code (not the declaring
149+ // lambda or local function, because the IL doesn't contain enough information to figure this out).
150+ public bool TryGetOwningMethodForCompilerGeneratedMember ( IMemberDefinition sourceMember , [ NotNullWhen ( true ) ] out MethodDefinition ? owningMethod )
68151 {
152+ owningMethod = null ;
69153 if ( sourceMember == null )
70- return null ;
154+ return false ;
71155
72- TypeDefinition compilerGeneratedType = ( sourceMember as TypeDefinition ) ?? sourceMember . DeclaringType ;
73- if ( _compilerGeneratedTypeToUserCodeMethod . TryGetValue ( compilerGeneratedType , out MethodDefinition ? userDefinedMethod ) )
74- return userDefinedMethod ;
156+ MethodDefinition ? compilerGeneratedMethod = sourceMember as MethodDefinition ;
157+ if ( compilerGeneratedMethod != null ) {
158+ if ( _compilerGeneratedMethodToUserCodeMethod . TryGetValue ( compilerGeneratedMethod , out owningMethod ) )
159+ return true ;
160+ }
75161
76- // Only handle async or iterator state machine
77- // So go to the declaring type and check if it's compiler generated (as a perf optimization)
78- if ( ! HasRoslynCompilerGeneratedName ( compilerGeneratedType ) || compilerGeneratedType . DeclaringType == null )
79- return null ;
162+ TypeDefinition sourceType = ( sourceMember as TypeDefinition ) ?? sourceMember . DeclaringType ;
163+
164+ if ( _compilerGeneratedTypeToUserCodeMethod . TryGetValue ( sourceType , out owningMethod ) )
165+ return true ;
166+
167+ if ( ! CompilerGeneratedNames . IsGeneratedMemberName ( sourceMember . Name ) && ! CompilerGeneratedNames . IsGeneratedMemberName ( sourceType . Name ) )
168+ return false ;
169+
170+ // sourceType is a state machine type, or the type containing a lambda or local function.
171+ var typeToCache = sourceType ;
80172
81- // Now go to its declaring type and search all methods to find the one which points to the type as its
173+ // Look in the declaring type if this is a compiler-generated type (state machine or display class).
174+ // State machines can be emitted into display classes, so we may also need to go one more level up.
175+ // To avoid depending on implementation details, we go up until we see a non-compiler-generated type.
176+ // This is the counterpart to GetCompilerGeneratedNestedTypes.
177+ while ( typeToCache != null && CompilerGeneratedNames . IsGeneratedMemberName ( typeToCache . Name ) )
178+ typeToCache = typeToCache . DeclaringType ;
179+
180+ if ( typeToCache == null )
181+ return false ;
182+
183+ // Search all methods to find the one which points to the type as its
82184 // state machine implementation.
83- PopulateCacheForType ( compilerGeneratedType . DeclaringType ) ;
84- if ( _compilerGeneratedTypeToUserCodeMethod . TryGetValue ( compilerGeneratedType , out userDefinedMethod ) )
85- return userDefinedMethod ;
185+ PopulateCacheForType ( typeToCache ) ;
186+ if ( compilerGeneratedMethod != null ) {
187+ if ( _compilerGeneratedMethodToUserCodeMethod . TryGetValue ( compilerGeneratedMethod , out owningMethod ) )
188+ return true ;
189+ }
190+
191+ if ( _compilerGeneratedTypeToUserCodeMethod . TryGetValue ( sourceType , out owningMethod ) )
192+ return true ;
86193
87- return null ;
194+ return false ;
88195 }
89196 }
90197}
0 commit comments