Example #1
0
        static IEnumerable <TypeDefinition> GetCompilerGeneratedNestedTypes(TypeDefinition type)
        {
            foreach (var nestedType in type.NestedTypes)
            {
                if (!CompilerGeneratedNames.IsGeneratedMemberName(nestedType.Name))
                {
                    continue;
                }

                yield return(nestedType);

                foreach (var recursiveNestedType in GetCompilerGeneratedNestedTypes(nestedType))
                {
                    yield return(recursiveNestedType);
                }
            }
        }
Example #2
0
        void PopulateCacheForType(TypeDefinition type)
        {
            // Avoid repeat scans of the same type
            if (!_typesWithPopulatedCache.Add(type))
            {
                return;
            }

            var callGraph      = new CompilerGeneratedCallGraph();
            var callingMethods = new HashSet <MethodDefinition> ();

            void ProcessMethod(MethodDefinition method)
            {
                bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(method.DeclaringType.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 = callingMethods.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).
                if (method.Body != null)
                {
                    foreach (var instruction in method.Body.Instructions)
                    {
                        if (instruction.OpCode.OperandType != OperandType.InlineMethod)
                        {
                            continue;
                        }

                        MethodDefinition?lambdaOrLocalFunction = _context.TryResolve((MethodReference)instruction.Operand);
                        if (lambdaOrLocalFunction == null)
                        {
                            continue;
                        }

                        if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(lambdaOrLocalFunction.Name))
                        {
                            continue;
                        }

                        if (isStateMachineMember)
                        {
                            callGraph.TrackCall(method.DeclaringType, lambdaOrLocalFunction);
                        }
                        else
                        {
                            callGraph.TrackCall(method, lambdaOrLocalFunction);
                        }
                    }
                }

                // Discover state machine methods.
                if (!method.HasCustomAttributes)
                {
                    return;
                }

                foreach (var attribute in method.CustomAttributes)
                {
                    if (attribute.AttributeType.Namespace != "System.Runtime.CompilerServices")
                    {
                        continue;
                    }

                    switch (attribute.AttributeType.Name)
                    {
                    case "AsyncIteratorStateMachineAttribute":
                    case "AsyncStateMachineAttribute":
                    case "IteratorStateMachineAttribute":
                        TypeDefinition?stateMachineType = GetFirstConstructorArgumentAsType(attribute);
                        if (stateMachineType == null)
                        {
                            break;
                        }
                        Debug.Assert(stateMachineType.DeclaringType == type ||
                                     (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.DeclaringType.Name) &&
                                      stateMachineType.DeclaringType.DeclaringType == type));
                        callGraph.TrackCall(method, stateMachineType);
                        if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method))
                        {
                            var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType];
                            _context.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName());
                        }

                        break;
                    }
                }
            }

            // Look for state machine methods, and methods which call local functions.
            foreach (MethodDefinition method in type.Methods)
            {
                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.Methods)
                {
                    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 callingMethods)
            {
                foreach (var compilerGeneratedMember in callGraph.GetReachableMembers(userDefinedMethod))
                {
                    switch (compilerGeneratedMember)
                    {
                    case MethodDefinition nestedFunction:
                        Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name));
                        // Nested functions get suppressions from the user method only.
                        if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod))
                        {
                            var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction];
                            _context.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName());
                        }
                        break;

                    case TypeDefinition 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();
                    }
                }
            }
        }
Example #3
0
        // 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(IMemberDefinition sourceMember, [NotNullWhen(true)] out MethodDefinition?owningMethod)
        {
            owningMethod = null;
            if (sourceMember == null)
            {
                return(false);
            }

            MethodDefinition?compilerGeneratedMethod = sourceMember as MethodDefinition;

            if (compilerGeneratedMethod != null)
            {
                if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod))
                {
                    return(true);
                }
            }

            TypeDefinition sourceType = (sourceMember as TypeDefinition) ?? sourceMember.DeclaringType;

            if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue(sourceType, out owningMethod))
            {
                return(true);
            }

            if (!CompilerGeneratedNames.IsGeneratedMemberName(sourceMember.Name) && !CompilerGeneratedNames.IsGeneratedMemberName(sourceType.Name))
            {
                return(false);
            }

            // sourceType is a state machine type, or the type containing a lambda or local function.
            var typeToCache = sourceType;

            // 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 (typeToCache != null && CompilerGeneratedNames.IsGeneratedMemberName(typeToCache.Name))
            {
                typeToCache = typeToCache.DeclaringType;
            }

            if (typeToCache == null)
            {
                return(false);
            }

            // Search all methods to find the one which points to the type as its
            // state machine implementation.
            PopulateCacheForType(typeToCache);
            if (compilerGeneratedMethod != null)
            {
                if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod))
                {
                    return(true);
                }
            }

            if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue(sourceType, out owningMethod))
            {
                return(true);
            }

            return(false);
        }