예제 #1
0
                void MapGeneratedTypeTypeParameters(MetadataType generatedType)
                {
                    Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name));
                    Debug.Assert(generatedType == generatedType.GetTypeDefinition());

                    if (_generatedTypeToTypeArgumentInfo?.TryGetValue(generatedType, out var typeInfo) != true)
                    {
                        // This can happen for static (non-capturing) closure environments, where more than
                        // nested function can map to the same closure environment. Since the current functionality
                        // is based on a one-to-one relationship between environments (types) and methods, this is
                        // not supported.
                        return;
                    }

                    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.IsGeneratedType(owningType.Name))
                                {
                                    userAttrs = param;
                                }
예제 #2
0
            internal TypeCache(MetadataType type, Logger?logger, ILProvider ilProvider)
            {
                Debug.Assert(type == type.GetTypeDefinition());
                Debug.Assert(!CompilerGeneratedNames.IsGeneratedMemberName(type.Name));

                Type = type;

                var callGraph          = new CompilerGeneratedCallGraph();
                var userDefinedMethods = new HashSet <MethodDesc>();

                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();

                                if (referencedMethod.IsConstructor &&
                                    referencedMethod.OwningType is MetadataType generatedType &&
                                    // Don't consider calls in the same type, like inside a static constructor
                                    method.OwningType != generatedType &&
                                    CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
                                {
                                    Debug.Assert(generatedType.IsTypeDefinition);

                                    // fill in null for now, attribute providers will be filled in later
                                    _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>();

                                    if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null)))
                                    {
                                        var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod;
                                        logger?.LogWarning(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:
                            {
                                // Same as above, but stsfld instead of a call to the constructor
                                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 type, like inside a static constructor
                                    method.OwningType != generatedType &&
                                    CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name))
                                {
                                    Debug.Assert(generatedType.IsTypeDefinition);

                                    _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>();

                                    if (!_generatedTypeToTypeArgumentInfo.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.IsGeneratedMemberName(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];
                            logger?.LogWarning(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
                        _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>();
                        _generatedTypeToTypeArgumentInfo[stateMachineType] = new TypeArgumentInfo(method, null);
                    }
                }

                // 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];
                                logger?.LogWarning(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
                if (_generatedTypeToTypeArgumentInfo != null)
                {
                    foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys)
                    {
                        Debug.Assert(generatedType == generatedType.GetTypeDefinition());

                        if (HasGenericParameters(generatedType))
                        {
                            MapGeneratedTypeTypeParameters(generatedType);
                        }
                    }
                }