|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata;
using ILCompiler.Logging;
using ILLink.Shared;
using ILLink.Shared.DataFlow;
using Internal.IL;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
#nullable enable
namespace ILCompiler.Dataflow
{
// Currently this is implemented using heuristics
public class CompilerGeneratedState
{
private readonly record struct TypeArgumentInfo(
/// <summary>The method which calls the ctor for the given type</summary>
MethodDesc CreatingMethod,
/// <summary>Generic parameters of the creator used as type arguments for the type</summary>
IReadOnlyList<GenericParameterDesc?>? OriginalAttributes);
private readonly TypeCacheHashtable _typeCacheHashtable;
private readonly Logger _logger;
private readonly bool _disableGeneratedCodeHeuristics;
public CompilerGeneratedState(ILProvider ilProvider, Logger logger, bool disableGeneratedCodeHeuristics)
{
_typeCacheHashtable = new TypeCacheHashtable(
#if ILTRIM
ilProvider
#else
new AsyncMaskingILProvider(ilProvider)
#endif
);
_logger = logger;
_disableGeneratedCodeHeuristics = disableGeneratedCodeHeuristics;
}
private sealed class TypeCacheHashtable : LockFreeReaderHashtable<MetadataType, TypeCache>
{
private ILProvider _ilProvider;
public TypeCacheHashtable(ILProvider ilProvider) => _ilProvider = ilProvider;
protected override bool CompareKeyToValue(MetadataType key, TypeCache value) => key == value.Type;
protected override bool CompareValueToValue(TypeCache value1, TypeCache value2) => value1.Type == value2.Type;
protected override int GetKeyHashCode(MetadataType key) => key.GetHashCode();
protected override int GetValueHashCode(TypeCache value) => value.Type.GetHashCode();
protected override TypeCache CreateValueFromKey(MetadataType key)
=> new TypeCache(key, _ilProvider);
public TypeCache GetOrCreateValue(MetadataType key, out bool created)
{
TypeCache existingValue;
created = false;
if (TryGetValue(key, out existingValue))
return existingValue;
var newValue = CreateValueFromKey(key);
if (TryAdd(newValue))
{
created = true;
return newValue;
}
if (!TryGetValue(key, out existingValue))
throw new InvalidOperationException();
return existingValue;
}
}
private sealed class TypeCache
{
public readonly MetadataType Type;
// The MetadataType keys must be type definitions (uninstantiated) same goes for MethodDesc must be method definition
private Dictionary<MetadataType, MethodDesc>? _compilerGeneratedTypeToUserCodeMethod;
private Dictionary<MetadataType, TypeArgumentInfo>? _generatedTypeToTypeArgumentInfo;
private Dictionary<MethodDesc, MethodDesc>? _compilerGeneratedMethodToUserCodeMethod;
// Stores a map of methods which have corresponding compiler-generated members
// (either methods or state machine types) to those compiler-generated members,
// or null if the type has no methods with compiler-generated members.
private Dictionary<MethodDesc, List<TypeSystemEntity>>? _compilerGeneratedMembers;
// Stores a list of warnings to be emitted at the end of the cache construction
private List<(MessageOrigin, DiagnosticId, string[])>? _warnings;
internal void LogWarnings(Logger? logger)
{
if (_warnings == null || logger == null)
return;
foreach (var (origin, id, messageArgs) in _warnings)
logger.LogWarning(origin, id, messageArgs);
}
/// <summary>
/// Walks the type and its descendents to find Roslyn-compiler generated
/// code and gather information to map it back to original user code. If
/// a compiler-generated type is passed in directly, this method will walk
/// up and find the nearest containing user type. Returns the nearest user type,
/// or null if none was found.
/// </summary>
internal TypeCache(MetadataType type, ILProvider ilProvider)
{
Debug.Assert(type == type.GetTypeDefinition());
Debug.Assert(!CompilerGeneratedNames.IsStateMachineOrDisplayClass(type.Name));
Type = type;
var callGraph = new CompilerGeneratedCallGraph();
var userDefinedMethods = new HashSet<MethodDesc>();
var generatedTypeToTypeArgs = new Dictionary<MetadataType, TypeArgumentInfo>();
// We delay actually logging the warnings until the compiler-generated type info is
// populated for this type, because the type info is needed to determine whether a warning
// is suppressed.
void AddWarning(MessageOrigin origin, DiagnosticId id, params string[] messageArgs)
{
_warnings ??= new List<(MessageOrigin, DiagnosticId, string[])>();
_warnings.Add((origin, id, messageArgs));
}
void ProcessMethod(MethodDesc method)
{
Debug.Assert(method == method.GetTypicalMethodDefinition());
bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name);
if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name))
{
if (!isStateMachineMember)
{
// If it's not a nested function, track as an entry point to the call graph.
var added = userDefinedMethods.Add(method);
Debug.Assert(added);
}
}
else
{
// We don't expect lambdas or local functions to be emitted directly into
// state machine types.
Debug.Assert(!isStateMachineMember);
}
// Discover calls or references to lambdas or local functions. This includes
// calls to local functions, and lambda assignments (which use ldftn).
var methodBody = ilProvider.GetMethodIL(method);
if (methodBody != null)
{
ILReader reader = new ILReader(methodBody.GetILBytes());
while (reader.HasNext)
{
ILOpcode opcode = reader.ReadILOpcode();
switch (opcode)
{
case ILOpcode.ldftn:
case ILOpcode.ldtoken:
case ILOpcode.call:
case ILOpcode.callvirt:
case ILOpcode.newobj:
{
MethodDesc? referencedMethod = methodBody.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) as MethodDesc;
if (referencedMethod == null)
continue;
referencedMethod = referencedMethod.GetTypicalMethodDefinition();
// Find calls to state machine constructors that occur outside the type
if (referencedMethod.IsConstructor &&
referencedMethod.OwningType is MetadataType generatedType &&
// Don't consider calls in the same/nested type, like inside a static constructor
!IsSameOrNestedType(method.OwningType, generatedType) &&
CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
{
Debug.Assert(generatedType.IsTypeDefinition);
// 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;
}
if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(referencedMethod.Name))
continue;
if (isStateMachineMember)
{
callGraph.TrackCall((MetadataType)method.OwningType, referencedMethod);
}
else
{
callGraph.TrackCall(method, referencedMethod);
}
}
break;
case ILOpcode.stsfld:
case ILOpcode.ldsfld:
{
// Same as above, but stsfld instead of a call to the constructor
// Ldsfld may also trigger a cctor that creates a closure environment
FieldDesc? field = methodBody.GetObject(reader.ReadILToken()) as FieldDesc;
if (field == null)
continue;
field = field.GetTypicalFieldDefinition();
if (field.OwningType is MetadataType generatedType &&
// Don't consider field accesses in the same/nested type, like inside a static constructor
!IsSameOrNestedType(method.OwningType, generatedType) &&
CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
{
Debug.Assert(generatedType.IsTypeDefinition);
if (!generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null)))
{
// It's expected that there may be multiple methods associated with the same static closure environment.
// All of these methods will substitute the same type arguments into the closure environment
// (if it is generic). Don't warn.
}
continue;
}
}
break;
default:
reader.Skip(opcode);
break;
}
}
}
if (TryGetStateMachineType(method, out MetadataType? stateMachineType))
{
Debug.Assert(stateMachineType.ContainingType == type ||
(CompilerGeneratedNames.IsStateMachineOrDisplayClass(stateMachineType.ContainingType.Name) &&
stateMachineType.ContainingType.ContainingType == type));
Debug.Assert(stateMachineType == stateMachineType.GetTypeDefinition());
callGraph.TrackCall(method, stateMachineType);
_compilerGeneratedTypeToUserCodeMethod ??= new Dictionary<MetadataType, MethodDesc>();
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(TypeDesc type, TypeDesc potentialOuterType)
{
do
{
if (type == potentialOuterType)
return true;
if (type is not EcmaType ecmaType)
return false;
type = ecmaType.ContainingType;
} while (type != null);
return false;
}
}
// Look for state machine methods, and methods which call local functions.
foreach (MethodDesc method in type.GetMethods())
ProcessMethod(method);
// Also scan compiler-generated state machine methods (in case they have calls to nested functions),
// and nested functions inside compiler-generated closures (in case they call other nested functions).
// State machines can be emitted into lambda display classes, so we need to go down at least two
// levels to find calls from iterator nested functions to other nested functions. We just recurse into
// all compiler-generated nested types to avoid depending on implementation details.
foreach (var nestedType in GetCompilerGeneratedNestedTypes(type))
{
foreach (var method in nestedType.GetMethods())
ProcessMethod(method);
}
// Now we've discovered the call graphs for calls to nested functions.
// Use this to map back from nested functions to the declaring user methods.
// Note: This maps all nested functions back to the user code, not to the immediately
// declaring local function. The IL doesn't contain enough information in general for
// us to determine the nesting of local functions and lambdas.
// Note: this only discovers nested functions which are referenced from the user
// code or its referenced nested functions. There is no reliable way to determine from
// IL which user code an unused nested function belongs to.
foreach (var userDefinedMethod in userDefinedMethods)
{
var callees = callGraph.GetReachableMembers(userDefinedMethod);
if (!callees.Any())
continue;
_compilerGeneratedMembers ??= new Dictionary<MethodDesc, List<TypeSystemEntity>>();
_compilerGeneratedMembers.Add(userDefinedMethod, new List<TypeSystemEntity>(callees));
foreach (var compilerGeneratedMember in callees)
{
switch (compilerGeneratedMember)
{
case MethodDesc nestedFunction:
Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name));
// Nested functions get suppressions from the user method only.
_compilerGeneratedMethodToUserCodeMethod ??= new Dictionary<MethodDesc, MethodDesc>();
if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod))
{
var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction];
AddWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName());
}
break;
case MetadataType stateMachineType:
// Types in the call graph are always state machine types
// For those all their methods are not tracked explicitly in the call graph; instead, they
// are represented by the state machine type itself.
// We are already tracking the association of the state machine type to the user code method
// above, so no need to track it here.
Debug.Assert(CompilerGeneratedNames.IsStateMachineType(stateMachineType.Name));
break;
default:
throw new InvalidOperationException();
}
}
}
// Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute
// providers
foreach (var generatedType in generatedTypeToTypeArgs.Keys)
{
Debug.Assert(generatedType == generatedType.GetTypeDefinition());
if (generatedType.HasInstantiation) {
MapGeneratedTypeTypeParameters(generatedType, generatedTypeToTypeArgs);
// Finally, add resolved type arguments to the cache
var info = generatedTypeToTypeArgs[generatedType];
_generatedTypeToTypeArgumentInfo ??= new Dictionary<MetadataType, TypeArgumentInfo>();
if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, info))
{
var method = info.CreatingMethod;
var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod;
AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName());
}
}
}
/// Attempts to reverse the process of the compiler's alpha renaming. So if the original code was
/// something like this:
/// <code>
/// void M<T>() {
/// Action a = () => { Console.WriteLine(typeof(T)); };
/// }
/// </code>
/// The compiler will generate a nested class like this:
/// <code>
/// class <>c__DisplayClass0<T> {
/// public void <M>b__0() {
/// Console.WriteLine(typeof(T));
/// }
/// }
/// </code>
/// The task of this method is to figure out that the type parameter T in the nested class is the same
/// as the type parameter T in the parent method M.
/// <paramref name="generatedTypeToTypeArgs"/> acts as a memoization table to avoid recalculating the
/// mapping multiple times.
/// </summary>
void MapGeneratedTypeTypeParameters(
MetadataType generatedType,
Dictionary<MetadataType, TypeArgumentInfo> generatedTypeToTypeArgs)
{
Debug.Assert(CompilerGeneratedNames.IsStateMachineOrDisplayClass(generatedType.Name));
Debug.Assert(generatedType == generatedType.GetTypeDefinition());
var typeInfo = generatedTypeToTypeArgs[generatedType];
if (typeInfo.OriginalAttributes is not null)
{
return;
}
var method = typeInfo.CreatingMethod;
var body = ilProvider.GetMethodIL(method);
var typeArgs = new GenericParameterDesc?[generatedType.Instantiation.Length];
var typeRef = ScanForInit(generatedType, body);
if (typeRef is null)
{
return;
}
// The typeRef is going to be a generic instantiation with signature variables
// We need to figure out the actual generic parameters which were used to create these
// so instantiate the typeRef in the context of the method body where it is created
TypeDesc instantiatedType = typeRef.InstantiateSignature(method.OwningType.Instantiation, method.Instantiation);
for (int i = 0; i < instantiatedType.Instantiation.Length; i++)
{
var typeArg = instantiatedType.Instantiation[i];
// Start with the existing parameters, in case we can't find the mapped one
GenericParameterDesc? userAttrs = generatedType.Instantiation[i] as GenericParameterDesc;
// The type parameters of the state machine types are alpha renames of the
// the method parameters, so the type ref should always be a GenericParameter. However,
// in the case of nesting, there may be multiple renames, so if the parameter is a method
// we know we're done, but if it's another state machine, we have to keep looking to find
// the original owner of that state machine.
if (typeArg is GenericParameterDesc { Kind: { } kind } param)
{
if (kind == GenericParameterKind.Method)
{
userAttrs = param;
}
else
{
// Must be a type ref
if (method.OwningType is not MetadataType owningType || !CompilerGeneratedNames.IsStateMachineOrDisplayClass(owningType.Name))
{
userAttrs = param;
}
else
{
owningType = (MetadataType)owningType.GetTypeDefinition();
MapGeneratedTypeTypeParameters(owningType, generatedTypeToTypeArgs);
if (generatedTypeToTypeArgs[owningType].OriginalAttributes is { } owningAttrs)
{
userAttrs = owningAttrs[param.Index];
}
else
{
Debug.Fail("This should be impossible in valid code");
}
}
}
}
typeArgs[i] = userAttrs;
}
generatedTypeToTypeArgs[generatedType] = typeInfo with { OriginalAttributes = typeArgs };
}
static MetadataType? ScanForInit(MetadataType compilerGeneratedType, MethodIL body)
{
ILReader reader = new ILReader(body.GetILBytes());
while (reader.HasNext)
{
ILOpcode opcode = reader.ReadILOpcode();
bool handled = false;
MethodDesc? methodOperand = null;
switch (opcode)
{
case ILOpcode.newobj:
{
methodOperand = body.GetObject(reader.ReadILToken()) as MethodDesc;
if (methodOperand is MethodDesc { OwningType: MetadataType owningType }
&& compilerGeneratedType == owningType.GetTypeDefinition())
{
return owningType;
}
handled = true;
}
break;
case ILOpcode.ldftn:
case ILOpcode.ldtoken:
case ILOpcode.call:
case ILOpcode.callvirt:
methodOperand = body.GetObject(reader.ReadILToken()) as MethodDesc;
break;
case ILOpcode.stsfld:
case ILOpcode.ldsfld:
{
if (body.GetObject(reader.ReadILToken()) is FieldDesc { OwningType: MetadataType owningType }
&& compilerGeneratedType == owningType.GetTypeDefinition())
{
return owningType;
}
handled = true;
}
break;
default:
reader.Skip(opcode);
break;
}
// Also look for type substitutions into generic methods
// (such as AsyncTaskMethodBuilder::Start<TStateMachine>).
if (!handled && methodOperand is not null)
{
if (methodOperand != methodOperand.GetMethodDefinition())
{
foreach (var tr in methodOperand.Instantiation)
{
if (tr is MetadataType && tr != tr.GetTypeDefinition()
&& compilerGeneratedType == tr.GetTypeDefinition())
{
return tr as MetadataType;
}
}
}
}
}
return null;
}
}
public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotNullWhen(true)] out List<TypeSystemEntity>? callees)
{
if (_compilerGeneratedMembers == null)
{
callees = null;
return false;
}
return _compilerGeneratedMembers.TryGetValue(method, out callees);
}
public IReadOnlyList<GenericParameterDesc?>? GetGeneratedTypeAttributes(MetadataType type)
{
if (_generatedTypeToTypeArgumentInfo?.TryGetValue(type, out var typeInfo) == true)
{
return typeInfo.OriginalAttributes;
}
return null;
}
public bool TryGetOwningMethodForCompilerGeneratedMethod(MethodDesc compilerGeneratedMethod, [NotNullWhen(true)] out MethodDesc? owningMethod)
{
if (_compilerGeneratedMethodToUserCodeMethod == null)
{
owningMethod = null;
return false;
}
return _compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod);
}
public bool TryGetOwningMethodForCompilerGeneratedType(MetadataType compilerGeneratedType, [NotNullWhen(true)] out MethodDesc? owningMethod)
{
if (_compilerGeneratedTypeToUserCodeMethod == null)
{
owningMethod = null;
return false;
}
return _compilerGeneratedTypeToUserCodeMethod.TryGetValue(compilerGeneratedType, out owningMethod);
}
}
private static IEnumerable<MetadataType> GetCompilerGeneratedNestedTypes(MetadataType type)
{
foreach (var nestedType in type.GetNestedTypes())
{
if (!CompilerGeneratedNames.IsStateMachineOrDisplayClass(nestedType.Name))
continue;
yield return nestedType;
foreach (var recursiveNestedType in GetCompilerGeneratedNestedTypes(nestedType))
yield return recursiveNestedType;
}
}
public static bool IsHoistedLocal(FieldDesc field)
{
if (CompilerGeneratedNames.IsLambdaDisplayClass(field.OwningType.Name))
return true;
if (CompilerGeneratedNames.IsStateMachineType(field.OwningType.Name))
{
// Don't track the "current" field which is used for state machine return values,
// because this can be expensive to track.
return !CompilerGeneratedNames.IsStateMachineCurrentField(field.Name);
}
return false;
}
// "Nested function" refers to lambdas and local functions.
public static bool IsNestedFunctionOrStateMachineMember(TypeSystemEntity member)
{
if (member is MethodDesc method && CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name))
return true;
if (member.GetOwningType() is not MetadataType declaringType)
return false;
return CompilerGeneratedNames.IsStateMachineType(declaringType.Name);
}
public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] out MetadataType? stateMachineType)
{
stateMachineType = null;
// Discover state machine methods.
if (method is not EcmaMethod ecmaMethod)
return false;
CustomAttributeValue<TypeDesc>? decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute");
decodedAttribute ??= ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncStateMachineAttribute");
decodedAttribute ??= ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "IteratorStateMachineAttribute");
if (decodedAttribute == null)
return false;
stateMachineType = GetFirstConstructorArgumentAsType(decodedAttribute.Value) as MetadataType;
return stateMachineType != null;
}
private TypeCache? GetCompilerGeneratedStateForType(MetadataType type)
{
Debug.Assert(type.IsTypeDefinition);
MetadataType? userType = type;
// Look in the declaring type if this is a compiler-generated type (state machine or display class).
// State machines can be emitted into display classes, so we may also need to go one more level up.
// To avoid depending on implementation details, we go up until we see a non-compiler-generated type.
// This is the counterpart to GetCompilerGeneratedNestedTypes.
while (userType != null && CompilerGeneratedNames.IsStateMachineOrDisplayClass(userType.Name))
userType = userType.ContainingType as MetadataType;
if (userType is null)
return null;
var typeCache = _typeCacheHashtable.GetOrCreateValue(userType, out bool created);
if (created)
typeCache.LogWarnings(_logger);
return typeCache;
}
private static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttributeValue<TypeDesc> attribute)
{
if (attribute.FixedArguments.Length == 0)
return null;
return attribute.FixedArguments[0].Value as TypeDesc;
}
public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotNullWhen(true)] out List<TypeSystemEntity>? callees)
{
method = method.GetTypicalMethodDefinition();
callees = null;
if (IsNestedFunctionOrStateMachineMember(method))
return false;
if (method.OwningType is not MetadataType owningType)
return false;
var typeCache = GetCompilerGeneratedStateForType(owningType);
if (typeCache is null)
return false;
return typeCache.TryGetCompilerGeneratedCalleesForUserMethod(method, out callees);
}
/// <summary>
/// Gets the attributes on the "original" method of a generated type, i.e. the
/// attributes on the corresponding type parameters from the owning method.
/// </summary>
public IReadOnlyList<GenericParameterDesc?>? GetGeneratedTypeAttributes(MetadataType type)
{
MetadataType generatedType = (MetadataType)type.GetTypeDefinition();
Debug.Assert(CompilerGeneratedNames.IsStateMachineOrDisplayClass(generatedType.Name));
// Avoid the heuristics for .NET10+, where DynamicallyAccessedMembers flows to generated code
// because it is annotated with CompilerLoweringPreserveAttribute.
if (_disableGeneratedCodeHeuristics &&
generatedType.Module.Assembly is EcmaAssembly asm && asm.GetTargetFrameworkVersion() >= new Version(10, 0))
{
// Still run the logic for coverage to help us find bugs, but don't use the result.
GetCompilerGeneratedStateForType(generatedType);
return null;
}
var typeCache = GetCompilerGeneratedStateForType(generatedType);
if (typeCache is null)
return null;
return typeCache.GetGeneratedTypeAttributes(type);
}
// For state machine types/members, maps back to the state machine method.
// For local functions and lambdas, maps back to the owning method in user code (not the declaring
// lambda or local function, because the IL doesn't contain enough information to figure this out).
public bool TryGetOwningMethodForCompilerGeneratedMember(TypeSystemEntity sourceMember, [NotNullWhen(true)] out MethodDesc? owningMethod)
{
owningMethod = null;
if (sourceMember == null)
return false;
MetadataType? sourceType = ((sourceMember as TypeDesc) ?? sourceMember.GetOwningType())?.GetTypeDefinition() as MetadataType;
if (sourceType is null)
return false;
if (!IsNestedFunctionOrStateMachineMember(sourceMember))
return false;
// sourceType is a state machine type, or the type containing a lambda or local function.
// Search all methods to find the one which points to the type as its
// state machine implementation.
var typeCache = GetCompilerGeneratedStateForType(sourceType);
if (typeCache is null)
return false;
MethodDesc? compilerGeneratedMethod = sourceMember as MethodDesc;
if (compilerGeneratedMethod != null)
{
if (typeCache.TryGetOwningMethodForCompilerGeneratedMethod(compilerGeneratedMethod, out owningMethod))
return true;
}
if (typeCache.TryGetOwningMethodForCompilerGeneratedType(sourceType, out owningMethod))
return true;
return false;
}
public bool TryGetUserMethodForCompilerGeneratedMember(TypeSystemEntity sourceMember, [NotNullWhen(true)] out MethodDesc? userMethod)
{
userMethod = null;
if (sourceMember == null)
return false;
Debug.Assert(sourceMember is not MethodDesc sourceMethod || sourceMethod.IsTypicalMethodDefinition);
#if !ILTRIM
if (sourceMember is AsyncMethodVariant asyncVariant)
sourceMember = asyncVariant.Target;
#endif
TypeSystemEntity member = sourceMember;
MethodDesc? userMethodCandidate;
while (TryGetOwningMethodForCompilerGeneratedMember(member, out userMethodCandidate))
{
Debug.Assert(userMethodCandidate != member);
member = userMethodCandidate;
userMethod = userMethodCandidate;
}
if (userMethod != null)
{
Debug.Assert(!IsNestedFunctionOrStateMachineMember(userMethod));
return true;
}
return false;
}
}
}
|