From 56b22b593c719ccb0729467c6d19a09369837951 Mon Sep 17 00:00:00 2001 From: Vitek Karas <10670590+vitek-karas@users.noreply.github.com> Date: Tue, 19 Jul 2022 01:19:48 -0700 Subject: [PATCH] Linker to NativeAOT sync (#71485) This brings changes from the linker repo to ilc roughly as of a week ago. This required some non-trivial changes to the code either already in ilc or the one coming from linker: * Port of CompilerGeneratedState * Modify it to use lock free hashtable (since it's a global cache and thus needs to be thread safe) * In ILC it requires to operate on generic definitions of everything - so add asserts and code to guarantee this * It's not a static class anymore, so pass the instance around as necessary * Requires IL access, so needs ILProvider on input * Cyclic dependency between Logger and CompilerGeneratedState, one needs the instance of the other to work. For now solved by setting it through a property on CompilerGeneratedState * Split of ReflectionMethodBodyScanner into AttributeDataFlow and GenericParameterDataFlow - mostly direct port from linker * The AttributeDataFlow works slightly differently in ilc so adopted the code there * Changes to DiagnosticContext * Can selectively disable Trim, AOT, Single-File diagnostics - used to correctly apply RUC, RDC and RAF suppressions * Improve handling of RUC and similar warnings in ReflectionMarker * Added ILOffset to MessageOrigin * Even though we already store file/column, the ILOffset is necessary since we use the MessageOrigin as a key in dictionary for recorded patterns and thus we need a way to make the origin unique for each callsite (and file/line/column might not be unique or if there's no PDB those will be null anyway). * Enable Equality/HashCode implementation on MessageOrigin, still no CompareTo since we don't need it for anything right now. --- eng/Versions.props | 1 + eng/testing/tests.singlefile.targets | 2 +- .../DependencyGraphTests.cs | 5 +- .../Compiler/Dataflow/ArrayValue.cs | 4 +- .../Compiler/Dataflow/AttributeDataFlow.cs | 140 ++++ .../Dataflow/CompilerGeneratedCallGraph.cs | 77 +++ .../Dataflow/CompilerGeneratedNames.cs | 74 +++ .../Dataflow/CompilerGeneratedState.cs | 601 ++++++++++++++++++ .../Compiler/Dataflow/DiagnosticContext.cs | 40 +- .../Compiler/Dataflow/DiagnosticUtilities.cs | 5 +- .../Compiler/Dataflow/EcmaExtensions.cs | 51 ++ .../Compiler/Dataflow/FieldReferenceValue.cs | 16 + .../Compiler/Dataflow/FlowAnnotations.cs | 163 +++-- .../Dataflow/GenericArgumentDataFlow.cs | 62 ++ .../Compiler/Dataflow/HandleCallAction.cs | 2 +- .../Compiler/Dataflow/HoistedLocalKey.cs | 32 + .../Compiler/Dataflow/InterproceduralState.cs | 159 +++++ .../Dataflow/LocalVariableReferenceValue.cs | 17 + .../Compiler/Dataflow/MethodBodyScanner.cs | 344 ++++++++-- .../Compiler/Dataflow/MethodProxy.cs | 11 +- .../Dataflow/ParameterReferenceValue.cs | 20 + .../Compiler/Dataflow/README.md | 9 +- .../Dataflow/ReferenceSource/ArrayValue.cs | 4 +- .../ReferenceSource/AttributeDataFlow.cs | 76 +++ .../CompilerGeneratedCallGraph.cs | 63 ++ .../ReferenceSource/CompilerGeneratedNames.cs | 72 +++ .../ReferenceSource/CompilerGeneratedState.cs | 427 +++++++++++++ .../ReferenceSource/DiagnosticContext.cs | 2 +- ...DynamicallyAccessedMembersTypeHierarchy.cs | 9 +- .../ReferenceSource/FieldReferenceValue.cs | 12 + .../ReferenceSource/FlowAnnotations.cs | 77 ++- .../GenericArgumentDataFlow.cs | 44 ++ .../ReferenceSource/HandleCallAction.cs | 9 +- .../ReferenceSource/HoistedLocalKey.cs | 30 + .../ReferenceSource/InterproceduralState.cs | 97 +++ .../LocalVariableReferenceValue.cs | 15 + .../ReferenceSource/MethodBodyScanner.cs | 310 +++++++-- .../Dataflow/ReferenceSource/MethodProxy.cs | 11 +- .../ParameterReferenceValue.cs | 16 + .../ReferenceSource/ReferenceValue.cs | 11 + .../ReferenceSource/ReflectionMarker.cs | 86 ++- .../ReflectionMethodBodyScanner.cs | 291 ++++----- ...RequireDynamicallyAccessedMembersAction.cs | 5 +- .../ReferenceSource/ScannerExtensions.cs | 6 - .../TrimAnalysisAssignmentPattern.cs | 51 ++ .../TrimAnalysisMethodCallPattern.cs | 76 +++ .../TrimAnalysisPatternStore.cs | 61 ++ .../Dataflow/ReferenceSource/ValueNode.cs | 9 +- .../Compiler/Dataflow/ReferenceValue.cs | 14 + .../Compiler/Dataflow/ReflectionMarker.cs | 116 ++-- .../Dataflow/ReflectionMethodBodyScanner.cs | 469 +++++--------- ...RequireDynamicallyAccessedMembersAction.cs | 2 +- .../Compiler/Dataflow/ScannerExtensions.cs | 5 - .../Dataflow/TrimAnalysisAssignmentPattern.cs | 74 +++ .../Dataflow/TrimAnalysisMethodCallPattern.cs | 93 +++ .../Dataflow/TrimAnalysisPatternStore.cs | 65 ++ .../Compiler/Dataflow/ValueNode.cs | 10 +- .../DataflowAnalyzedMethodNode.cs | 2 + .../ILCompiler.Compiler/Compiler/Logger.cs | 109 +++- .../Logging/CompilerGeneratedState.cs | 50 -- .../Compiler/Logging/MessageOrigin.cs | 69 +- .../ReferenceSource/CompilerGeneratedState.cs | 90 --- .../Compiler/UsageBasedMetadataManager.cs | 55 +- .../ILCompiler.Compiler.csproj | 22 +- src/coreclr/tools/aot/ILCompiler/Program.cs | 6 +- .../DataFlow/DefaultValueDictionary.cs | 25 + .../tools/aot/ILLink.Shared/DiagnosticId.cs | 16 +- .../aot/ILLink.Shared/ILLink.Shared.projitems | 2 +- .../aot/ILLink.Shared/SharedStrings.resx | 26 +- .../TrimAnalysis/DiagnosticContext.cs | 11 + .../TrimAnalysis/HandleCallAction.cs | 10 +- .../ILLink.Shared/TrimAnalysis/IntrinsicId.cs | 1 + .../ILLink.Shared/TrimAnalysis/Intrinsics.cs | 1 + .../TrimAnalysis/ReferenceKind.cs | 14 + .../TypeSystemProxy/WellKnownType.cs | 4 +- .../TestCasesRunner/ILCompilerDriver.cs | 6 +- .../nativeaot/SmokeTests/Dataflow/Dataflow.cs | 52 ++ 77 files changed, 4174 insertions(+), 950 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs create mode 100644 src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs diff --git a/eng/Versions.props b/eng/Versions.props index 49b36949e354f..541155ecb7df8 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -50,6 +50,7 @@ TODO: Remove pinned version once arcade supplies a compiler that enables the repo to compile. --> 4.4.0-1.22358.14 + 0.1.0 2.0.0-preview.4.22252.4 diff --git a/eng/testing/tests.singlefile.targets b/eng/testing/tests.singlefile.targets index f95e1957de028..18b342db71ac0 100644 --- a/eng/testing/tests.singlefile.targets +++ b/eng/testing/tests.singlefile.targets @@ -26,7 +26,7 @@ $(CoreCLRILCompilerDir)netstandard/ILCompiler.Build.Tasks.dll $(CoreCLRAotSdkDir) $(NetCoreAppCurrentTestHostSharedFrameworkPath) - $(NoWarn);IL3050;IL3051;IL3052;IL3055;IL1005 + $(NoWarn);IL3050;IL3051;IL3052;IL3055;IL1005;IL3002 false true diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs index dc1bf6c9cd0c4..5d7a4ab2eac3c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; - +using ILCompiler.Dataflow; using Internal.IL; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -67,11 +67,12 @@ public void TestDependencyGraphInvariants(EcmaMethod method) CompilationModuleGroup compilationGroup = new SingleFileCompilationModuleGroup(); NativeAotILProvider ilProvider = new NativeAotILProvider(); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, Logger.Null); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager(compilationGroup, context, new FullyBlockedMetadataBlockingPolicy(), new FullyBlockedManifestResourceBlockingPolicy(), null, new NoStackTraceEmissionPolicy(), new NoDynamicInvokeThunkGenerationPolicy(), - new ILLink.Shared.TrimAnalysis.FlowAnnotations(Logger.Null, ilProvider), UsageBasedMetadataGenerationOptions.None, + new ILLink.Shared.TrimAnalysis.FlowAnnotations(Logger.Null, ilProvider, compilerGeneratedState), UsageBasedMetadataGenerationOptions.None, Logger.Null, Array.Empty>(), Array.Empty(), Array.Empty()); CompilationBuilder builder = new RyuJitCompilationBuilder(context, compilationGroup) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs index 5c48191c8eca6..23740b13ef31a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs @@ -27,9 +27,9 @@ public static MultiValue Create(MultiValue size, TypeDesc elementType) return result; } - public static MultiValue Create(int size, TypeDesc elementType) + public static ArrayValue Create(int size, TypeDesc elementType) { - return new MultiValue(new ArrayValue(new ConstIntValue(size), elementType)); + return new ArrayValue(new ConstIntValue(size), elementType); } /// diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs new file mode 100644 index 0000000000000..afdc85ab9e06e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -0,0 +1,140 @@ +// 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.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection.Metadata; + +using ILCompiler.DependencyAnalysis; +using ILCompiler.Logging; + +using ILLink.Shared.TrimAnalysis; + +using Internal.TypeSystem; + +using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue; +using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly struct AttributeDataFlow + { + readonly Logger _logger; + readonly NodeFactory _factory; + readonly FlowAnnotations _annotations; + readonly MessageOrigin _origin; + + public AttributeDataFlow(Logger logger, NodeFactory factory, FlowAnnotations annotations, in MessageOrigin origin) + { + _annotations = annotations; + _factory = factory; + _logger = logger; + _origin = origin; + } + + public DependencyList? ProcessAttributeDataflow(MethodDesc method, CustomAttributeValue arguments) + { + DependencyList? result = null; + + // First do the dataflow for the constructor parameters if necessary. + if (_annotations.RequiresDataflowAnalysis(method)) + { + var builder = ImmutableArray.CreateBuilder(arguments.FixedArguments.Length); + foreach (var argument in arguments.FixedArguments) + { + builder.Add(argument.Value); + } + + ProcessAttributeDataflow(method, builder.ToImmutableArray(), ref result); + } + + // Named arguments next + TypeDesc attributeType = method.OwningType; + foreach (var namedArgument in arguments.NamedArguments) + { + if (namedArgument.Kind == CustomAttributeNamedArgumentKind.Field) + { + FieldDesc field = attributeType.GetField(namedArgument.Name); + if (field != null) + { + ProcessAttributeDataflow(field, namedArgument.Value, ref result); + } + } + else + { + Debug.Assert(namedArgument.Kind == CustomAttributeNamedArgumentKind.Property); + PropertyPseudoDesc property = ((MetadataType)attributeType).GetProperty(namedArgument.Name, null); + MethodDesc setter = property.SetMethod; + if (setter != null && setter.Signature.Length > 0 && !setter.Signature.IsStatic) + { + ProcessAttributeDataflow(setter, ImmutableArray.Create(namedArgument.Value), ref result); + } + } + } + + return result; + } + + void ProcessAttributeDataflow(MethodDesc method, ImmutableArray arguments, ref DependencyList? result) + { + for (int i = 0; i < method.Signature.Length; i++) + { + var parameterValue = _annotations.GetMethodParameterValue(method, i); + if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) + { + MultiValue value = GetValueForCustomAttributeArgument(arguments[i]); + var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: true, _logger); + RequireDynamicallyAccessedMembers(diagnosticContext, value, parameterValue, parameterValue.ParameterOrigin, ref result); + } + } + } + + public void ProcessAttributeDataflow(FieldDesc field, object? value, ref DependencyList? result) + { + var fieldValueCandidate = _annotations.GetFieldValue(field); + if (fieldValueCandidate is ValueWithDynamicallyAccessedMembers fieldValue + && fieldValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) + { + MultiValue valueNode = GetValueForCustomAttributeArgument(value); + var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: true, _logger); + RequireDynamicallyAccessedMembers(diagnosticContext, valueNode, fieldValue, new FieldOrigin(field), ref result); + } + } + + MultiValue GetValueForCustomAttributeArgument(object? argument) + => argument switch + { + TypeDesc td => new SystemTypeValue(td), + string str => new KnownStringValue(str), + null => NullValue.Instance, + // We shouldn't have gotten a None annotation from flow annotations since only string/Type can have annotations + _ => throw new InvalidOperationException() + }; + + void RequireDynamicallyAccessedMembers( + in DiagnosticContext diagnosticContext, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue, + Origin memberWithRequirements, + ref DependencyList? result) + { + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, memberWithRequirements); + requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); + + if (result == null) + { + result = reflectionMarker.Dependencies; + } + else + { + result.AddRange(reflectionMarker.Dependencies); + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs new file mode 100644 index 0000000000000..643e0a6a4bf13 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; + +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + sealed class CompilerGeneratedCallGraph + { + readonly Dictionary> _callGraph; + + public CompilerGeneratedCallGraph() => _callGraph = new Dictionary>(); + + void TrackCallInternal(TypeSystemEntity fromMember, TypeSystemEntity toMember) + { + if (!_callGraph.TryGetValue(fromMember, out HashSet? toMembers)) + { + toMembers = new HashSet(); + _callGraph.Add(fromMember, toMembers); + } + toMembers.Add(toMember); + } + + public void TrackCall(MethodDesc fromMethod, MethodDesc toMethod) + { + Debug.Assert(fromMethod.IsTypicalMethodDefinition); + Debug.Assert(toMethod.IsTypicalMethodDefinition); + Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(toMethod.Name)); + TrackCallInternal(fromMethod, toMethod); + } + + public void TrackCall(MethodDesc fromMethod, DefType toType) + { + Debug.Assert(fromMethod.IsTypicalMethodDefinition); + Debug.Assert(toType.IsTypeDefinition); + Debug.Assert(CompilerGeneratedNames.IsStateMachineType(toType.Name)); + TrackCallInternal(fromMethod, toType); + } + + public void TrackCall(DefType fromType, MethodDesc toMethod) + { + Debug.Assert(fromType.IsTypeDefinition); + Debug.Assert(toMethod.IsTypicalMethodDefinition); + Debug.Assert(CompilerGeneratedNames.IsStateMachineType(fromType.Name)); + Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(toMethod.Name)); + TrackCallInternal(fromType, toMethod); + } + + public IEnumerable GetReachableMembers(MethodDesc start) + { + Queue queue = new(); + HashSet visited = new(); + visited.Add(start); + queue.Enqueue(start); + while (queue.TryDequeue(out TypeSystemEntity? method)) + { + if (!_callGraph.TryGetValue(method, out HashSet? callees)) + continue; + + foreach (var callee in callees) + { + if (visited.Add(callee)) + { + queue.Enqueue(callee); + yield return callee; + } + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs new file mode 100644 index 0000000000000..c41d5fa11d7a3 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace ILCompiler.Dataflow +{ + sealed class CompilerGeneratedNames + { + internal static bool IsGeneratedMemberName(string memberName) + { + return memberName.Length > 0 && memberName[0] == '<'; + } + + internal static bool IsLambdaDisplayClass(string className) + { + if (!IsGeneratedMemberName(className)) + return false; + + // This is true for static lambdas (which are emitted into a class like <>c) + // and for instance lambdas (which are emitted into a class like <>c__DisplayClass1_0) + return className.StartsWith("<>c"); + } + + internal static bool IsStateMachineType(string typeName) + { + if (!IsGeneratedMemberName(typeName)) + return false; + + // State machines are generated into types with names like d__0 + // Or if its nested in a local function the name will look like <g__Local>d and so on + int i = typeName.LastIndexOf('>'); + if (i == -1) + return false; + + return typeName.Length > i + 1 && typeName[i + 1] == 'd'; + } + + internal static bool IsGeneratedType(string name) => IsStateMachineType(name) || IsLambdaDisplayClass(name); + + internal static bool IsLambdaOrLocalFunction(string methodName) => IsLambdaMethod(methodName) || IsLocalFunction(methodName); + + // Lambda methods have generated names like "b__0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration. + internal static bool IsLambdaMethod(string methodName) + { + if (!IsGeneratedMemberName(methodName)) + return false; + + int i = methodName.IndexOf('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and lambda ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'b'; + } + + // Local functions have generated names like "g__LocalFunction|0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration, and "LocalFunction" is the name of + // the local function. + internal static bool IsLocalFunction(string methodName) + { + if (!IsGeneratedMemberName(methodName)) + return false; + + int i = methodName.IndexOf('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and local function ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'g'; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs new file mode 100644 index 0000000000000..9cf7ee200ffde --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -0,0 +1,601 @@ +// 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 System.Reflection.PortableExecutable; +using ILCompiler.Logging; +using ILLink.Shared; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + // Currently this is implemented using heuristics + public class CompilerGeneratedState + { + readonly record struct TypeArgumentInfo( + /// The method which calls the ctor for the given type + MethodDesc CreatingMethod, + /// Attributes for the type, pulled from the creators type arguments + IReadOnlyList? OriginalAttributes); + + readonly TypeCacheHashtable _typeCacheHashtable; + + public CompilerGeneratedState(ILProvider ilProvider, Logger logger) + { + _typeCacheHashtable = new TypeCacheHashtable(ilProvider, logger); + } + + class TypeCacheHashtable : LockFreeReaderHashtable + { + private ILProvider _ilProvider; + private Logger? _logger; + + public TypeCacheHashtable(ILProvider ilProvider, Logger logger) => (_ilProvider, _logger) = (ilProvider, logger); + + 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, _logger, _ilProvider); + } + + class TypeCache + { + public readonly MetadataType Type; + + // The MetadataType keys must be type definitions (uninstantiated) same goes for MethodDesc must be method definition + private Dictionary? _compilerGeneratedTypeToUserCodeMethod; + private Dictionary? _generatedTypeToTypeArgumentInfo; + private Dictionary? _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>? _compilerGeneratedMembers; + + /// + /// 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. + /// + 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(); + + 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(); + MethodDesc? lambdaOrLocalFunction; + switch (opcode) + { + case ILOpcode.ldftn: + case ILOpcode.ldtoken: + case ILOpcode.call: + case ILOpcode.callvirt: + case ILOpcode.newobj: + lambdaOrLocalFunction = methodBody.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) as MethodDesc; + break; + + default: + lambdaOrLocalFunction = null; + reader.Skip(opcode); + break; + } + + if (lambdaOrLocalFunction == null) + continue; + + lambdaOrLocalFunction = lambdaOrLocalFunction.GetTypicalMethodDefinition(); + + if (lambdaOrLocalFunction.IsConstructor && + lambdaOrLocalFunction.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(); + + 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(lambdaOrLocalFunction.Name)) + continue; + + if (isStateMachineMember) + { + callGraph.TrackCall((MetadataType)method.OwningType, lambdaOrLocalFunction); + } + else + { + callGraph.TrackCall(method, lambdaOrLocalFunction); + } + } + } + + 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(); + 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(); + _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>(); + _compilerGeneratedMembers.Add(userDefinedMethod, new List(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(); + 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); + } + } + + /// + /// Check if the type itself is generic. The only difference is that + /// if the type is a nested type, the generic parameters from its + /// parent type don't count. + /// + static bool HasGenericParameters(MetadataType typeDef) + { + if (typeDef.ContainingType == null) + return typeDef.HasInstantiation; + + return typeDef.Instantiation.Length > typeDef.ContainingType.Instantiation.Length; + } + + 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; + } + else + { + owningType = (MetadataType)owningType.GetTypeDefinition(); + MapGeneratedTypeTypeParameters(owningType); + if (_generatedTypeToTypeArgumentInfo.TryGetValue(owningType, out var owningInfo) && + owningInfo.OriginalAttributes is { } owningAttrs) + { + userAttrs = owningAttrs[param.Index]; + } + } + } + } + + typeArgs[i] = userAttrs; + } + + _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; + } + + MetadataType? ScanForInit(MetadataType stateMachineType, MethodIL body) + { + ILReader reader = new ILReader(body.GetILBytes()); + while (reader.HasNext) + { + ILOpcode opcode = reader.ReadILOpcode(); + switch (opcode) + { + case ILOpcode.initobj: + case ILOpcode.newobj: + if (body.GetObject(reader.ReadILToken()) is MethodDesc { OwningType: MetadataType owningType } + && stateMachineType == owningType.GetTypeDefinition()) + { + return owningType; + } + break; + + default: + reader.Skip(opcode); + break; + } + } + return null; + } + } + + public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotNullWhen(true)] out List? callees) + { + if (_compilerGeneratedMembers == null) + { + callees = null; + return false; + } + + return _compilerGeneratedMembers.TryGetValue(method, out callees); + } + + public IReadOnlyList? 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); + } + } + + static IEnumerable GetCompilerGeneratedNestedTypes(MetadataType type) + { + foreach (var nestedType in type.GetNestedTypes()) + { + if (!CompilerGeneratedNames.IsGeneratedMemberName(nestedType.Name)) + continue; + + yield return nestedType; + + foreach (var recursiveNestedType in GetCompilerGeneratedNestedTypes(nestedType)) + yield return recursiveNestedType; + } + } + + public static bool IsHoistedLocal(FieldDesc field) + { + // Treat all fields on compiler-generated types as hoisted locals. + // This avoids depending on the name mangling scheme for hoisted locals. + var declaringTypeName = field.OwningType.Name; + return CompilerGeneratedNames.IsLambdaDisplayClass(declaringTypeName) || CompilerGeneratedNames.IsStateMachineType(declaringTypeName); + } + + // "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? decodedAttribute = null; + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute"); + if (decodedAttribute == null) + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncStateMachineAttribute"); + if (decodedAttribute == null) + 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.IsGeneratedMemberName(userType.Name)) + userType = userType.ContainingType as MetadataType; + + if (userType is null) + return null; + + return _typeCacheHashtable.GetOrCreateValue(userType); + } + + static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttributeValue attribute) + { + if (attribute.FixedArguments.Length == 0) + return null; + + return attribute.FixedArguments[0].Value as TypeDesc; + } + + public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotNullWhen(true)] out List? 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); + } + + /// + /// Gets the attributes on the "original" method of a generated type, i.e. the + /// attributes on the corresponding type parameters from the owning method. + /// + public IReadOnlyList? GetGeneratedTypeAttributes(MetadataType type) + { + MetadataType generatedType = (MetadataType)type.GetTypeDefinition(); + Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); + + 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; + + TypeSystemEntity member = sourceMember; + MethodDesc? userMethodCandidate = null; + while (TryGetOwningMethodForCompilerGeneratedMember(member, out userMethodCandidate)) + { + Debug.Assert(userMethodCandidate != member); + member = userMethodCandidate; + userMethod = userMethodCandidate; + } + + if (userMethod != null) + { + Debug.Assert(!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(userMethod)); + return true; + } + + return false; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs index afb38e0370a33..85dbe6df08497 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs @@ -8,19 +8,49 @@ namespace ILLink.Shared.TrimAnalysis { - readonly partial struct DiagnosticContext + public readonly partial struct DiagnosticContext { public readonly MessageOrigin Origin; - public readonly bool DiagnosticsEnabled; + readonly bool _diagnosticsEnabled; + readonly bool _suppressTrimmerDiagnostics; + readonly bool _suppressAotDiagnostics; + readonly bool _suppressSingleFileDiagnostics; readonly Logger _logger; public DiagnosticContext(in MessageOrigin origin, bool diagnosticsEnabled, Logger logger) - => (Origin, DiagnosticsEnabled, _logger) = (origin, diagnosticsEnabled, logger); + { + Origin = origin; + _diagnosticsEnabled = diagnosticsEnabled; + _suppressTrimmerDiagnostics = false; + _suppressAotDiagnostics = false; + _suppressSingleFileDiagnostics = false; + _logger = logger; + } + + public DiagnosticContext(in MessageOrigin origin, bool suppressTrimmerDiagnostics, bool suppressAotDiagnostics, bool suppressSingleFileDiagnostics, Logger logger) + { + Origin = origin; + _diagnosticsEnabled = true; + _suppressTrimmerDiagnostics = suppressTrimmerDiagnostics; + _suppressAotDiagnostics = suppressAotDiagnostics; + _suppressSingleFileDiagnostics = suppressSingleFileDiagnostics; + _logger = logger; + } public partial void AddDiagnostic(DiagnosticId id, params string[] args) { - if (DiagnosticsEnabled) - _logger.LogWarning(Origin, id, args); + if (!_diagnosticsEnabled) + return; + + string category = id.GetDiagnosticCategory(); + if (_suppressTrimmerDiagnostics && category == DiagnosticCategory.Trimming) + return; + if (_suppressAotDiagnostics && category == DiagnosticCategory.AOT) + return; + if (_suppressSingleFileDiagnostics && category == DiagnosticCategory.SingleFile) + return; + + _logger.LogWarning(Origin, id, args); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs index 08e9ce14177f2..31ec99f22285c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; -using ILCompiler.Logging; using ILLink.Shared; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -200,5 +199,9 @@ internal static bool DoesMemberRequire(this TypeSystemEntity member, string requ _ => false }; } + + internal const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); + internal const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute); + internal const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs index 77266e84ddca4..5d91895e35711 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs @@ -1,9 +1,13 @@ // 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 Internal.TypeSystem; using Internal.TypeSystem.Ecma; +using ILLink.Shared.TypeSystemProxy; + using MethodAttributes = System.Reflection.MethodAttributes; using FieldAttributes = System.Reflection.FieldAttributes; using TypeAttributes = System.Reflection.TypeAttributes; @@ -63,5 +67,52 @@ public static PropertyPseudoDesc GetProperty(this MetadataType mdType, string na return null; } + + public static ReferenceKind ParameterReferenceKind(this MethodDesc method, int index) + { + if (!method.Signature.IsStatic) + { + if (index == 0) + { + return method.OwningType.IsValueType ? ReferenceKind.Ref : ReferenceKind.None; + } + + index--; + } + + if (!method.Signature[index].IsByRef) + return ReferenceKind.None; + + // Parameter metadata index 0 is for return parameter + foreach (var parameterMetadata in method.GetParameterMetadata()) + { + if (parameterMetadata.Index != index + 1) + continue; + + if (parameterMetadata.In) + return ReferenceKind.In; + if (parameterMetadata.Out) + return ReferenceKind.Out; + return ReferenceKind.Ref; + } + + return ReferenceKind.None; + } + + public static bool IsByRefOrPointer(this TypeDesc type) + => type.IsByRef || type.IsPointer; + + public static TypeDesc GetOwningType(this TypeSystemEntity entity) + { + return entity switch + { + MethodDesc method => method.OwningType, + FieldDesc field => field.OwningType, + MetadataType type => type.ContainingType, + PropertyPseudoDesc property => property.OwningType, + EventPseudoDesc @event => @event.OwningType, + _ => throw new NotImplementedException("Unexpected type system entity") + }; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs new file mode 100644 index 0000000000000..30e5c53156fd3 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +using Internal.TypeSystem; + +#nullable enable + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record FieldReferenceValue(FieldDesc FieldDefinition) : ReferenceValue + { + public override SingleValue DeepCopy() => this; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index 489c2726eb278..4ed23ceb4bbfc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -6,14 +6,15 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; -using Internal.IL; -using Internal.TypeSystem; -using Internal.TypeSystem.Ecma; using ILCompiler; using ILCompiler.Dataflow; -using ILLink.Shared; +using ILLink.Shared.DataFlow; using ILLink.Shared.TypeSystemProxy; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + using Debug = System.Diagnostics.Debug; using WellKnownType = Internal.TypeSystem.WellKnownType; @@ -24,15 +25,20 @@ namespace ILLink.Shared.TrimAnalysis /// /// Caches dataflow annotations for type members. /// - public partial class FlowAnnotations + sealed public partial class FlowAnnotations { private readonly TypeAnnotationsHashtable _hashtable; private readonly Logger _logger; - public FlowAnnotations(Logger logger, ILProvider ilProvider) + public ILProvider ILProvider { get; } + public CompilerGeneratedState CompilerGeneratedState { get; } + + public FlowAnnotations(Logger logger, ILProvider ilProvider, CompilerGeneratedState compilerGeneratedState) { - _hashtable = new TypeAnnotationsHashtable(logger, ilProvider); + _hashtable = new TypeAnnotationsHashtable(logger, ilProvider, compilerGeneratedState); _logger = logger; + ILProvider = ilProvider; + CompilerGeneratedState = compilerGeneratedState; } public bool RequiresDataflowAnalysis(MethodDesc method) @@ -118,6 +124,14 @@ public DynamicallyAccessedMemberTypes GetTypeAnnotation(TypeDesc type) return GetAnnotations(type.GetTypeDefinition()).TypeAnnotation; } + public bool ShouldWarnWhenAccessedForReflection(TypeSystemEntity entity) => + entity switch + { + MethodDesc method => ShouldWarnWhenAccessedForReflection(method), + FieldDesc field => ShouldWarnWhenAccessedForReflection(field), + _ => false + }; + public DynamicallyAccessedMemberTypes GetGenericParameterAnnotation(GenericParameterDesc genericParameter) { if (genericParameter is not EcmaGenericParameter ecmaGenericParameter) @@ -205,6 +219,37 @@ public bool ShouldWarnWhenAccessedForReflection(FieldDesc field) return GetAnnotations(field.OwningType).TryGetAnnotation(field, out _); } + public static bool IsTypeInterestingForDataflow(TypeDesc type) + { + // NOTE: this method is not particulary fast. It's assumed that the caller limits + // calls to this method as much as possible. + + if (type.IsWellKnownType(WellKnownType.String)) + return true; + + if (!type.IsDefType) + return false; + + var metadataType = (MetadataType)type; + + foreach (var intf in type.RuntimeInterfaces) + { + if (intf.Name == "IReflect" && intf.Namespace == "System.Reflection") + return true; + } + + if (metadataType.Name == "IReflect" && metadataType.Namespace == "System.Reflection") + return true; + + do + { + if (metadataType.Name == "Type" && metadataType.Namespace == "System") + return true; + } while ((metadataType = metadataType.MetadataBaseType) != null); + + return false; + } + private TypeAnnotations GetAnnotations(TypeDesc type) { return _hashtable.GetOrCreateValue(type); @@ -214,8 +259,9 @@ private class TypeAnnotationsHashtable : LockFreeReaderHashtable (_logger, _ilProvider) = (logger, ilProvider); + public TypeAnnotationsHashtable(Logger logger, ILProvider ilProvider, CompilerGeneratedState compilerGeneratedState) => (_logger, _ilProvider, _compilerGeneratedState) = (logger, ilProvider, compilerGeneratedState); private static DynamicallyAccessedMemberTypes GetMemberTypesForDynamicallyAccessedMembersAttribute(MetadataReader reader, CustomAttributeHandleCollection customAttributeHandles) { @@ -525,22 +571,38 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) } DynamicallyAccessedMemberTypes[]? typeGenericParameterAnnotations = null; - foreach (EcmaGenericParameter genericParameter in ecmaType.Instantiation) + if (ecmaType.Instantiation.Length > 0) { - GenericParameter genericParameterDef = reader.GetGenericParameter(genericParameter.Handle); - - var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute(reader, genericParameterDef.GetCustomAttributes()); - if (annotation != DynamicallyAccessedMemberTypes.None) + var attrs = GetGeneratedTypeAttributes(ecmaType); + for (int genericParameterIndex = 0; genericParameterIndex < ecmaType.Instantiation.Length; genericParameterIndex++) { - if (typeGenericParameterAnnotations == null) - typeGenericParameterAnnotations = new DynamicallyAccessedMemberTypes[ecmaType.Instantiation.Length]; - typeGenericParameterAnnotations[genericParameter.Index] = annotation; + EcmaGenericParameter genericParameter = (EcmaGenericParameter)ecmaType.Instantiation[genericParameterIndex]; + genericParameter = (attrs?[genericParameterIndex] as EcmaGenericParameter) ?? genericParameter; + GenericParameter genericParameterDef = reader.GetGenericParameter(genericParameter.Handle); + var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute(reader, genericParameterDef.GetCustomAttributes()); + if (annotation != DynamicallyAccessedMemberTypes.None) + { + if (typeGenericParameterAnnotations == null) + typeGenericParameterAnnotations = new DynamicallyAccessedMemberTypes[ecmaType.Instantiation.Length]; + typeGenericParameterAnnotations[genericParameter.Index] = annotation; + } } } return new TypeAnnotations(ecmaType, typeAnnotation, annotatedMethods.ToArray(), annotatedFields.ToArray(), typeGenericParameterAnnotations); } + private IReadOnlyList? GetGeneratedTypeAttributes(EcmaType typeDef) + { + if (!CompilerGeneratedNames.IsGeneratedType(typeDef.Name)) + { + return null; + } + var attrs = _compilerGeneratedState.GetGeneratedTypeAttributes(typeDef); + Debug.Assert(attrs is null || attrs.Count == typeDef.Instantiation.Length); + return attrs; + } + private static bool ScanMethodBodyForFieldAccess(MethodIL body, bool write, out FieldDesc? found) { // Tries to find the backing field for a property getter/setter. @@ -595,37 +657,6 @@ private static bool ScanMethodBodyForFieldAccess(MethodIL body, bool write, out return true; } - - private bool IsTypeInterestingForDataflow(TypeDesc type) - { - // NOTE: this method is not particulary fast. It's assumed that the caller limits - // calls to this method as much as possible. - - if (type.IsWellKnownType(WellKnownType.String)) - return true; - - if (!type.IsDefType) - return false; - - var metadataType = (MetadataType)type; - - foreach (var intf in type.RuntimeInterfaces) - { - if (intf.Name == "IReflect" && intf.Namespace == "System.Reflection") - return true; - } - - if (metadataType.Name == "IReflect" && metadataType.Namespace == "System.Reflection") - return true; - - do - { - if (metadataType.Name == "Type" && metadataType.Namespace == "System") - return true; - } while ((metadataType = metadataType.MetadataBaseType) != null); - - return false; - } } internal void ValidateMethodAnnotationsAreSame(MethodDesc method, MethodDesc baseMethod) @@ -891,5 +922,43 @@ public FieldAnnotation(FieldDesc field, DynamicallyAccessedMemberTypes annotatio internal partial MethodParameterValue GetMethodParameterValue(MethodProxy method, int parameterIndex) => GetMethodParameterValue(method, parameterIndex, GetParameterAnnotation(method.Method, parameterIndex + (method.IsStatic() ? 0 : 1))); + + internal SingleValue GetFieldValue(FieldDesc field) + => field.Name switch + { + "EmptyTypes" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_Type) => ArrayValue.Create(0, field.OwningType), + "Empty" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_String) => new KnownStringValue(string.Empty), + _ => new FieldValue(field, GetFieldAnnotation(field)) + }; + + internal SingleValue GetTypeValueFromGenericArgument(TypeDesc genericArgument) + { + if (genericArgument is GenericParameterDesc inputGenericParameter) + { + return GetGenericParameterValue(inputGenericParameter); + } + else if (genericArgument is MetadataType genericArgumentType) + { + if (genericArgumentType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_Nullable_T)) + { + var innerGenericArgument = genericArgumentType.Instantiation.Length == 1 ? genericArgumentType.Instantiation[0] : null; + switch (innerGenericArgument) + { + case GenericParameterDesc gp: + return new NullableValueWithDynamicallyAccessedMembers(genericArgumentType, + new GenericParameterValue(gp, GetGenericParameterAnnotation(gp))); + + case TypeDesc underlyingType: + return new NullableSystemTypeValue(genericArgumentType, new SystemTypeValue(underlyingType)); + } + } + // All values except for Nullable, including Nullable<> (with no type arguments) + return new SystemTypeValue(genericArgumentType); + } + else + { + return UnknownValue.Instance; + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs new file mode 100644 index 0000000000000..1bdc58eee2922 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +using ILCompiler.DependencyAnalysis; +using ILCompiler.Logging; + +using ILLink.Shared.TrimAnalysis; + +using Internal.TypeSystem; + +using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly struct GenericArgumentDataFlow + { + readonly Logger _logger; + readonly NodeFactory _factory; + readonly FlowAnnotations _annotations; + readonly MessageOrigin _origin; + + public GenericArgumentDataFlow(Logger logger, NodeFactory factory, FlowAnnotations annotations, in MessageOrigin origin) + { + _logger = logger; + _factory = factory; + _annotations = annotations; + _origin = origin; + } + + public DependencyList ProcessGenericArgumentDataFlow(GenericParameterDesc genericParameter, TypeDesc genericArgument) + { + var genericParameterValue = _annotations.GetGenericParameterValue(genericParameter); + Debug.Assert(genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); + + MultiValue genericArgumentValue = _annotations.GetTypeValueFromGenericArgument(genericArgument); + + var diagnosticContext = new DiagnosticContext( + _origin, + _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + _logger); + return RequireDynamicallyAccessedMembers(diagnosticContext, genericArgumentValue, genericParameterValue, new GenericParameterOrigin(genericParameter)); + } + + DependencyList RequireDynamicallyAccessedMembers( + in DiagnosticContext diagnosticContext, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue, + Origin memberWithRequirements) + { + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, memberWithRequirements); + requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); + return reflectionMarker.Dependencies; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index 9b41163b33ad4..e885dc796f9f5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -75,7 +75,7 @@ partial struct HandleCallAction return false; } - private partial bool TryResolveTypeNameForCreateInstance(in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) + private partial bool TryResolveTypeNameForCreateInstanceAndMark(in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) { // TODO: niche APIs that we probably shouldn't even have added // We have to issue a warning, otherwise we could break the app without a warning. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs new file mode 100644 index 0000000000000..11be949bed019 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs @@ -0,0 +1,32 @@ +// 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.Diagnostics; +using Internal.TypeSystem; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + // This represents a field which has been generated by the compiler as the + // storage location for a hoisted local (a local variable which is lifted to a + // field on a state machine type, or to a field on a closure accessed by lambdas + // or local functions). + public readonly struct HoistedLocalKey : IEquatable + { + readonly FieldDesc Field; + + public HoistedLocalKey(FieldDesc field) + { + Debug.Assert(CompilerGeneratedState.IsHoistedLocal(field)); + Field = field; + } + + public bool Equals(HoistedLocalKey other) => Field.Equals(other.Field); + + public override bool Equals(object? obj) => obj is HoistedLocalKey other && Equals(other); + + public override int GetHashCode() => Field.GetHashCode(); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs new file mode 100644 index 0000000000000..304fa353d69e8 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs @@ -0,0 +1,159 @@ +// 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 ILLink.Shared.DataFlow; +using Internal.IL; +using Internal.TypeSystem; + +using HoistedLocalState = ILLink.Shared.DataFlow.DefaultValueDictionary< + ILCompiler.Dataflow.HoistedLocalKey, + ILLink.Shared.DataFlow.ValueSet>; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + // Wrapper that implements IEquatable for MethodBody. + readonly record struct MethodBodyValue(MethodIL MethodBody) : IEquatable + { + bool IEquatable.Equals(ILCompiler.Dataflow.MethodBodyValue other) + => other.MethodBody.OwningMethod == MethodBody.OwningMethod; + + public override int GetHashCode() => MethodBody.OwningMethod.GetHashCode(); + } + + // Tracks the set of methods which get analyzer together during interprocedural analysis, + // and the possible states of hoisted locals in state machine methods and lambdas/local functions. + struct InterproceduralState : IEquatable + { + readonly ILProvider _ilProvider; + public ValueSet MethodBodies; + public HoistedLocalState HoistedLocals; + readonly InterproceduralStateLattice lattice; + + public InterproceduralState(ILProvider ilProvider, ValueSet methodBodies, HoistedLocalState hoistedLocals, InterproceduralStateLattice lattice) + => (_ilProvider, MethodBodies, HoistedLocals, this.lattice) = (ilProvider, methodBodies, hoistedLocals, lattice); + + public bool Equals(InterproceduralState other) + => MethodBodies.Equals(other.MethodBodies) && HoistedLocals.Equals(other.HoistedLocals); + + public InterproceduralState Clone() + => new(_ilProvider, MethodBodies.Clone(), HoistedLocals.Clone(), lattice); + + public void TrackMethod(MethodDesc method) + { + if (!TryGetMethodBody(method, out MethodIL? methodBody)) + return; + + TrackMethod(methodBody); + } + + public void TrackMethod(MethodIL methodBody) + { + Debug.Assert(methodBody.GetMethodILDefinition() == methodBody); + methodBody = GetInstantiatedMethodIL(methodBody); + + // Work around the fact that ValueSet is readonly + var methodsList = new List(MethodBodies); + methodsList.Add(new MethodBodyValue(methodBody)); + + // For state machine methods, also scan the state machine members. + // Simplification: assume that all generated methods of the state machine type are + // reached at the point where the state machine method is reached. + if (CompilerGeneratedState.TryGetStateMachineType(methodBody.OwningMethod, out MetadataType? stateMachineType)) + { + foreach (var stateMachineMethod in stateMachineType.GetMethods()) + { + Debug.Assert(!CompilerGeneratedNames.IsLambdaOrLocalFunction(stateMachineMethod.Name)); + if (TryGetMethodBody(stateMachineMethod, out MethodIL? stateMachineMethodBody)) + { + stateMachineMethodBody = GetInstantiatedMethodIL(stateMachineMethodBody); + methodsList.Add(new MethodBodyValue(stateMachineMethodBody)); + } + } + } + + MethodBodies = new ValueSet(methodsList); + + static MethodIL GetInstantiatedMethodIL(MethodIL methodIL) + { + if (methodIL.OwningMethod.HasInstantiation || methodIL.OwningMethod.OwningType.HasInstantiation) + { + // We instantiate the body over the generic parameters. + // + // This will transform references like "call Foo.Method(!0 arg)" into + // "call Foo.Method(T arg)". We do this to avoid getting confused about what + // context the generic variables refer to - in the above example, we would see + // two !0's - one refers to the generic parameter of the type that owns the method with + // the call, but the other one (in the signature of "Method") actually refers to + // the generic parameter of Foo. + // + // If we don't do this translation, retrieving the signature of the called method + // would attempt to do bogus substitutions. + // + // By doing the following transformation, we ensure we don't see the generic variables + // that need to be bound to the context of the currently analyzed method. + methodIL = new InstantiatedMethodIL(methodIL.OwningMethod, methodIL); + } + + return methodIL; + } + } + + public void SetHoistedLocal(HoistedLocalKey key, MultiValue value) + { + // For hoisted locals, we track the entire set of assigned values seen + // in the closure of a method, so setting a hoisted local value meets + // it with any existing value. + HoistedLocals.Set(key, + lattice.HoistedLocalsLattice.ValueLattice.Meet( + HoistedLocals.Get(key), value)); + } + + public MultiValue GetHoistedLocal(HoistedLocalKey key) + => HoistedLocals.Get(key); + + bool TryGetMethodBody(MethodDesc method, [NotNullWhen(true)] out MethodIL? methodBody) + { + methodBody = null; + + if (method.IsPInvoke) + return false; + + MethodIL methodIL = _ilProvider.GetMethodIL(method); + if (methodIL == null) + return false; + + methodBody = methodIL; + return true; + } + } + + struct InterproceduralStateLattice : ILattice + { + private readonly ILProvider _ilProvider; + public readonly ValueSetLattice MethodBodyLattice; + public readonly DictionaryLattice> HoistedLocalsLattice; + + public InterproceduralStateLattice( + ILProvider ilProvider, + ValueSetLattice methodBodyLattice, + DictionaryLattice> hoistedLocalsLattice) + => (_ilProvider, MethodBodyLattice, HoistedLocalsLattice) = (ilProvider, methodBodyLattice, hoistedLocalsLattice); + + public InterproceduralState Top => new InterproceduralState(_ilProvider, MethodBodyLattice.Top, HoistedLocalsLattice.Top, this); + + public InterproceduralState Meet(InterproceduralState left, InterproceduralState right) + => new( + _ilProvider, + MethodBodyLattice.Meet(left.MethodBodies, right.MethodBodies), + HoistedLocalsLattice.Meet(left.HoistedLocals, right.HoistedLocals), + this); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs new file mode 100644 index 0000000000000..2d54ba80d8d1e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +#nullable enable + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record LocalVariableReferenceValue(int LocalIndex) : ReferenceValue + { + public override SingleValue DeepCopy() + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs index 535ae2d1cf2f0..4fde62216e253 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs @@ -3,10 +3,17 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Reflection.Metadata; + +using ILCompiler.Logging; + +using ILLink.Shared; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; + using Internal.IL; using Internal.TypeSystem; @@ -23,36 +30,37 @@ namespace ILCompiler.Dataflow { public MultiValue Value { get; } - /// - /// True if the value is on the stack as a byref - /// - public bool IsByRef { get; } - public StackSlot() { Value = new MultiValue(UnknownValue.Instance); - IsByRef = false; } - public StackSlot(SingleValue value, bool isByRef = false) + public StackSlot(SingleValue value) { Value = new MultiValue(value); - IsByRef = isByRef; } - public StackSlot(MultiValue value, bool isByRef = false) + public StackSlot(MultiValue value) { Value = value; - IsByRef = isByRef; } } abstract partial class MethodBodyScanner { + protected readonly InterproceduralStateLattice InterproceduralStateLattice; protected static ValueSetLattice MultiValueLattice => default; + protected readonly FlowAnnotations _annotations; + internal MultiValue ReturnValue { private set; get; } + protected MethodBodyScanner(FlowAnnotations annotations) + { + _annotations = annotations; + InterproceduralStateLattice = new InterproceduralStateLattice(annotations.ILProvider, default, default); + } + protected virtual void WarnAboutInvalidILInMethod(MethodIL method, int ilOffset) { } @@ -193,6 +201,34 @@ public int MoveNext(int offset) } } + [Conditional("DEBUG")] + static void ValidateNoReferenceToReference(ValueBasicBlockPair?[] locals, MethodIL method, int ilOffset) + { + for (int localVariableIndex = 0; localVariableIndex < locals.Length; localVariableIndex++) + { + ValueBasicBlockPair? localVariable = locals[localVariableIndex]; + if (localVariable == null) + continue; + + MultiValue localValue = localVariable.Value.Value; + foreach (var val in localValue) + { + if (val is LocalVariableReferenceValue reference) + { + ValueBasicBlockPair? referenceLocalVariable = locals[reference.LocalIndex]; + if (referenceLocalVariable.HasValue + && referenceLocalVariable.Value.Value.Any(v => v is ReferenceValue)) + { + throw new InvalidOperationException(MessageContainer.CreateErrorMessage( + $"In method {method.OwningMethod.GetDisplayName()}, local variable {localVariableIndex} references variable {reference.LocalIndex} which is a reference.", + (int)DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin(method, ilOffset)).ToMSBuildString()); + } + } + } + } + } + private static void StoreMethodLocalValue( ValueBasicBlockPair?[] valueCollection, in MultiValue valueToStore, @@ -253,7 +289,65 @@ public int MoveNext(int offset) } } - public void Scan(MethodIL methodBody) + // Scans the method as well as any nested functions (local functions or lambdas) and state machines + // reachable from it. + public virtual void InterproceduralScan(MethodIL startingMethodBody) + { + MethodDesc startingMethod = startingMethodBody.OwningMethod; + Debug.Assert(startingMethod.IsTypicalMethodDefinition); + + // We should never have created a DataFlowAnalyzedMethodNode for compiler generated methods + // since their data flow analysis is handled as part of their parent method analysis. + Debug.Assert(!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(startingMethod)); + + // Note that the default value of a hoisted local will be MultiValueLattice.Top, not UnknownValue.Instance. + // This ensures that there are no warnings for the "unassigned state" of a parameter. + // Definite assignment should ensure that there is no way for this to be an analysis hole. + var interproceduralState = InterproceduralStateLattice.Top; + + var oldInterproceduralState = interproceduralState.Clone(); + interproceduralState.TrackMethod(startingMethodBody); + + while (!interproceduralState.Equals(oldInterproceduralState)) + { + oldInterproceduralState = interproceduralState.Clone(); + + // Flow state through all methods encountered so far, as long as there + // are changes discovered in the hoisted local state on entry to any method. + foreach (var methodBodyValue in oldInterproceduralState.MethodBodies) + Scan(methodBodyValue.MethodBody, ref interproceduralState); + } + +#if DEBUG + // Validate that the compiler-generated callees tracked by the compiler-generated state + // are the same set of methods that we discovered and scanned above. + if (_annotations.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod(startingMethod, out List? compilerGeneratedCallees)) + { + var calleeMethods = compilerGeneratedCallees.OfType(); + // https://github.com/dotnet/linker/issues/2845 + // Disabled asserts due to a bug + // Debug.Assert (interproceduralState.Count == 1 + calleeMethods.Count ()); + // foreach (var method in calleeMethods) + // Debug.Assert (interproceduralState.Any (kvp => kvp.Key.Method == method)); + } + else + { + Debug.Assert(interproceduralState.MethodBodies.Count() == 1); + } +#endif + } + + void TrackNestedFunctionReference(MethodDesc referencedMethod, ref InterproceduralState interproceduralState) + { + MethodDesc method = referencedMethod.GetTypicalMethodDefinition(); + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) + return; + + interproceduralState.TrackMethod(method); + } + + protected virtual void Scan(MethodIL methodBody, ref InterproceduralState interproceduralState) { MethodDesc thisMethod = methodBody.OwningMethod; @@ -270,6 +364,7 @@ public void Scan(MethodIL methodBody) ILReader reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { + ValidateNoReferenceToReference(locals, methodBody, reader.Offset); int curBasicBlock = blockIterator.MoveNext(reader.Offset); if (knownStacks.ContainsKey(reader.Offset)) @@ -377,7 +472,6 @@ public void Scan(MethodIL methodBody) break; case ILOpcode.arglist: - case ILOpcode.ldftn: case ILOpcode.sizeof_: case ILOpcode.ldc_i8: case ILOpcode.ldc_r4: @@ -386,6 +480,17 @@ public void Scan(MethodIL methodBody) reader.Skip(opcode); break; + case ILOpcode.ldftn: + { + if (methodBody.GetObject(reader.ReadILToken()) is MethodDesc methodOperand) + { + TrackNestedFunctionReference(methodOperand, ref interproceduralState); + } + + PushUnknown(currentStack); + } + break; + case ILOpcode.ldarg: case ILOpcode.ldarg_0: case ILOpcode.ldarg_1: @@ -507,7 +612,7 @@ public void Scan(MethodIL methodBody) case ILOpcode.ldsfld: case ILOpcode.ldflda: case ILOpcode.ldsflda: - ScanLdfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack); + ScanLdfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack, ref interproceduralState); break; case ILOpcode.newarr: @@ -556,7 +661,7 @@ public void Scan(MethodIL methodBody) case ILOpcode.stfld: case ILOpcode.stsfld: - ScanStfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack); + ScanStfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack, ref interproceduralState); break; case ILOpcode.cpobj: @@ -573,7 +678,7 @@ public void Scan(MethodIL methodBody) case ILOpcode.stind_r8: case ILOpcode.stind_ref: case ILOpcode.stobj: - ScanIndirectStore(methodBody, offset, currentStack); + ScanIndirectStore(methodBody, offset, currentStack, locals, curBasicBlock); reader.Skip(opcode); break; @@ -643,7 +748,11 @@ public void Scan(MethodIL methodBody) case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: - HandleCall(methodBody, opcode, offset, (MethodDesc)methodBody.GetObject(reader.ReadILToken()), currentStack, curBasicBlock); + { + MethodDesc methodOperand = (MethodDesc)methodBody.GetObject(reader.ReadILToken()); + TrackNestedFunctionReference(methodOperand, ref interproceduralState); + HandleCall(methodBody, opcode, offset, methodOperand, currentStack, locals, ref interproceduralState, curBasicBlock); + } break; case ILOpcode.jmp: @@ -680,7 +789,9 @@ public void Scan(MethodIL methodBody) if (hasReturnValue) { StackSlot retValue = PopUnknown(currentStack, 1, methodBody, offset); - ReturnValue = MultiValueLattice.Meet(ReturnValue, retValue.Value); + // If the return value is a reference, treat it as the value itself for now + // We can handle ref return values better later + ReturnValue = MultiValueLattice.Meet(ReturnValue, DereferenceValue(retValue.Value, locals, ref interproceduralState)); } ClearStack(ref currentStack); break; @@ -760,12 +871,16 @@ private void ScanLdarg(ILOpcode opcode, int paramNum, Stack currentSt } else { + // This is semantically wrong if it returns true - we would representing a reference parameter as a reference to a parameter - but it should be fine for now isByRef = thisMethod.Signature[paramNum - (thisMethod.Signature.IsStatic ? 0 : 1)].IsByRefOrPointer(); } isByRef |= opcode == ILOpcode.ldarga || opcode == ILOpcode.ldarga_s; - StackSlot slot = new StackSlot(GetMethodParameterValue(thisMethod, paramNum), isByRef); + StackSlot slot = new StackSlot( + isByRef + ? new ParameterReferenceValue(thisMethod, paramNum) + : GetMethodParameterValue(thisMethod, paramNum)); currentStack.Push(slot); } @@ -792,18 +907,19 @@ Stack currentStack Stack currentStack, ValueBasicBlockPair?[] locals) { - bool isByRef = operation == ILOpcode.ldloca || operation == ILOpcode.ldloca_s - || methodBody.GetLocals()[index].Type.IsByRefOrPointer(); + bool isByRef = operation == ILOpcode.ldloca || operation == ILOpcode.ldloca_s; ValueBasicBlockPair? localValue = locals[index]; - if (!localValue.HasValue) + StackSlot newSlot; + if (isByRef) { - currentStack.Push(new StackSlot(UnknownValue.Instance, isByRef)); + newSlot = new StackSlot(new LocalVariableReferenceValue(index)); } + else if (localValue.HasValue) + newSlot = new StackSlot(localValue.Value.Value); else - { - currentStack.Push(new StackSlot(localValue.Value.Value, isByRef)); - } + newSlot = new StackSlot(UnknownValue.Instance); + currentStack.Push(newSlot); } private static void ScanLdtoken(MethodIL methodBody, object operand, Stack currentStack) @@ -870,7 +986,9 @@ private static void ScanLdtoken(MethodIL methodBody, object operand, Stack currentStack) + Stack currentStack, + ValueBasicBlockPair?[] locals, + int curBasicBlock) { StackSlot valueToStore = PopUnknown(currentStack, 1, methodBody, offset); StackSlot destination = PopUnknown(currentStack, 1, methodBody, offset); @@ -889,6 +1007,56 @@ private static void ScanLdtoken(MethodIL methodBody, object operand, Stack + /// Handles storing the source value in a target or MultiValue of ReferenceValues. + /// + /// A set of that a value is being stored into + /// The value to store + /// The method body that contains the operation causing the store + /// The instruction offset causing the store + /// Throws if is not a valid target for an indirect store. + protected void StoreInReference(MultiValue target, MultiValue source, MethodIL method, int offset, ValueBasicBlockPair?[] locals, int curBasicBlock) + { + foreach (var value in target) + { + switch (value) + { + case LocalVariableReferenceValue localReference: + StoreMethodLocalValue(locals, source, localReference.LocalIndex, curBasicBlock); + break; + case FieldReferenceValue fieldReference + when GetFieldValue(fieldReference.FieldDefinition).AsSingleValue() is FieldValue fieldValue: + HandleStoreField(method, offset, fieldValue, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue(parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodParameterValue parameterValue: + HandleStoreParameter(method, offset, parameterValue, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue(parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodThisParameterValue thisParameterValue: + HandleStoreMethodThisParameter(method, offset, thisParameterValue, source); + break; + case MethodReturnValue methodReturnValue: + // Ref returns don't have special ReferenceValue values, so assume if the target here is a MethodReturnValue then it must be a ref return value + HandleStoreMethodReturnValue(method, offset, methodReturnValue, source); + break; + case IValueWithStaticType valueWithStaticType: + if (valueWithStaticType.StaticType is not null && FlowAnnotations.IsTypeInterestingForDataflow(valueWithStaticType.StaticType)) + throw new InvalidOperationException(MessageContainer.CreateErrorMessage( + $"Unhandled StoreReference call. Unhandled attempt to store a value in {value} of type {value.GetType()}.", + (int)DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin(method, offset)).ToMSBuildString()); + // This should only happen for pointer derefs, which can't point to interesting types + break; + default: + // These cases should only be refs to array elements + // References to array elements are not yet tracked and since we don't allow annotations on arrays, they won't cause problems + break; + } + } + + } + protected abstract MultiValue GetFieldValue(FieldDesc field); private void ScanLdfld( @@ -896,16 +1064,28 @@ private static void ScanLdtoken(MethodIL methodBody, object operand, Stack currentStack - ) + Stack currentStack, + ref InterproceduralState interproceduralState) { if (opcode == ILOpcode.ldfld || opcode == ILOpcode.ldflda) PopUnknown(currentStack, 1, methodBody, offset); bool isByRef = opcode == ILOpcode.ldflda || opcode == ILOpcode.ldsflda; - StackSlot slot = new StackSlot(GetFieldValue(field), isByRef); - currentStack.Push(slot); + MultiValue value; + if (isByRef) + { + value = new FieldReferenceValue(field); + } + else if (CompilerGeneratedState.IsHoistedLocal(field)) + { + value = interproceduralState.GetHoistedLocal(new HoistedLocalKey(field)); + } + else + { + value = GetFieldValue(field); + } + currentStack.Push(new StackSlot(value)); } protected virtual void HandleStoreField(MethodIL method, int offset, FieldValue field, MultiValue valueToStore) @@ -916,17 +1096,32 @@ protected virtual void HandleStoreParameter(MethodIL method, int offset, MethodP { } + protected virtual void HandleStoreMethodThisParameter(MethodIL method, int offset, MethodThisParameterValue thisParameter, MultiValue sourceValue) + { + } + + protected virtual void HandleStoreMethodReturnValue(MethodIL method, int offset, MethodReturnValue thisParameter, MultiValue sourceValue) + { + } + private void ScanStfld( MethodIL methodBody, int offset, ILOpcode opcode, FieldDesc field, - Stack currentStack) + Stack currentStack, + ref InterproceduralState interproceduralState) { StackSlot valueToStoreSlot = PopUnknown(currentStack, 1, methodBody, offset); if (opcode == ILOpcode.stfld) PopUnknown(currentStack, 1, methodBody, offset); + if (CompilerGeneratedState.IsHoistedLocal(field)) + { + interproceduralState.SetHoistedLocal(new HoistedLocalKey(field), valueToStoreSlot.Value); + return; + } + foreach (var value in GetFieldValue(field)) { // GetFieldValue may return different node types, in which case they can't be stored to. @@ -968,27 +1163,90 @@ protected virtual void HandleStoreParameter(MethodIL method, int offset, MethodP return methodParams; } + internal MultiValue DereferenceValue(MultiValue maybeReferenceValue, ValueBasicBlockPair?[] locals, ref InterproceduralState interproceduralState) + { + MultiValue dereferencedValue = MultiValueLattice.Top; + foreach (var value in maybeReferenceValue) + { + switch (value) + { + case FieldReferenceValue fieldReferenceValue: + dereferencedValue = MultiValue.Meet( + dereferencedValue, + CompilerGeneratedState.IsHoistedLocal(fieldReferenceValue.FieldDefinition) + ? interproceduralState.GetHoistedLocal(new HoistedLocalKey(fieldReferenceValue.FieldDefinition)) + : GetFieldValue(fieldReferenceValue.FieldDefinition)); + break; + case ParameterReferenceValue parameterReferenceValue: + dereferencedValue = MultiValue.Meet( + dereferencedValue, + GetMethodParameterValue(parameterReferenceValue.MethodDefinition, parameterReferenceValue.ParameterIndex)); + break; + case LocalVariableReferenceValue localVariableReferenceValue: + var valueBasicBlockPair = locals[localVariableReferenceValue.LocalIndex]; + if (valueBasicBlockPair.HasValue) + dereferencedValue = MultiValue.Meet(dereferencedValue, valueBasicBlockPair.Value.Value); + else + dereferencedValue = MultiValue.Meet(dereferencedValue, UnknownValue.Instance); + break; + case ReferenceValue referenceValue: + throw new NotImplementedException($"Unhandled dereference of ReferenceValue of type {referenceValue.GetType().FullName}"); + default: + dereferencedValue = MultiValue.Meet(dereferencedValue, value); + break; + } + } + return dereferencedValue; + } + + /// + /// Assigns a MethodParameterValue to the location of each parameter passed by reference. (i.e. assigns the value to x when passing `ref x` as a parameter) + /// + protected void AssignRefAndOutParameters( + MethodIL callingMethodBody, + MethodDesc calledMethod, + ValueNodeList methodArguments, + int offset, + ValueBasicBlockPair?[] locals, + int curBasicBlock) + { + int parameterOffset = !calledMethod.Signature.IsStatic ? 1 : 0; + int parameterIndex = 0; + for (int ilArgumentIndex = parameterOffset; ilArgumentIndex < methodArguments.Count; ilArgumentIndex++, parameterIndex++) + { + if (calledMethod.ParameterReferenceKind(ilArgumentIndex) is not (ReferenceKind.Ref or ReferenceKind.Out)) + continue; + SingleValue newByRefValue = _annotations.GetMethodParameterValue(calledMethod, parameterIndex); + StoreInReference(methodArguments[ilArgumentIndex], newByRefValue, callingMethodBody, offset, locals, curBasicBlock); + } + } + private void HandleCall( MethodIL callingMethodBody, ILOpcode opcode, int offset, MethodDesc calledMethod, Stack currentStack, + ValueBasicBlockPair?[] locals, + ref InterproceduralState interproceduralState, int curBasicBlock) { bool isNewObj = opcode == ILOpcode.newobj; SingleValue? newObjValue; - ValueNodeList methodParams = PopCallArguments(currentStack, calledMethod, callingMethodBody, isNewObj, - offset, out newObjValue); + ValueNodeList methodArguments = PopCallArguments(currentStack, calledMethod, callingMethodBody, isNewObj, + offset, out newObjValue); + var dereferencedMethodParams = new List(); + foreach (var argument in methodArguments) + dereferencedMethodParams.Add(DereferenceValue(argument, locals, ref interproceduralState)); MultiValue methodReturnValue; bool handledFunction = HandleCall( callingMethodBody, calledMethod, opcode, offset, - methodParams, + new ValueNodeList(dereferencedMethodParams), out methodReturnValue); // Handle the return value or newobj result @@ -1011,9 +1269,11 @@ protected virtual void HandleStoreParameter(MethodIL method, int offset, MethodP } if (isNewObj || !calledMethod.Signature.ReturnType.IsVoid) - currentStack.Push(new StackSlot(methodReturnValue, calledMethod.Signature.ReturnType.IsByRefOrPointer())); + currentStack.Push(new StackSlot(methodReturnValue)); + + AssignRefAndOutParameters(callingMethodBody, calledMethod, methodArguments, offset, locals, curBasicBlock); - foreach (var param in methodParams) + foreach (var param in methodArguments) { foreach (var v in param) { @@ -1088,6 +1348,7 @@ private static void MarkArrayValuesAsUnknown(ArrayValue arrValue, int curBasicBl PushUnknown(currentStack); return; } + // We don't yet handle arrays of references or pointers bool isByRef = opcode == ILOpcode.ldelema; int? index = indexToLoadFrom.Value.AsConstInt(); @@ -1100,15 +1361,16 @@ private static void MarkArrayValuesAsUnknown(ArrayValue arrValue, int curBasicBl } return; } - - if (arr.IndexValues.TryGetValue(index.Value, out ValueBasicBlockPair arrayIndexValue)) + // Don't try to track refs to array elements. Set it as unknown, then push unknown to the stack + else if (isByRef) { - currentStack.Push(new StackSlot(arrayIndexValue.Value, isByRef)); + arr.IndexValues[index.Value] = new ValueBasicBlockPair(UnknownValue.Instance, curBasicBlock); + PushUnknown(currentStack); } + else if (arr.IndexValues.TryGetValue(index.Value, out ValueBasicBlockPair arrayIndexValue)) + currentStack.Push(new StackSlot(arrayIndexValue.Value)); else - { PushUnknown(currentStack); - } } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs index ed866e82d32d1..4795258de4a15 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs @@ -1,6 +1,7 @@ // 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.Immutable; using ILCompiler; using ILCompiler.Dataflow; @@ -11,7 +12,7 @@ namespace ILLink.Shared.TypeSystemProxy { - readonly partial struct MethodProxy + readonly partial struct MethodProxy : IEquatable { public MethodProxy(MethodDesc method) => Method = method; @@ -61,5 +62,13 @@ namespace ILLink.Shared.TypeSystemProxy internal partial bool ReturnsVoid() => Method.Signature.ReturnType.IsVoid; public override string ToString() => Method.ToString(); + + public ReferenceKind ParameterReferenceKind(int index) => Method.ParameterReferenceKind(Method.Signature.IsStatic ? index : index + 1); + + public bool Equals(MethodProxy other) => Method.Equals(other.Method); + + public override bool Equals(object? obj) => obj is MethodProxy other && Equals(other); + + public override int GetHashCode() => Method.GetHashCode(); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs new file mode 100644 index 0000000000000..2ffd7e5ac3bc4 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +using Internal.TypeSystem; + +#nullable enable + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record ParameterReferenceValue(MethodDesc MethodDefinition, int ParameterIndex) + : ReferenceValue + { + public override SingleValue DeepCopy() + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md index 6388ad638a5b6..fb9471b534ada 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md @@ -4,4 +4,11 @@ The purpose of this logic is to analyze dynamic behavior of the compiled code to Let's try to keep this in sync. The ReferenceSource contains sources at the time of porting. -It should be updated whenever we take fixes from IL linker. \ No newline at end of file +It should be updated whenever we take fixes from IL linker. + +Standard updates when taking files from the linker: +* Note: These rules apply only to the DataFlow directory, the files which we share verbatime should remain exactly as-is in linker for now +* Use the runtime's version of the license header in the file (starts with `Licensed to the .NET Foundation`) +* Use the formatting from runtime repo - in VS this can be done by reformatting the whole document: Edit -> Advanced -> Format whole document. +* Add `#nullable enable` since nullable is not globaly enabled in ILC yet + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs index 71934bc28ca87..570714f6c8d23 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs @@ -24,9 +24,9 @@ public static MultiValue Create (MultiValue size, TypeReference elementType) return result; } - public static MultiValue Create (int size, TypeReference elementType) + public static ArrayValue Create (int size, TypeReference elementType) { - return new MultiValue (new ArrayValue (new ConstIntValue (size), elementType)); + return new ArrayValue (new ConstIntValue (size), elementType); } /// diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs new file mode 100644 index 0000000000000..3ed11d392487a --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using ILLink.Shared.TrimAnalysis; +using Mono.Cecil; +using Mono.Linker.Steps; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly struct AttributeDataFlow + { + readonly LinkContext _context; + readonly MarkStep _markStep; + readonly MessageOrigin _origin; + + public AttributeDataFlow (LinkContext context, MarkStep markStep, in MessageOrigin origin) + { + _context = context; + _markStep = markStep; + _origin = origin; + } + + public void ProcessAttributeDataflow (MethodDefinition method, IList arguments) + { + for (int i = 0; i < method.Parameters.Count; i++) { + var parameterValue = _context.Annotations.FlowAnnotations.GetMethodParameterValue (method, i); + if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { + MultiValue value = GetValueForCustomAttributeArgument (arguments[i]); + var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); + RequireDynamicallyAccessedMembers (diagnosticContext, value, parameterValue); + } + } + } + + public void ProcessAttributeDataflow (FieldDefinition field, CustomAttributeArgument value) + { + MultiValue valueNode = GetValueForCustomAttributeArgument (value); + var fieldValueCandidate = _context.Annotations.FlowAnnotations.GetFieldValue (field); + if (fieldValueCandidate is not ValueWithDynamicallyAccessedMembers fieldValue) + return; + + var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); + RequireDynamicallyAccessedMembers (diagnosticContext, valueNode, fieldValue); + } + + MultiValue GetValueForCustomAttributeArgument (CustomAttributeArgument argument) + { + if (argument.Type.Name == "Type") { + if (argument.Value is null) + return NullValue.Instance; + + TypeDefinition? referencedType = ((TypeReference) argument.Value).ResolveToTypeDefinition (_context); + return referencedType == null + ? UnknownValue.Instance + : new SystemTypeValue (referencedType); + } + + if (argument.Type.MetadataType == MetadataType.String) + return argument.Value is null ? NullValue.Instance : new KnownStringValue ((string) argument.Value); + + // We shouldn't have gotten a non-null annotation for this from GetParameterAnnotation + throw new InvalidOperationException (); + } + + void RequireDynamicallyAccessedMembers (in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue) + { + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (reflectionMarker, diagnosticContext); + requireDynamicallyAccessedMembersAction.Invoke (value, targetValue); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs new file mode 100644 index 0000000000000..1cfbc8770e62f --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker.Dataflow +{ + sealed class CompilerGeneratedCallGraph + { + readonly Dictionary> _callGraph; + + public CompilerGeneratedCallGraph () => _callGraph = new Dictionary> (); + + void TrackCallInternal (IMemberDefinition fromMember, IMemberDefinition toMember) + { + if (!_callGraph.TryGetValue (fromMember, out HashSet? toMembers)) { + toMembers = new HashSet (); + _callGraph.Add (fromMember, toMembers); + } + toMembers.Add (toMember); + } + + public void TrackCall (MethodDefinition fromMethod, MethodDefinition toMethod) + { + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); + TrackCallInternal (fromMethod, toMethod); + } + + public void TrackCall (MethodDefinition fromMethod, TypeDefinition toType) + { + Debug.Assert (CompilerGeneratedNames.IsStateMachineType (toType.Name)); + TrackCallInternal (fromMethod, toType); + } + + public void TrackCall (TypeDefinition fromType, MethodDefinition toMethod) + { + Debug.Assert (CompilerGeneratedNames.IsStateMachineType (fromType.Name)); + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); + TrackCallInternal (fromType, toMethod); + } + + public IEnumerable GetReachableMembers (MethodDefinition start) + { + Queue queue = new (); + HashSet visited = new (); + visited.Add (start); + queue.Enqueue (start); + while (queue.TryDequeue (out IMemberDefinition? method)) { + if (!_callGraph.TryGetValue (method, out HashSet? callees)) + continue; + + foreach (var callee in callees) { + if (visited.Add (callee)) { + queue.Enqueue (callee); + yield return callee; + } + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs new file mode 100644 index 0000000000000..46e6c9764782a --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Mono.Linker.Dataflow +{ + sealed class CompilerGeneratedNames + { + internal static bool IsGeneratedMemberName (string memberName) + { + return memberName.Length > 0 && memberName[0] == '<'; + } + + internal static bool IsLambdaDisplayClass (string className) + { + if (!IsGeneratedMemberName (className)) + return false; + + // This is true for static lambdas (which are emitted into a class like <>c) + // and for instance lambdas (which are emitted into a class like <>c__DisplayClass1_0) + return className.StartsWith ("<>c"); + } + + internal static bool IsStateMachineType (string typeName) + { + if (!IsGeneratedMemberName (typeName)) + return false; + + // State machines are generated into types with names like d__0 + // Or if its nested in a local function the name will look like <g__Local>d and so on + int i = typeName.LastIndexOf ('>'); + if (i == -1) + return false; + + return typeName.Length > i + 1 && typeName[i + 1] == 'd'; + } + + internal static bool IsGeneratedType (string name) => IsStateMachineType (name) || IsLambdaDisplayClass (name); + + internal static bool IsLambdaOrLocalFunction (string methodName) => IsLambdaMethod (methodName) || IsLocalFunction (methodName); + + // Lambda methods have generated names like "b__0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration. + internal static bool IsLambdaMethod (string methodName) + { + if (!IsGeneratedMemberName (methodName)) + return false; + + int i = methodName.IndexOf ('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and lambda ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'b'; + } + + // Local functions have generated names like "g__LocalFunction|0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration, and "LocalFunction" is the name of + // the local function. + internal static bool IsLocalFunction (string methodName) + { + if (!IsGeneratedMemberName (methodName)) + return false; + + int i = methodName.IndexOf ('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and local function ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'g'; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs new file mode 100644 index 0000000000000..c99968f1d0cc3 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs @@ -0,0 +1,427 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using ILLink.Shared; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Mono.Linker.Dataflow +{ + // Currently this is implemented using heuristics + public class CompilerGeneratedState + { + readonly LinkContext _context; + readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; + readonly Dictionary _generatedTypeToTypeArgumentInfo; + readonly record struct TypeArgumentInfo ( + /// The method which calls the ctor for the given type + MethodDefinition CreatingMethod, + /// Attributes for the type, pulled from the creators type arguments + IReadOnlyList? OriginalAttributes); + + readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; + + // For each type that has had its cache populated, 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. + readonly Dictionary>?> _cachedTypeToCompilerGeneratedMembers; + + public CompilerGeneratedState (LinkContext context) + { + _context = context; + _compilerGeneratedTypeToUserCodeMethod = new Dictionary (); + _generatedTypeToTypeArgumentInfo = new Dictionary (); + _compilerGeneratedMethodToUserCodeMethod = new Dictionary (); + _cachedTypeToCompilerGeneratedMembers = new Dictionary>?> (); + } + + static IEnumerable 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; + } + } + + public static bool IsHoistedLocal (FieldDefinition field) + { + // Treat all fields on compiler-generated types as hoisted locals. + // This avoids depending on the name mangling scheme for hoisted locals. + var declaringTypeName = field.DeclaringType.Name; + return CompilerGeneratedNames.IsLambdaDisplayClass (declaringTypeName) || CompilerGeneratedNames.IsStateMachineType (declaringTypeName); + } + + // "Nested function" refers to lambdas and local functions. + public static bool IsNestedFunctionOrStateMachineMember (IMemberDefinition member) + { + if (member is MethodDefinition method && CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)) + return true; + + if (member.DeclaringType is not TypeDefinition declaringType) + return false; + + return CompilerGeneratedNames.IsStateMachineType (declaringType.Name); + } + + public static bool TryGetStateMachineType (MethodDefinition method, [NotNullWhen (true)] out TypeDefinition? stateMachineType) + { + stateMachineType = null; + // Discover state machine methods. + if (!method.HasCustomAttributes) + return false; + + foreach (var attribute in method.CustomAttributes) { + if (attribute.AttributeType.Namespace != "System.Runtime.CompilerServices") + continue; + + switch (attribute.AttributeType.Name) { + case "AsyncIteratorStateMachineAttribute": + case "AsyncStateMachineAttribute": + case "IteratorStateMachineAttribute": + stateMachineType = GetFirstConstructorArgumentAsType (attribute); + return stateMachineType != null; + } + } + return false; + } + + /// + /// 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. + /// + TypeDefinition? GetCompilerGeneratedStateForType (TypeDefinition 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 (type != null && CompilerGeneratedNames.IsGeneratedMemberName (type.Name)) + type = type.DeclaringType; + + if (type is null) + return null; + + // Avoid repeat scans of the same type + if (_cachedTypeToCompilerGeneratedMembers.ContainsKey (type)) + return type; + + var callGraph = new CompilerGeneratedCallGraph (); + var userDefinedMethods = new HashSet (); + + 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 = 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). + 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 (lambdaOrLocalFunction.IsConstructor && + lambdaOrLocalFunction.DeclaringType is var generatedType && + // Don't consider calls in the same type, like inside a static constructor + method.DeclaringType != generatedType && + CompilerGeneratedNames.IsLambdaDisplayClass (generatedType.Name)) { + // fill in null for now, attribute providers will be filled in later + if (!_generatedTypeToTypeArgumentInfo.TryAdd (generatedType, new TypeArgumentInfo (method, null))) { + var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; + _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), generatedType.GetDisplayName ()); + } + continue; + } + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (lambdaOrLocalFunction.Name)) + continue; + + if (isStateMachineMember) { + callGraph.TrackCall (method.DeclaringType, lambdaOrLocalFunction); + } else { + callGraph.TrackCall (method, lambdaOrLocalFunction); + } + } + } + + if (TryGetStateMachineType (method, out TypeDefinition? stateMachineType)) { + 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 ()); + } + // 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[stateMachineType] = new TypeArgumentInfo (method, null); + } + } + + // 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. + + Dictionary>? compilerGeneratedCallees = null; + foreach (var userDefinedMethod in userDefinedMethods) { + var callees = callGraph.GetReachableMembers (userDefinedMethod); + if (!callees.Any ()) + continue; + + compilerGeneratedCallees ??= new Dictionary> (); + compilerGeneratedCallees.Add (userDefinedMethod, new List (callees)); + + foreach (var compilerGeneratedMember in callees) { + 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 (); + } + } + } + + // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute + // providers + foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) { + if (HasGenericParameters (generatedType)) + MapGeneratedTypeTypeParameters (generatedType); + } + + _cachedTypeToCompilerGeneratedMembers.Add (type, compilerGeneratedCallees); + return type; + + /// + /// Check if the type itself is generic. The only difference is that + /// if the type is a nested type, the generic parameters from its + /// parent type don't count. + /// + static bool HasGenericParameters (TypeDefinition typeDef) + { + if (!typeDef.IsNested) + return typeDef.HasGenericParameters; + + return typeDef.GenericParameters.Count > typeDef.DeclaringType.GenericParameters.Count; + } + + void MapGeneratedTypeTypeParameters (TypeDefinition generatedType) + { + Debug.Assert (CompilerGeneratedNames.IsGeneratedType (generatedType.Name)); + + if (!_generatedTypeToTypeArgumentInfo.TryGetValue (generatedType, out var typeInfo)) { + // 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; + if (method.Body is { } body) { + var typeArgs = new ICustomAttributeProvider[generatedType.GenericParameters.Count]; + var typeRef = ScanForInit (generatedType, body); + if (typeRef is null) { + return; + } + + for (int i = 0; i < typeRef.GenericArguments.Count; i++) { + var typeArg = typeRef.GenericArguments[i]; + // Start with the existing parameters, in case we can't find the mapped one + ICustomAttributeProvider userAttrs = generatedType.GenericParameters[i]; + // 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 GenericParameter { Owner: { } owner } param) { + if (owner is MethodReference) { + userAttrs = param; + } else { + // Must be a type ref + var owningRef = (TypeReference) owner; + if (!CompilerGeneratedNames.IsGeneratedType (owningRef.Name)) { + userAttrs = param; + } else if (_context.TryResolve ((TypeReference) param.Owner) is { } owningType) { + MapGeneratedTypeTypeParameters (owningType); + if (_generatedTypeToTypeArgumentInfo.TryGetValue (owningType, out var owningInfo) && + owningInfo.OriginalAttributes is { } owningAttrs) { + userAttrs = owningAttrs[param.Position]; + } + } + } + } + + typeArgs[i] = userAttrs; + } + + _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; + } + } + + GenericInstanceType? ScanForInit (TypeDefinition stateMachineType, MethodBody body) + { + foreach (var instr in body.Instructions) { + switch (instr.OpCode.Code) { + case Code.Initobj: + case Code.Newobj: + if (instr.Operand is MethodReference { DeclaringType: GenericInstanceType typeRef } + && stateMachineType == _context.TryResolve (typeRef)) { + return typeRef; + } + break; + } + } + return null; + } + } + + static TypeDefinition? GetFirstConstructorArgumentAsType (CustomAttribute attribute) + { + if (!attribute.HasConstructorArguments) + return null; + + return attribute.ConstructorArguments[0].Value as TypeDefinition; + } + + public bool TryGetCompilerGeneratedCalleesForUserMethod (MethodDefinition method, [NotNullWhen (true)] out List? callees) + { + callees = null; + if (IsNestedFunctionOrStateMachineMember (method)) + return false; + + var typeToCache = GetCompilerGeneratedStateForType (method.DeclaringType); + if (typeToCache is null) + return false; + + return _cachedTypeToCompilerGeneratedMembers[typeToCache]?.TryGetValue (method, out callees) == true; + } + + /// + /// Gets the attributes on the "original" method of a generated type, i.e. the + /// attributes on the corresponding type parameters from the owning method. + /// + public IReadOnlyList? GetGeneratedTypeAttributes (TypeDefinition generatedType) + { + Debug.Assert (CompilerGeneratedNames.IsGeneratedType (generatedType.Name)); + + var typeToCache = GetCompilerGeneratedStateForType (generatedType); + if (typeToCache is null) + return null; + + if (_generatedTypeToTypeArgumentInfo.TryGetValue (generatedType, out var typeInfo)) { + return typeInfo.OriginalAttributes; + } + return null; + } + + // 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 (!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 typeToCache = GetCompilerGeneratedStateForType (sourceType); + if (typeToCache is null) + return false; + + if (compilerGeneratedMethod != null) { + if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue (compilerGeneratedMethod, out owningMethod)) + return true; + } + + if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (sourceType, out owningMethod)) + return true; + + return false; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs index df43246bbb815..67fb9ec08d234 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs @@ -5,7 +5,7 @@ namespace ILLink.Shared.TrimAnalysis { - readonly partial struct DiagnosticContext + public readonly partial struct DiagnosticContext { public readonly MessageOrigin Origin; public readonly bool DiagnosticsEnabled; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs index 56e9496e84e8f..caa1be80e7260 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs @@ -10,7 +10,7 @@ namespace Mono.Linker.Dataflow { - class DynamicallyAccessedMembersTypeHierarchy + sealed class DynamicallyAccessedMembersTypeHierarchy { readonly LinkContext _context; readonly MarkStep _markStep; @@ -108,7 +108,7 @@ public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep ma // One of the base/interface types is already marked as having the annotation applied // so we need to apply the annotation to this type as well var origin = new MessageOrigin (type); - var reflectionMarker = new ReflectionMarker (_context, _markStep); + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); // Report warnings on access to annotated members, with the annotated type as the origin. ApplyDynamicallyAccessedMembersToType (reflectionMarker, origin, type, annotation); } @@ -116,9 +116,7 @@ public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep ma return (annotation, apply); } - public DynamicallyAccessedMemberTypes ApplyDynamicallyAccessedMembersToTypeHierarchy ( - ReflectionMarker reflectionMarker, - TypeDefinition type) + public DynamicallyAccessedMemberTypes ApplyDynamicallyAccessedMembersToTypeHierarchy (TypeDefinition type) { (var annotation, var applied) = ProcessMarkedTypeForDynamicallyAccessedMembersHierarchy (type); @@ -129,6 +127,7 @@ public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep ma // Apply the effective annotation for the type var origin = new MessageOrigin (type); + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); // Report warnings on access to annotated members, with the annotated type as the origin. ApplyDynamicallyAccessedMembersToType (reflectionMarker, origin, type, annotation); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs new file mode 100644 index 0000000000000..de0f2354189c9 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; +using Mono.Cecil; + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record FieldReferenceValue (FieldDefinition FieldDefinition) : ReferenceValue + { + public override SingleValue DeepCopy () => this; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs index aea413b2c7ce4..610cd1f59d5a9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using ILLink.Shared.DataFlow; using ILLink.Shared.TypeSystemProxy; using Mono.Cecil; using Mono.Cecil.Cil; @@ -13,7 +15,7 @@ namespace ILLink.Shared.TrimAnalysis { - partial class FlowAnnotations + sealed partial class FlowAnnotations { readonly LinkContext _context; readonly Dictionary _annotations = new Dictionary (); @@ -145,6 +147,17 @@ public bool ShouldWarnWhenAccessedForReflection (MethodDefinition method) public bool ShouldWarnWhenAccessedForReflection (FieldDefinition field) => GetAnnotations (field.DeclaringType).TryGetAnnotation (field, out _); + public bool IsTypeInterestingForDataflow (TypeReference typeReference) + { + if (typeReference.MetadataType == MetadataType.String) + return true; + + TypeDefinition? type = _context.TryResolve (typeReference); + return type != null && ( + _hierarchyInfo.IsSystemType (type) || + _hierarchyInfo.IsSystemReflectionIReflect (type)); + } + TypeAnnotations GetAnnotations (TypeDefinition type) { if (!_annotations.TryGetValue (type, out TypeAnnotations value)) { @@ -372,9 +385,10 @@ TypeAnnotations BuildTypeAnnotations (TypeDefinition type) DynamicallyAccessedMemberTypes[]? typeGenericParameterAnnotations = null; if (type.HasGenericParameters) { + var attrs = GetGeneratedTypeAttributes (type); for (int genericParameterIndex = 0; genericParameterIndex < type.GenericParameters.Count; genericParameterIndex++) { - var genericParameter = type.GenericParameters[genericParameterIndex]; - var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute (type, providerIfNotMember: genericParameter); + var provider = attrs?[genericParameterIndex] ?? type.GenericParameters[genericParameterIndex]; + var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute (type, providerIfNotMember: provider); if (annotation != DynamicallyAccessedMemberTypes.None) { if (typeGenericParameterAnnotations == null) typeGenericParameterAnnotations = new DynamicallyAccessedMemberTypes[type.GenericParameters.Count]; @@ -386,6 +400,16 @@ TypeAnnotations BuildTypeAnnotations (TypeDefinition type) return new TypeAnnotations (type, typeAnnotation, annotatedMethods.ToArray (), annotatedFields.ToArray (), typeGenericParameterAnnotations); } + private IReadOnlyList? GetGeneratedTypeAttributes (TypeDefinition typeDef) + { + if (!CompilerGeneratedNames.IsGeneratedType (typeDef.Name)) { + return null; + } + var attrs = _context.CompilerGeneratedState.GetGeneratedTypeAttributes (typeDef); + Debug.Assert (attrs is null || attrs.Count == typeDef.GenericParameters.Count); + return attrs; + } + bool ScanMethodBodyForFieldAccess (MethodBody body, bool write, out FieldDefinition? found) { // Tries to find the backing field for a property getter/setter. @@ -440,17 +464,6 @@ bool ScanMethodBodyForFieldAccess (MethodBody body, bool write, out FieldDefinit return true; } - bool IsTypeInterestingForDataflow (TypeReference typeReference) - { - if (typeReference.MetadataType == MetadataType.String) - return true; - - TypeDefinition? type = _context.TryResolve (typeReference); - return type != null && ( - _hierarchyInfo.IsSystemType (type) || - _hierarchyInfo.IsSystemReflectionIReflect (type)); - } - internal void ValidateMethodAnnotationsAreSame (MethodDefinition method, MethodDefinition baseMethod) { GetAnnotations (method.DeclaringType).TryGetAnnotation (method, out var methodAnnotations); @@ -694,5 +707,41 @@ public FieldAnnotation (FieldDefinition field, DynamicallyAccessedMemberTypes an internal partial MethodParameterValue GetMethodParameterValue (MethodProxy method, int parameterIndex) => GetMethodParameterValue (method, parameterIndex, GetParameterAnnotation (method.Method, parameterIndex + (method.IsStatic () ? 0 : 1))); + + // Linker-specific dataflow value creation. Eventually more of these should be shared. + internal SingleValue GetFieldValue (FieldDefinition field) + => field.Name switch { + "EmptyTypes" when field.DeclaringType.IsTypeOf (WellKnownType.System_Type) => ArrayValue.Create (0, field.DeclaringType), + "Empty" when field.DeclaringType.IsTypeOf (WellKnownType.System_String) => new KnownStringValue (string.Empty), + _ => new FieldValue (field.FieldType.ResolveToTypeDefinition (_context), field, GetFieldAnnotation (field)) + }; + + internal SingleValue GetTypeValueFromGenericArgument (TypeReference genericArgument) + { + if (genericArgument is GenericParameter inputGenericParameter) { + // Technically this should be a new value node type as it's not a System.Type instance representation, but just the generic parameter + // That said we only use it to perform the dynamically accessed members checks and for that purpose treating it as System.Type is perfectly valid. + return GetGenericParameterValue (inputGenericParameter); + } else if (genericArgument.ResolveToTypeDefinition (_context) is TypeDefinition genericArgumentType) { + if (genericArgumentType.IsTypeOf (WellKnownType.System_Nullable_T)) { + var innerGenericArgument = (genericArgument as IGenericInstance)?.GenericArguments.FirstOrDefault (); + switch (innerGenericArgument) { + case GenericParameter gp: + return new NullableValueWithDynamicallyAccessedMembers (genericArgumentType, + new GenericParameterValue (gp, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (gp))); + + case TypeReference underlyingType: + if (underlyingType.ResolveToTypeDefinition (_context) is TypeDefinition underlyingTypeDefinition) + return new NullableSystemTypeValue (genericArgumentType, new SystemTypeValue (underlyingTypeDefinition)); + else + return UnknownValue.Instance; + } + } + // All values except for Nullable, including Nullable<> (with no type arguments) + return new SystemTypeValue (genericArgumentType); + } else { + return UnknownValue.Instance; + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs new file mode 100644 index 0000000000000..a4092c4a9f805 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using ILLink.Shared.TrimAnalysis; +using Mono.Cecil; +using Mono.Linker.Steps; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly struct GenericArgumentDataFlow + { + readonly LinkContext _context; + readonly MarkStep _markStep; + readonly MessageOrigin _origin; + + public GenericArgumentDataFlow (LinkContext context, MarkStep markStep, in MessageOrigin origin) + { + _context = context; + _markStep = markStep; + _origin = origin; + } + + public void ProcessGenericArgumentDataFlow (GenericParameter genericParameter, TypeReference genericArgument) + { + var genericParameterValue = _context.Annotations.FlowAnnotations.GetGenericParameterValue (genericParameter); + Debug.Assert (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); + + MultiValue genericArgumentValue = _context.Annotations.FlowAnnotations.GetTypeValueFromGenericArgument (genericArgument); + + var diagnosticContext = new DiagnosticContext (_origin, !_context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (_origin.Provider), _context); + RequireDynamicallyAccessedMembers (diagnosticContext, genericArgumentValue, genericParameterValue); + } + + void RequireDynamicallyAccessedMembers (in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue) + { + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (reflectionMarker, diagnosticContext); + requireDynamicallyAccessedMembersAction.Invoke (value, targetValue); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs index 3d402cef977ec..69641ecc65333 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs @@ -29,7 +29,7 @@ partial struct HandleCallAction _diagnosticContext = diagnosticContext; _callingMethodDefinition = callingMethodDefinition; _annotations = context.Annotations.FlowAnnotations; - _requireDynamicallyAccessedMembersAction = new (context, reflectionMarker, diagnosticContext); + _requireDynamicallyAccessedMembersAction = new (reflectionMarker, diagnosticContext); } private partial bool MethodIsTypeConstructor (MethodProxy method) @@ -68,7 +68,7 @@ partial struct HandleCallAction return false; } - private partial bool TryResolveTypeNameForCreateInstance (in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) + private partial bool TryResolveTypeNameForCreateInstanceAndMark (in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) { var resolvedAssembly = _context.TryResolve (assemblyName); if (resolvedAssembly == null) { @@ -79,9 +79,8 @@ partial struct HandleCallAction return false; } - if (!_context.TypeNameResolver.TryResolveTypeName (resolvedAssembly, typeName, out TypeReference? typeRef) - || _context.TryResolve (typeRef) is not TypeDefinition resolvedTypeDefinition - || typeRef is ArrayType) { + if (!_reflectionMarker.TryResolveTypeNameAndMark (resolvedAssembly, typeName, _diagnosticContext, out TypeDefinition? resolvedTypeDefinition) + || resolvedTypeDefinition.IsTypeOf (WellKnownType.System_Array)) { // It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case // Note that we did find the assembly, so it's not a linker config problem, it's either intentional, or wrong versions of assemblies // but linker can't know that. In case a user tries to create an array using System.Activator we should simply ignore it, the user diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs new file mode 100644 index 0000000000000..b721df288b408 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker.Dataflow +{ + // This represents a field which has been generated by the compiler as the + // storage location for a hoisted local (a local variable which is lifted to a + // field on a state machine type, or to a field on a closure accessed by lambdas + // or local functions). + public readonly struct HoistedLocalKey : IEquatable + { + readonly FieldDefinition Field; + + public HoistedLocalKey (FieldDefinition field) + { + Debug.Assert (CompilerGeneratedState.IsHoistedLocal (field)); + Field = field; + } + + public bool Equals (HoistedLocalKey other) => Field.Equals (other.Field); + + public override bool Equals (object? obj) => obj is HoistedLocalKey other && Equals (other); + + public override int GetHashCode () => Field.GetHashCode (); + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs new file mode 100644 index 0000000000000..09bc3be226300 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs @@ -0,0 +1,97 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using ILLink.Shared.DataFlow; +using Mono.Cecil; +using Mono.Cecil.Cil; +using HoistedLocalState = ILLink.Shared.DataFlow.DefaultValueDictionary< + Mono.Linker.Dataflow.HoistedLocalKey, + ILLink.Shared.DataFlow.ValueSet>; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + // Wrapper that implements IEquatable for MethodBody. + readonly record struct MethodBodyValue (MethodBody MethodBody); + + // Tracks the set of methods which get analyzer together during interprocedural analysis, + // and the possible states of hoisted locals in state machine methods and lambdas/local functions. + struct InterproceduralState : IEquatable + { + public ValueSet MethodBodies; + public HoistedLocalState HoistedLocals; + readonly InterproceduralStateLattice lattice; + + public InterproceduralState (ValueSet methodBodies, HoistedLocalState hoistedLocals, InterproceduralStateLattice lattice) + => (MethodBodies, HoistedLocals, this.lattice) = (methodBodies, hoistedLocals, lattice); + + public bool Equals (InterproceduralState other) + => MethodBodies.Equals (other.MethodBodies) && HoistedLocals.Equals (other.HoistedLocals); + + public InterproceduralState Clone () + => new (MethodBodies.Clone (), HoistedLocals.Clone (), lattice); + + public void TrackMethod (MethodDefinition method) + { + if (method.Body is not MethodBody methodBody) + return; + + TrackMethod (methodBody); + } + + public void TrackMethod (MethodBody methodBody) + { + // Work around the fact that ValueSet is readonly + var methodsList = new List (MethodBodies); + methodsList.Add (new MethodBodyValue (methodBody)); + + // For state machine methods, also scan the state machine members. + // Simplification: assume that all generated methods of the state machine type are + // reached at the point where the state machine method is reached. + if (CompilerGeneratedState.TryGetStateMachineType (methodBody.Method, out TypeDefinition? stateMachineType)) { + foreach (var stateMachineMethod in stateMachineType.Methods) { + Debug.Assert (!CompilerGeneratedNames.IsLambdaOrLocalFunction (stateMachineMethod.Name)); + if (stateMachineMethod.Body is MethodBody stateMachineMethodBody) + methodsList.Add (new MethodBodyValue (stateMachineMethodBody)); + } + } + + MethodBodies = new ValueSet (methodsList); + } + + public void SetHoistedLocal (HoistedLocalKey key, MultiValue value) + { + // For hoisted locals, we track the entire set of assigned values seen + // in the closure of a method, so setting a hoisted local value meets + // it with any existing value. + HoistedLocals.Set (key, + lattice.HoistedLocalsLattice.ValueLattice.Meet ( + HoistedLocals.Get (key), value)); + } + + public MultiValue GetHoistedLocal (HoistedLocalKey key) + => HoistedLocals.Get (key); + } + + struct InterproceduralStateLattice : ILattice + { + public readonly ValueSetLattice MethodBodyLattice; + public readonly DictionaryLattice> HoistedLocalsLattice; + + public InterproceduralStateLattice ( + ValueSetLattice methodBodyLattice, + DictionaryLattice> hoistedLocalsLattice) + => (MethodBodyLattice, HoistedLocalsLattice) = (methodBodyLattice, hoistedLocalsLattice); + + public InterproceduralState Top => new InterproceduralState (MethodBodyLattice.Top, HoistedLocalsLattice.Top, this); + + public InterproceduralState Meet (InterproceduralState left, InterproceduralState right) + => new ( + MethodBodyLattice.Meet (left.MethodBodies, right.MethodBodies), + HoistedLocalsLattice.Meet (left.HoistedLocals, right.HoistedLocals), + this); + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs new file mode 100644 index 0000000000000..92365210a80d7 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; +using Mono.Cecil.Cil; + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record LocalVariableReferenceValue (VariableDefinition LocalDefinition) : ReferenceValue + { + public override SingleValue DeepCopy () + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs index b50de89788f1f..1c056663ebc54 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs @@ -3,13 +3,18 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using ILLink.Shared; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; +using LocalVariableStore = System.Collections.Generic.Dictionary< + Mono.Cecil.Cil.VariableDefinition, + Mono.Linker.Dataflow.ValueBasicBlockPair>; using MultiValue = ILLink.Shared.DataFlow.ValueSet; namespace Mono.Linker.Dataflow @@ -21,38 +26,32 @@ namespace Mono.Linker.Dataflow { public MultiValue Value { get; } - /// - /// True if the value is on the stack as a byref - /// - public bool IsByRef { get; } - public StackSlot () { Value = new MultiValue (UnknownValue.Instance); - IsByRef = false; } - public StackSlot (SingleValue value, bool isByRef = false) + public StackSlot (SingleValue value) { Value = new MultiValue (value); - IsByRef = isByRef; } - public StackSlot (MultiValue value, bool isByRef = false) + public StackSlot (MultiValue value) { Value = value; - IsByRef = isByRef; } } abstract partial class MethodBodyScanner { protected readonly LinkContext _context; + protected readonly InterproceduralStateLattice InterproceduralStateLattice; protected static ValueSetLattice MultiValueLattice => default; protected MethodBodyScanner (LinkContext context) { this._context = context; + this.InterproceduralStateLattice = default; } internal MultiValue ReturnValue { private set; get; } @@ -180,7 +179,25 @@ public int MoveNext (Instruction op) } } - private static void StoreMethodLocalValue ( + [Conditional ("DEBUG")] + static void ValidateNoReferenceToReference (LocalVariableStore locals, MethodDefinition method, int ilOffset) + { + foreach (var keyValuePair in locals) { + MultiValue localValue = keyValuePair.Value.Value; + VariableDefinition localVariable = keyValuePair.Key; + foreach (var val in localValue) { + if (val is LocalVariableReferenceValue reference + && locals[reference.LocalDefinition].Value.Any (v => v is ReferenceValue)) { + throw new LinkerFatalErrorException (MessageContainer.CreateCustomErrorMessage ( + $"In method {method.FullName}, local variable {localVariable.Index} references variable {reference.LocalDefinition.Index} which is a reference.", + (int) DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin (method, ilOffset))); + } + } + } + } + + protected static void StoreMethodLocalValue ( Dictionary valueCollection, in MultiValue valueToStore, KeyType collectionKey, @@ -207,11 +224,61 @@ public int MoveNext (Instruction op) } } - public void Scan (MethodBody methodBody) + // Scans the method as well as any nested functions (local functions or lambdas) and state machines + // reachable from it. + public virtual void InterproceduralScan (MethodBody startingMethodBody) + { + MethodDefinition startingMethod = startingMethodBody.Method; + + // Note that the default value of a hoisted local will be MultiValueLattice.Top, not UnknownValue.Instance. + // This ensures that there are no warnings for the "unassigned state" of a parameter. + // Definite assignment should ensure that there is no way for this to be an analysis hole. + var interproceduralState = InterproceduralStateLattice.Top; + + var oldInterproceduralState = interproceduralState.Clone (); + interproceduralState.TrackMethod (startingMethodBody); + + while (!interproceduralState.Equals (oldInterproceduralState)) { + oldInterproceduralState = interproceduralState.Clone (); + + // Flow state through all methods encountered so far, as long as there + // are changes discovered in the hoisted local state on entry to any method. + foreach (var methodBodyValue in oldInterproceduralState.MethodBodies) + Scan (methodBodyValue.MethodBody, ref interproceduralState); + } + +#if DEBUG + // Validate that the compiler-generated callees tracked by the compiler-generated state + // are the same set of methods that we discovered and scanned above. + if (_context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod (startingMethod, out List? compilerGeneratedCallees)) { + var calleeMethods = compilerGeneratedCallees.OfType (); + // https://github.com/dotnet/linker/issues/2845 + // Disabled asserts due to a bug + // Debug.Assert (interproceduralState.Count == 1 + calleeMethods.Count ()); + // foreach (var method in calleeMethods) + // Debug.Assert (interproceduralState.Any (kvp => kvp.Key.Method == method)); + } else { + Debug.Assert (interproceduralState.MethodBodies.Count () == 1); + } +#endif + } + + void TrackNestedFunctionReference (MethodReference referencedMethod, ref InterproceduralState interproceduralState) + { + if (_context.TryResolve (referencedMethod) is not MethodDefinition method) + return; + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)) + return; + + interproceduralState.TrackMethod (method); + } + + protected virtual void Scan (MethodBody methodBody, ref InterproceduralState interproceduralState) { MethodDefinition thisMethod = methodBody.Method; - Dictionary locals = new Dictionary (methodBody.Variables.Count); + LocalVariableStore locals = new (methodBody.Variables.Count); Dictionary> knownStacks = new Dictionary> (); Stack? currentStack = new Stack (methodBody.MaxStackSize); @@ -222,6 +289,7 @@ public void Scan (MethodBody methodBody) ReturnValue = new (); foreach (Instruction operation in methodBody.Instructions) { + ValidateNoReferenceToReference (locals, methodBody.Method, operation.Offset); int curBasicBlock = blockIterator.MoveNext (operation); if (knownStacks.ContainsKey (operation.Offset)) { @@ -315,7 +383,6 @@ public void Scan (MethodBody methodBody) break; case Code.Arglist: - case Code.Ldftn: case Code.Sizeof: case Code.Ldc_I8: case Code.Ldc_R4: @@ -323,6 +390,11 @@ public void Scan (MethodBody methodBody) PushUnknown (currentStack); break; + case Code.Ldftn: + TrackNestedFunctionReference ((MethodReference) operation.Operand, ref interproceduralState); + PushUnknown (currentStack); + break; + case Code.Ldarg: case Code.Ldarg_0: case Code.Ldarg_1: @@ -426,7 +498,7 @@ public void Scan (MethodBody methodBody) case Code.Ldsfld: case Code.Ldflda: case Code.Ldsflda: - ScanLdfld (operation, currentStack, methodBody); + ScanLdfld (operation, currentStack, methodBody, ref interproceduralState); break; case Code.Newarr: { @@ -470,7 +542,7 @@ public void Scan (MethodBody methodBody) case Code.Stfld: case Code.Stsfld: - ScanStfld (operation, currentStack, thisMethod, methodBody); + ScanStfld (operation, currentStack, thisMethod, methodBody, ref interproceduralState); break; case Code.Cpobj: @@ -486,7 +558,7 @@ public void Scan (MethodBody methodBody) case Code.Stind_R8: case Code.Stind_Ref: case Code.Stobj: - ScanIndirectStore (operation, currentStack, methodBody); + ScanIndirectStore (operation, currentStack, methodBody, locals, curBasicBlock); break; case Code.Initobj: @@ -545,7 +617,8 @@ public void Scan (MethodBody methodBody) case Code.Call: case Code.Callvirt: case Code.Newobj: - HandleCall (methodBody, operation, currentStack, curBasicBlock); + TrackNestedFunctionReference ((MethodReference) operation.Operand, ref interproceduralState); + HandleCall (methodBody, operation, currentStack, locals, ref interproceduralState, curBasicBlock); break; case Code.Jmp: @@ -580,7 +653,9 @@ public void Scan (MethodBody methodBody) } if (hasReturnValue) { StackSlot retValue = PopUnknown (currentStack, 1, methodBody, operation.Offset); - ReturnValue = MultiValueLattice.Meet (ReturnValue, retValue.Value); + // If the return value is a reference, treat it as the value itself for now + // We can handle ref return values better later + ReturnValue = MultiValueLattice.Meet (ReturnValue, DereferenceValue (retValue.Value, locals, ref interproceduralState)); } ClearStack (ref currentStack); break; @@ -675,12 +750,16 @@ private void ScanLdarg (Instruction operation, Stack currentStack, Me paramNum = paramDefinition.Index; } + // This is semantically wrong if it returns true - we would representing a reference parameter as a reference to a parameter - but it should be fine for now isByRef = paramDefinition.ParameterType.IsByRefOrPointer (); } isByRef |= code == Code.Ldarga || code == Code.Ldarga_S; - StackSlot slot = new StackSlot (GetMethodParameterValue (thisMethod, paramNum), isByRef); + StackSlot slot = new StackSlot ( + isByRef + ? new ParameterReferenceValue (thisMethod, paramNum) + : GetMethodParameterValue (thisMethod, paramNum)); currentStack.Push (slot); } @@ -703,7 +782,7 @@ private void ScanLdarg (Instruction operation, Stack currentStack, Me Instruction operation, Stack currentStack, MethodBody methodBody, - Dictionary locals) + LocalVariableStore locals) { VariableDefinition localDef = GetLocalDef (operation, methodBody.Variables); if (localDef == null) { @@ -711,13 +790,16 @@ private void ScanLdarg (Instruction operation, Stack currentStack, Me return; } - bool isByRef = operation.OpCode.Code == Code.Ldloca || operation.OpCode.Code == Code.Ldloca_S - || localDef.VariableType.IsByRefOrPointer (); + bool isByRef = operation.OpCode.Code == Code.Ldloca || operation.OpCode.Code == Code.Ldloca_S; - if (!locals.TryGetValue (localDef, out ValueBasicBlockPair localValue)) - currentStack.Push (new StackSlot (UnknownValue.Instance, isByRef)); + StackSlot newSlot; + if (isByRef) { + newSlot = new StackSlot (new LocalVariableReferenceValue (localDef)); + } else if (locals.TryGetValue (localDef, out ValueBasicBlockPair localValue)) + newSlot = new StackSlot (localValue.Value); else - currentStack.Push (new StackSlot (localValue.Value, isByRef)); + newSlot = new StackSlot (UnknownValue.Instance); + currentStack.Push (newSlot); } void ScanLdtoken (Instruction operation, Stack currentStack) @@ -763,7 +845,7 @@ void ScanLdtoken (Instruction operation, Stack currentStack) Instruction operation, Stack currentStack, MethodBody methodBody, - Dictionary locals, + LocalVariableStore locals, int curBasicBlock) { StackSlot valueToStore = PopUnknown (currentStack, 1, methodBody, operation.Offset); @@ -779,16 +861,59 @@ void ScanLdtoken (Instruction operation, Stack currentStack) private void ScanIndirectStore ( Instruction operation, Stack currentStack, - MethodBody methodBody) + MethodBody methodBody, + LocalVariableStore locals, + int curBasicBlock) { StackSlot valueToStore = PopUnknown (currentStack, 1, methodBody, operation.Offset); StackSlot destination = PopUnknown (currentStack, 1, methodBody, operation.Offset); - foreach (var uniqueDestination in destination.Value) { - if (uniqueDestination is FieldValue fieldDestination) { - HandleStoreField (methodBody.Method, fieldDestination, operation, valueToStore.Value); - } else if (uniqueDestination is MethodParameterValue parameterDestination) { - HandleStoreParameter (methodBody.Method, parameterDestination, operation, valueToStore.Value); + StoreInReference (destination.Value, valueToStore.Value, methodBody.Method, operation, locals, curBasicBlock); + } + + /// + /// Handles storing the source value in a target or MultiValue of ReferenceValues. + /// + /// A set of that a value is being stored into + /// The value to store + /// The method body that contains the operation causing the store + /// The instruction causing the store + /// Throws if is not a valid target for an indirect store. + protected void StoreInReference (MultiValue target, MultiValue source, MethodDefinition method, Instruction operation, LocalVariableStore locals, int curBasicBlock) + { + foreach (var value in target) { + switch (value) { + case LocalVariableReferenceValue localReference: + StoreMethodLocalValue (locals, source, localReference.LocalDefinition, curBasicBlock); + break; + case FieldReferenceValue fieldReference + when GetFieldValue (fieldReference.FieldDefinition).AsSingleValue () is FieldValue fieldValue: + HandleStoreField (method, fieldValue, operation, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue (parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodParameterValue parameterValue: + HandleStoreParameter (method, parameterValue, operation, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue (parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodThisParameterValue thisParameterValue: + HandleStoreMethodThisParameter (method, thisParameterValue, operation, source); + break; + case MethodReturnValue methodReturnValue: + // Ref returns don't have special ReferenceValue values, so assume if the target here is a MethodReturnValue then it must be a ref return value + HandleStoreMethodReturnValue (method, methodReturnValue, operation, source); + break; + case IValueWithStaticType valueWithStaticType: + if (valueWithStaticType.StaticType is not null && _context.Annotations.FlowAnnotations.IsTypeInterestingForDataflow (valueWithStaticType.StaticType)) + throw new LinkerFatalErrorException (MessageContainer.CreateErrorMessage ( + $"Unhandled StoreReference call. Unhandled attempt to store a value in {value} of type {value.GetType ()}.", + (int) DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin (method, operation.Offset))); + // This should only happen for pointer derefs, which can't point to interesting types + break; + default: + // These cases should only be refs to array elements + // References to array elements are not yet tracked and since we don't allow annotations on arrays, they won't cause problems + break; } } @@ -799,7 +924,8 @@ void ScanLdtoken (Instruction operation, Stack currentStack) private void ScanLdfld ( Instruction operation, Stack currentStack, - MethodBody methodBody) + MethodBody methodBody, + ref InterproceduralState interproceduralState) { Code code = operation.OpCode.Code; if (code == Code.Ldfld || code == Code.Ldflda) @@ -808,13 +934,20 @@ void ScanLdtoken (Instruction operation, Stack currentStack) bool isByRef = code == Code.Ldflda || code == Code.Ldsflda; FieldDefinition? field = _context.TryResolve ((FieldReference) operation.Operand); - if (field != null) { - StackSlot slot = new StackSlot (GetFieldValue (field), isByRef); - currentStack.Push (slot); + if (field == null) { + PushUnknown (currentStack); return; } - PushUnknown (currentStack); + MultiValue value; + if (isByRef) { + value = new FieldReferenceValue (field); + } else if (CompilerGeneratedState.IsHoistedLocal (field)) { + value = interproceduralState.GetHoistedLocal (new HoistedLocalKey (field)); + } else { + value = GetFieldValue (field); + } + currentStack.Push (new StackSlot (value)); } protected virtual void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore) @@ -825,11 +958,20 @@ protected virtual void HandleStoreParameter (MethodDefinition method, MethodPara { } + protected virtual void HandleStoreMethodThisParameter (MethodDefinition method, MethodThisParameterValue thisParameter, Instruction operation, MultiValue sourceValue) + { + } + + protected virtual void HandleStoreMethodReturnValue (MethodDefinition method, MethodReturnValue thisParameter, Instruction operation, MultiValue sourceValue) + { + } + private void ScanStfld ( Instruction operation, Stack currentStack, MethodDefinition thisMethod, - MethodBody methodBody) + MethodBody methodBody, + ref InterproceduralState interproceduralState) { StackSlot valueToStoreSlot = PopUnknown (currentStack, 1, methodBody, operation.Offset); if (operation.OpCode.Code == Code.Stfld) @@ -837,6 +979,11 @@ protected virtual void HandleStoreParameter (MethodDefinition method, MethodPara FieldDefinition? field = _context.TryResolve ((FieldReference) operation.Operand); if (field != null) { + if (CompilerGeneratedState.IsHoistedLocal (field)) { + interproceduralState.SetHoistedLocal (new HoistedLocalKey (field), valueToStoreSlot.Value); + return; + } + foreach (var value in GetFieldValue (field)) { // GetFieldValue may return different node types, in which case they can't be stored to. // At least not yet. @@ -887,10 +1034,70 @@ private static VariableDefinition GetLocalDef (Instruction operation, Collection return methodParams; } + internal MultiValue DereferenceValue (MultiValue maybeReferenceValue, Dictionary locals, ref InterproceduralState interproceduralState) + { + MultiValue dereferencedValue = MultiValueLattice.Top; + foreach (var value in maybeReferenceValue) { + switch (value) { + case FieldReferenceValue fieldReferenceValue: + dereferencedValue = MultiValue.Meet ( + dereferencedValue, + CompilerGeneratedState.IsHoistedLocal (fieldReferenceValue.FieldDefinition) + ? interproceduralState.GetHoistedLocal (new HoistedLocalKey (fieldReferenceValue.FieldDefinition)) + : GetFieldValue (fieldReferenceValue.FieldDefinition)); + break; + case ParameterReferenceValue parameterReferenceValue: + dereferencedValue = MultiValue.Meet ( + dereferencedValue, + GetMethodParameterValue (parameterReferenceValue.MethodDefinition, parameterReferenceValue.ParameterIndex)); + break; + case LocalVariableReferenceValue localVariableReferenceValue: + if (locals.TryGetValue (localVariableReferenceValue.LocalDefinition, out var valueBasicBlockPair)) + dereferencedValue = MultiValue.Meet (dereferencedValue, valueBasicBlockPair.Value); + else + dereferencedValue = MultiValue.Meet (dereferencedValue, UnknownValue.Instance); + break; + case ReferenceValue referenceValue: + throw new NotImplementedException ($"Unhandled dereference of ReferenceValue of type {referenceValue.GetType ().FullName}"); + default: + dereferencedValue = MultiValue.Meet (dereferencedValue, value); + break; + } + } + return dereferencedValue; + } + + /// + /// Assigns a MethodParameterValue to the location of each parameter passed by reference. (i.e. assigns the value to x when passing `ref x` as a parameter) + /// + protected void AssignRefAndOutParameters ( + MethodBody callingMethodBody, + MethodReference calledMethod, + ValueNodeList methodArguments, + Instruction operation, + LocalVariableStore locals, + int curBasicBlock) + { + MethodDefinition? calledMethodDefinition = _context.Resolve (calledMethod); + bool methodIsResolved = calledMethodDefinition is not null; + int offset = calledMethod.HasImplicitThis () ? 1 : 0; + int parameterIndex = 0; + for (int ilArgumentIndex = offset; ilArgumentIndex < methodArguments.Count; ilArgumentIndex++, parameterIndex++) { + if (calledMethod.ParameterReferenceKind (ilArgumentIndex) is not (ReferenceKind.Ref or ReferenceKind.Out)) + continue; + SingleValue newByRefValue = methodIsResolved + ? _context.Annotations.FlowAnnotations.GetMethodParameterValue (calledMethodDefinition!, parameterIndex) + : UnknownValue.Instance; + StoreInReference (methodArguments[ilArgumentIndex], newByRefValue, callingMethodBody.Method, operation, locals, curBasicBlock); + } + } + private void HandleCall ( MethodBody callingMethodBody, Instruction operation, Stack currentStack, + LocalVariableStore locals, + ref InterproceduralState interproceduralState, int curBasicBlock) { MethodReference calledMethod = (MethodReference) operation.Operand; @@ -898,15 +1105,17 @@ private static VariableDefinition GetLocalDef (Instruction operation, Collection bool isNewObj = operation.OpCode.Code == Code.Newobj; SingleValue? newObjValue; - ValueNodeList methodParams = PopCallArguments (currentStack, calledMethod, callingMethodBody, isNewObj, + ValueNodeList methodArguments = PopCallArguments (currentStack, calledMethod, callingMethodBody, isNewObj, operation.Offset, out newObjValue); - + var dereferencedMethodParams = new List (); + foreach (var argument in methodArguments) + dereferencedMethodParams.Add (DereferenceValue (argument, locals, ref interproceduralState)); MultiValue methodReturnValue; bool handledFunction = HandleCall ( callingMethodBody, calledMethod, operation, - methodParams, + new ValueNodeList (dereferencedMethodParams), out methodReturnValue); // Handle the return value or newobj result @@ -924,9 +1133,11 @@ private static VariableDefinition GetLocalDef (Instruction operation, Collection } if (isNewObj || !calledMethod.ReturnsVoid ()) - currentStack.Push (new StackSlot (methodReturnValue, calledMethod.ReturnType.IsByRefOrPointer ())); + currentStack.Push (new StackSlot (methodReturnValue)); + + AssignRefAndOutParameters (callingMethodBody, calledMethod, methodArguments, operation, locals, curBasicBlock); - foreach (var param in methodParams) { + foreach (var param in methodArguments) { foreach (var v in param) { if (v is ArrayValue arr) { MarkArrayValuesAsUnknown (arr, curBasicBlock); @@ -991,6 +1202,7 @@ private static void MarkArrayValuesAsUnknown (ArrayValue arrValue, int curBasicB PushUnknown (currentStack); return; } + // We don't yet handle arrays of references or pointers bool isByRef = operation.OpCode.Code == Code.Ldelema; int? index = indexToLoadFrom.Value.AsConstInt (); @@ -999,11 +1211,13 @@ private static void MarkArrayValuesAsUnknown (ArrayValue arrValue, int curBasicB if (isByRef) { MarkArrayValuesAsUnknown (arr, curBasicBlock); } - return; } - - if (arr.IndexValues.TryGetValue (index.Value, out ValueBasicBlockPair arrayIndexValue)) - currentStack.Push (new StackSlot (arrayIndexValue.Value, isByRef)); + // Don't try to track refs to array elements. Set it as unknown, then push unknown to the stack + else if (isByRef) { + arr.IndexValues[index.Value] = new ValueBasicBlockPair (UnknownValue.Instance, curBasicBlock); + PushUnknown (currentStack); + } else if (arr.IndexValues.TryGetValue (index.Value, out ValueBasicBlockPair arrayIndexValue)) + currentStack.Push (new StackSlot (arrayIndexValue.Value)); else PushUnknown (currentStack); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs index 6180b2bdb4f20..6821613c44f50 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Immutable; using Mono.Cecil; using Mono.Linker; namespace ILLink.Shared.TypeSystemProxy { - readonly partial struct MethodProxy + readonly partial struct MethodProxy : IEquatable { public MethodProxy (MethodDefinition method) => Method = method; @@ -51,5 +52,13 @@ namespace ILLink.Shared.TypeSystemProxy internal partial bool ReturnsVoid () => Method.ReturnsVoid (); public override string ToString () => Method.ToString (); + + public ReferenceKind ParameterReferenceKind (int index) => Method.HasImplicitThis () ? Method.ParameterReferenceKind (index + 1) : Method.ParameterReferenceKind (index); + + public bool Equals (MethodProxy other) => Method.Equals (other.Method); + + public override bool Equals (object? obj) => obj is MethodProxy other && Equals (other); + + public override int GetHashCode () => Method.GetHashCode (); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs new file mode 100644 index 0000000000000..7f3804791f999 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; +using Mono.Cecil; + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record ParameterReferenceValue (MethodDefinition MethodDefinition, int ParameterIndex) +: ReferenceValue + { + public override SingleValue DeepCopy () + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs new file mode 100644 index 0000000000000..4e43a95cdc531 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// Acts as the base class for all values that represent a reference to another value. These should only be held in a ref type or on the stack as a result of a 'load address' instruction (e.g. ldloca). + /// + public abstract record ReferenceValue : SingleValue { } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs index 51268fdfac854..be699512ac586 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs @@ -2,8 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using ILLink.Shared.TrimAnalysis; using Mono.Cecil; using Mono.Linker.Steps; @@ -13,15 +15,20 @@ namespace Mono.Linker.Dataflow { readonly LinkContext _context; readonly MarkStep _markStep; + readonly bool _enabled; - public ReflectionMarker (LinkContext context, MarkStep markStep) + public ReflectionMarker (LinkContext context, MarkStep markStep, bool enabled) { _context = context; _markStep = markStep; + _enabled = enabled; } internal void MarkTypeForDynamicallyAccessedMembers (in MessageOrigin origin, TypeDefinition typeDefinition, DynamicallyAccessedMemberTypes requiredMemberTypes, DependencyKind dependencyKind, bool declaredOnly = false) { + if (!_enabled) + return; + foreach (var member in typeDefinition.GetDynamicallyAccessedMembers (_context, requiredMemberTypes, declaredOnly)) { switch (member) { case MethodDefinition method: @@ -46,77 +53,146 @@ internal void MarkTypeForDynamicallyAccessedMembers (in MessageOrigin origin, Ty } } - internal bool TryResolveTypeNameAndMark (string typeName, MessageOrigin origin, bool needsAssemblyName, [NotNullWhen (true)] out TypeDefinition? type) + // Resolve a (potentially assembly qualified) type name based on the current context (taken from DiagnosticContext) and mark the type for reflection. + // This method will probe the current context assembly and if that fails CoreLib for the specified type. Emulates behavior of Type.GetType. + internal bool TryResolveTypeNameAndMark (string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen (true)] out TypeDefinition? type) + { + if (!_context.TypeNameResolver.TryResolveTypeName (typeName, diagnosticContext, out TypeReference? typeRef, out var typeResolutionRecords, needsAssemblyName) + || typeRef.ResolveToTypeDefinition (_context) is not TypeDefinition foundType) { + type = default; + return false; + } + + MarkResolvedType (diagnosticContext, typeRef, foundType, typeResolutionRecords); + + type = foundType; + return true; + } + + // Resolve a type from the specified assembly and mark it for reflection. + internal bool TryResolveTypeNameAndMark (AssemblyDefinition assembly, string typeName, in DiagnosticContext diagnosticContext, [NotNullWhen (true)] out TypeDefinition? type) { - if (!_context.TypeNameResolver.TryResolveTypeName (typeName, origin.Provider, out TypeReference? typeRef, out AssemblyDefinition? typeAssembly, needsAssemblyName) + if (!_context.TypeNameResolver.TryResolveTypeName (assembly, typeName, out TypeReference? typeRef, out var typeResolutionRecords) || typeRef.ResolveToTypeDefinition (_context) is not TypeDefinition foundType) { type = default; return false; } - _markStep.MarkTypeVisibleToReflection (typeRef, foundType, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin); - _context.MarkingHelpers.MarkMatchingExportedType (foundType, typeAssembly, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, foundType), origin); + MarkResolvedType (diagnosticContext, typeRef, foundType, typeResolutionRecords); type = foundType; return true; } + void MarkResolvedType ( + in DiagnosticContext diagnosticContext, + TypeReference typeReference, + TypeDefinition typeDefinition, + List typeResolutionRecords) + { + if (_enabled) { + // Mark the resolved type for reflection access, but also go over all types which were resolved in the process + // of resolving the outer type (typically generic arguments) and make sure we mark all type forwarders + // used for that resolution. + // This is necessary because if the app's code contains the input string as literal (which is pretty much always the case) + // that string has to work at runtime, and if it relies on type forwarders we need to preserve those as well. + var origin = diagnosticContext.Origin; + _markStep.MarkTypeVisibleToReflection (typeReference, typeDefinition, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin); + foreach (var typeResolutionRecord in typeResolutionRecords) { + _context.MarkingHelpers.MarkMatchingExportedType (typeResolutionRecord.ResolvedType, typeResolutionRecord.ReferringAssembly, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, typeDefinition), origin); + } + } + } + internal void MarkType (in MessageOrigin origin, TypeDefinition type, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkTypeVisibleToReflection (type, type, new DependencyInfo (dependencyKind, origin.Provider), origin); } internal void MarkMethod (in MessageOrigin origin, MethodDefinition method, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkMethodVisibleToReflection (method, new DependencyInfo (dependencyKind, origin.Provider), origin); } void MarkField (in MessageOrigin origin, FieldDefinition field, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkFieldVisibleToReflection (field, new DependencyInfo (dependencyKind, origin.Provider), origin); } internal void MarkProperty (in MessageOrigin origin, PropertyDefinition property, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkPropertyVisibleToReflection (property, new DependencyInfo (dependencyKind, origin.Provider), origin); } void MarkEvent (in MessageOrigin origin, EventDefinition @event, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkEventVisibleToReflection (@event, new DependencyInfo (dependencyKind, origin.Provider), origin); } void MarkInterfaceImplementation (in MessageOrigin origin, InterfaceImplementation interfaceImplementation, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkInterfaceImplementation (interfaceImplementation, null, new DependencyInfo (dependencyKind, origin.Provider)); } internal void MarkConstructorsOnType (in MessageOrigin origin, TypeDefinition type, Func? filter, BindingFlags? bindingFlags = null) { + if (!_enabled) + return; + foreach (var ctor in type.GetConstructorsOnType (filter, bindingFlags)) MarkMethod (origin, ctor); } internal void MarkFieldsOnTypeHierarchy (in MessageOrigin origin, TypeDefinition type, Func filter, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var field in type.GetFieldsOnTypeHierarchy (_context, filter, bindingFlags)) MarkField (origin, field); } internal void MarkPropertiesOnTypeHierarchy (in MessageOrigin origin, TypeDefinition type, Func filter, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var property in type.GetPropertiesOnTypeHierarchy (_context, filter, bindingFlags)) MarkProperty (origin, property); } internal void MarkEventsOnTypeHierarchy (in MessageOrigin origin, TypeDefinition type, Func filter, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var @event in type.GetEventsOnTypeHierarchy (_context, filter, bindingFlags)) MarkEvent (origin, @event); } internal void MarkStaticConstructor (in MessageOrigin origin, TypeDefinition type) { + if (!_enabled) + return; + _markStep.MarkStaticConstructorVisibleToReflection (type, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs index ee9cd02264cb3..02a512806bfe6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs @@ -2,13 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using ILLink.Shared; -using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; using Mono.Cecil; @@ -18,12 +16,13 @@ namespace Mono.Linker.Dataflow { - class ReflectionMethodBodyScanner : MethodBodyScanner + sealed class ReflectionMethodBodyScanner : MethodBodyScanner { readonly MarkStep _markStep; MessageOrigin _origin; readonly FlowAnnotations _annotations; readonly ReflectionMarker _reflectionMarker; + public readonly TrimAnalysisPatternStore TrimAnalysisPatterns; public static bool RequiresReflectionMethodBodyScannerForCallSite (LinkContext context, MethodReference calledMethod) { @@ -52,117 +51,34 @@ public static bool RequiresReflectionMethodBodyScannerForAccess (LinkContext con return context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (fieldDefinition); } - bool ShouldEnableReflectionPatternReporting (ICustomAttributeProvider? provider) - { - if (_markStep.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (provider)) - return false; - - return true; - } - public ReflectionMethodBodyScanner (LinkContext context, MarkStep parent, MessageOrigin origin) : base (context) { _markStep = parent; _origin = origin; _annotations = context.Annotations.FlowAnnotations; - _reflectionMarker = new ReflectionMarker (context, parent); + _reflectionMarker = new ReflectionMarker (context, parent, enabled: false); + TrimAnalysisPatterns = new TrimAnalysisPatternStore (MultiValueLattice, context); } - public void ScanAndProcessReturnValue (MethodBody methodBody) + public override void InterproceduralScan (MethodBody methodBody) { - Scan (methodBody); - - if (!methodBody.Method.ReturnsVoid ()) { - var method = methodBody.Method; - var methodReturnValue = _annotations.GetMethodReturnValue (method); - if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) { - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, ReturnValue, methodReturnValue); - } - } - } + base.InterproceduralScan (methodBody); - public void ProcessAttributeDataflow (MethodDefinition method, IList arguments) - { - for (int i = 0; i < method.Parameters.Count; i++) { - var parameterValue = _annotations.GetMethodParameterValue (method, i); - if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { - MultiValue value = GetValueNodeForCustomAttributeArgument (arguments[i]); - var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); - RequireDynamicallyAccessedMembers (diagnosticContext, value, parameterValue); - } - } + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); + TrimAnalysisPatterns.MarkAndProduceDiagnostics (reflectionMarker, _markStep); } - public void ProcessAttributeDataflow (FieldDefinition field, CustomAttributeArgument value) + protected override void Scan (MethodBody methodBody, ref InterproceduralState interproceduralState) { - MultiValue valueNode = GetValueNodeForCustomAttributeArgument (value); - foreach (var fieldValueCandidate in GetFieldValue (field)) { - if (fieldValueCandidate is not ValueWithDynamicallyAccessedMembers fieldValue) - continue; + _origin = new MessageOrigin (methodBody.Method); + base.Scan (methodBody, ref interproceduralState); - var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); - RequireDynamicallyAccessedMembers (diagnosticContext, valueNode, fieldValue); - } - } - - MultiValue GetValueNodeForCustomAttributeArgument (CustomAttributeArgument argument) - { - SingleValue value; - if (argument.Type.Name == "Type") { - TypeDefinition? referencedType = ResolveToTypeDefinition ((TypeReference) argument.Value); - if (referencedType == null) - value = UnknownValue.Instance; - else - value = new SystemTypeValue (referencedType); - } else if (argument.Type.MetadataType == MetadataType.String) { - value = new KnownStringValue ((string) argument.Value); - } else { - // We shouldn't have gotten a non-null annotation for this from GetParameterAnnotation - throw new InvalidOperationException (); - } - - Debug.Assert (value != null); - return value; - } - - public void ProcessGenericArgumentDataFlow (GenericParameter genericParameter, TypeReference genericArgument) - { - var genericParameterValue = _annotations.GetGenericParameterValue (genericParameter); - Debug.Assert (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); - - MultiValue genericArgumentValue = GetTypeValueNodeFromGenericArgument (genericArgument); - - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, genericArgumentValue, genericParameterValue); - } - - MultiValue GetTypeValueNodeFromGenericArgument (TypeReference genericArgument) - { - if (genericArgument is GenericParameter inputGenericParameter) { - // Technically this should be a new value node type as it's not a System.Type instance representation, but just the generic parameter - // That said we only use it to perform the dynamically accessed members checks and for that purpose treating it as System.Type is perfectly valid. - return _annotations.GetGenericParameterValue (inputGenericParameter); - } else if (ResolveToTypeDefinition (genericArgument) is TypeDefinition genericArgumentType) { - if (genericArgumentType.IsTypeOf (WellKnownType.System_Nullable_T)) { - var innerGenericArgument = (genericArgument as IGenericInstance)?.GenericArguments.FirstOrDefault (); - switch (innerGenericArgument) { - case GenericParameter gp: - return new NullableValueWithDynamicallyAccessedMembers (genericArgumentType, - new GenericParameterValue (gp, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (gp))); - - case TypeReference underlyingType: - if (ResolveToTypeDefinition (underlyingType) is TypeDefinition underlyingTypeDefinition) - return new NullableSystemTypeValue (genericArgumentType, new SystemTypeValue (underlyingTypeDefinition)); - else - return UnknownValue.Instance; - } - } - // All values except for Nullable, including Nullable<> (with no type arguments) - return new SystemTypeValue (genericArgumentType); - } else { - return UnknownValue.Instance; + if (!methodBody.Method.ReturnsVoid ()) { + var method = methodBody.Method; + var methodReturnValue = _annotations.GetMethodReturnValue (method); + if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) + HandleAssignmentPattern (_origin, ReturnValue, methodReturnValue); } } @@ -192,63 +108,98 @@ ValueWithDynamicallyAccessedMembers GetMethodParameterValue (MethodDefinition me return _annotations.GetMethodParameterValue (method, parameterIndex, dynamicallyAccessedMemberTypes); } - protected override MultiValue GetFieldValue (FieldDefinition field) - { - switch (field.Name) { - case "EmptyTypes" when field.DeclaringType.IsTypeOf (WellKnownType.System_Type): { - return ArrayValue.Create (0, field.DeclaringType); - } - case "Empty" when field.DeclaringType.IsTypeOf (WellKnownType.System_String): { - return new KnownStringValue (string.Empty); - } - - default: { - DynamicallyAccessedMemberTypes memberTypes = _context.Annotations.FlowAnnotations.GetFieldAnnotation (field); - return new FieldValue (ResolveToTypeDefinition (field.FieldType), field, memberTypes); - } - } - } + protected override MultiValue GetFieldValue (FieldDefinition field) => _annotations.GetFieldValue (field); - protected override void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore) + private void HandleStoreValueWithDynamicallyAccessedMembers (ValueWithDynamicallyAccessedMembers targetValue, Instruction operation, MultiValue sourceValue) { - if (field.DynamicallyAccessedMemberTypes != 0) { + if (targetValue.DynamicallyAccessedMemberTypes != 0) { _origin = _origin.WithInstructionOffset (operation.Offset); - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, valueToStore, field); + HandleAssignmentPattern (_origin, sourceValue, targetValue); } } + protected override void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers (field, operation, valueToStore); + protected override void HandleStoreParameter (MethodDefinition method, MethodParameterValue parameter, Instruction operation, MultiValue valueToStore) - { - if (parameter.DynamicallyAccessedMemberTypes != 0) { - _origin = _origin.WithInstructionOffset (operation.Offset); - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, valueToStore, parameter); - } - } + => HandleStoreValueWithDynamicallyAccessedMembers (parameter, operation, valueToStore); + + protected override void HandleStoreMethodThisParameter (MethodDefinition method, MethodThisParameterValue thisParameter, Instruction operation, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers (thisParameter, operation, valueToStore); + + protected override void HandleStoreMethodReturnValue (MethodDefinition method, MethodReturnValue returnValue, Instruction operation, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers (returnValue, operation, valueToStore); public override bool HandleCall (MethodBody callingMethodBody, MethodReference calledMethod, Instruction operation, ValueNodeList methodParams, out MultiValue methodReturnValue) { methodReturnValue = new (); - MultiValue? maybeMethodReturnValue = null; var reflectionProcessed = _markStep.ProcessReflectionDependency (callingMethodBody, operation); if (reflectionProcessed) return false; - var callingMethodDefinition = callingMethodBody.Method; + Debug.Assert (callingMethodBody.Method == _origin.Provider); var calledMethodDefinition = _context.TryResolve (calledMethod); if (calledMethodDefinition == null) return false; - bool requiresDataFlowAnalysis = _context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (calledMethodDefinition); - DynamicallyAccessedMemberTypes returnValueDynamicallyAccessedMemberTypes = requiresDataFlowAnalysis ? - _context.Annotations.FlowAnnotations.GetReturnParameterAnnotation (calledMethodDefinition) : 0; - _origin = _origin.WithInstructionOffset (operation.Offset); - bool diagnosticsEnabled = ShouldEnableReflectionPatternReporting (_origin.Provider); - var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled, _context); - var handleCallAction = new HandleCallAction (_context, _reflectionMarker, diagnosticContext, callingMethodDefinition); + + MultiValue instanceValue; + ImmutableArray arguments; + if (calledMethodDefinition.HasImplicitThis ()) { + instanceValue = methodParams[0]; + arguments = methodParams.Skip (1).ToImmutableArray (); + } else { + instanceValue = MultiValueLattice.Top; + arguments = methodParams.ToImmutableArray (); + } + + TrimAnalysisPatterns.Add (new TrimAnalysisMethodCallPattern ( + operation, + calledMethod, + instanceValue, + arguments, + _origin + )); + + var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: false, _context); + return HandleCall ( + operation, + calledMethod, + instanceValue, + arguments, + diagnosticContext, + _reflectionMarker, + _context, + _markStep, + out methodReturnValue); + } + + public static bool HandleCall ( + Instruction operation, + MethodReference calledMethod, + MultiValue instanceValue, + ImmutableArray argumentValues, + DiagnosticContext diagnosticContext, + ReflectionMarker reflectionMarker, + LinkContext context, + MarkStep markStep, + out MultiValue methodReturnValue) + { + var origin = diagnosticContext.Origin; + var calledMethodDefinition = context.TryResolve (calledMethod); + Debug.Assert (calledMethodDefinition != null); + var callingMethodDefinition = origin.Provider as MethodDefinition; + Debug.Assert (callingMethodDefinition != null); + + bool requiresDataFlowAnalysis = context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (calledMethodDefinition); + var annotatedMethodReturnValue = context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition); + Debug.Assert (requiresDataFlowAnalysis || annotatedMethodReturnValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None); + + MultiValue? maybeMethodReturnValue = null; + + var handleCallAction = new HandleCallAction (context, reflectionMarker, diagnosticContext, callingMethodDefinition); switch (Intrinsics.GetIntrinsicIdForMethod (calledMethodDefinition)) { case IntrinsicId.IntrospectionExtensions_GetTypeInfo: case IntrinsicId.TypeInfo_AsType: @@ -294,42 +245,31 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c || appDomainCreateInstance == IntrinsicId.AppDomain_CreateInstanceFrom || appDomainCreateInstance == IntrinsicId.AppDomain_CreateInstanceFromAndUnwrap: case IntrinsicId.Assembly_CreateInstance: { - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (calledMethodDefinition.HasImplicitThis ()) { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip (1).ToImmutableList (); - } - return handleCallAction.Invoke (calledMethodDefinition, instanceValue, parameterValues, out methodReturnValue, out _); + return handleCallAction.Invoke (calledMethodDefinition, instanceValue, argumentValues, out methodReturnValue, out _); } case IntrinsicId.None: { if (calledMethodDefinition.IsPInvokeImpl) { // Is the PInvoke dangerous? - bool comDangerousMethod = IsComInterop (calledMethodDefinition.MethodReturnType, calledMethodDefinition.ReturnType); + bool comDangerousMethod = IsComInterop (calledMethodDefinition.MethodReturnType, calledMethodDefinition.ReturnType, context); foreach (ParameterDefinition pd in calledMethodDefinition.Parameters) { - comDangerousMethod |= IsComInterop (pd, pd.ParameterType); + comDangerousMethod |= IsComInterop (pd, pd.ParameterType, context); } if (comDangerousMethod) { diagnosticContext.AddDiagnostic (DiagnosticId.CorrectnessOfCOMCannotBeGuaranteed, calledMethodDefinition.GetDisplayName ()); } } - _markStep.CheckAndReportRequiresUnreferencedCode (calledMethodDefinition, _origin); + if (context.Annotations.DoesMethodRequireUnreferencedCode (calledMethodDefinition, out RequiresUnreferencedCodeAttribute? requiresUnreferencedCode)) + MarkStep.ReportRequiresUnreferencedCode (calledMethodDefinition.GetDisplayName (), requiresUnreferencedCode, diagnosticContext); - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (calledMethodDefinition.HasImplicitThis ()) { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip (1).ToImmutableList (); - } - return handleCallAction.Invoke (calledMethodDefinition, instanceValue, parameterValues, out methodReturnValue, out _); + return handleCallAction.Invoke (calledMethodDefinition, instanceValue, argumentValues, out methodReturnValue, out _); } case IntrinsicId.TypeDelegator_Ctor: { // This is an identity function for analysis purposes if (operation.OpCode == OpCodes.Newobj) - AddReturnValue (methodParams[1]); + AddReturnValue (argumentValues[0]); } break; @@ -338,13 +278,22 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } break; + case IntrinsicId.Enum_GetValues: + case IntrinsicId.Marshal_SizeOf: + case IntrinsicId.Marshal_OffsetOf: + case IntrinsicId.Marshal_PtrToStructure: + case IntrinsicId.Marshal_DestroyStructure: + case IntrinsicId.Marshal_GetDelegateForFunctionPointer: + // These intrinsics are not interesting for trimmer (they are interesting for AOT and that's why they are recognized) + break; + // // System.Object // // GetType() // case IntrinsicId.Object_GetType: { - foreach (var valueNode in methodParams[0]) { + foreach (var valueNode in instanceValue) { // Note that valueNode can be statically typed in IL as some generic argument type. // For example: // void Method(T instance) { instance.GetType().... } @@ -363,7 +312,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c TypeDefinition? staticType = (valueNode as IValueWithStaticType)?.StaticType; if (staticType is null) { // We don't know anything about the type GetType was called on. Track this as a usual result of a method call without any annotations - AddReturnValue (_annotations.GetMethodReturnValue (calledMethodDefinition)); + AddReturnValue (context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition)); } else if (staticType.IsSealed || staticType.IsTypeOf ("System", "Delegate")) { // We can treat this one the same as if it was a typeof() expression @@ -382,15 +331,15 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } else { // Make sure the type is marked (this will mark it as used via reflection, which is sort of true) // This should already be true for most cases (method params, fields, ...), but just in case - _reflectionMarker.MarkType (_origin, staticType); + reflectionMarker.MarkType (origin, staticType); - var annotation = _markStep.DynamicallyAccessedMembersTypeHierarchy - .ApplyDynamicallyAccessedMembersToTypeHierarchy (_reflectionMarker, staticType); + var annotation = markStep.DynamicallyAccessedMembersTypeHierarchy + .ApplyDynamicallyAccessedMembersToTypeHierarchy (staticType); // Return a value which is "unknown type" with annotation. For now we'll use the return value node // for the method, which means we're loosing the information about which staticType this // started with. For now we don't need it, but we can add it later on. - AddReturnValue (_annotations.GetMethodReturnValue (calledMethodDefinition, annotation)); + AddReturnValue (context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition, annotation)); } } } @@ -414,13 +363,13 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c bool returnsVoid = calledMethod.ReturnsVoid (); methodReturnValue = maybeMethodReturnValue ?? (returnsVoid ? MultiValueLattice.Top : - _annotations.GetMethodReturnValue (calledMethodDefinition, returnValueDynamicallyAccessedMemberTypes)); + annotatedMethodReturnValue); // Validate that the return value has the correct annotations as per the method return value annotations - if (returnValueDynamicallyAccessedMemberTypes != 0) { + if (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes != 0) { foreach (var uniqueValue in methodReturnValue) { if (uniqueValue is ValueWithDynamicallyAccessedMembers methodReturnValueWithMemberTypes) { - if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag (returnValueDynamicallyAccessedMemberTypes)) + if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes)) throw new InvalidOperationException ($"Internal linker error: processing of call from {callingMethodDefinition.GetDisplayName ()} to {calledMethod.GetDisplayName ()} returned value which is not correctly annotated with the expected dynamic member access kinds."); } else if (uniqueValue is SystemTypeValue) { // SystemTypeValue can fullfill any requirement, so it's always valid @@ -439,7 +388,7 @@ void AddReturnValue (MultiValue value) } } - bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType) + static bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType, LinkContext context) { // This is best effort. One can likely find ways how to get COM without triggering these alarms. // AsAny marshalling of a struct with an object-typed field would be one, for example. @@ -460,7 +409,7 @@ bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference param if (nativeType == NativeType.None) { // Resolve will look at the element type - var parameterTypeDef = _context.TryResolve (parameterType); + var parameterTypeDef = context.TryResolve (parameterType); if (parameterTypeDef != null) { if (parameterTypeDef.IsTypeOf (WellKnownType.System_Array)) { @@ -481,10 +430,10 @@ bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference param } else if (parameterTypeDef.IsMulticastDelegate ()) { // Delegates are special cased by interop return false; - } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle", _context)) { + } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle", context)) { // Subclasses of CriticalHandle are special cased by interop return false; - } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle", _context)) { + } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle", context)) { // Subclasses of SafeHandle are special cased by interop return false; } else if (!parameterTypeDef.IsSequentialLayout && !parameterTypeDef.IsExplicitLayout) { @@ -497,10 +446,12 @@ bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference param return false; } - void RequireDynamicallyAccessedMembers (in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue) + void HandleAssignmentPattern ( + in MessageOrigin origin, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue) { - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (_context, _reflectionMarker, diagnosticContext); - requireDynamicallyAccessedMembersAction.Invoke (value, targetValue); + TrimAnalysisPatterns.Add (new TrimAnalysisAssignmentPattern (value, targetValue, origin)); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs index d2fe33783b6ee..5f1f8ee54db7b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs @@ -11,22 +11,19 @@ namespace ILLink.Shared.TrimAnalysis { partial struct RequireDynamicallyAccessedMembersAction { - readonly LinkContext _context; readonly ReflectionMarker _reflectionMarker; public RequireDynamicallyAccessedMembersAction ( - LinkContext context, ReflectionMarker reflectionMarker, in DiagnosticContext diagnosticContext) { - _context = context; _reflectionMarker = reflectionMarker; _diagnosticContext = diagnosticContext; } public partial bool TryResolveTypeNameAndMark (string typeName, bool needsAssemblyName, out TypeProxy type) { - if (_reflectionMarker.TryResolveTypeNameAndMark (typeName, _diagnosticContext.Origin, needsAssemblyName, out TypeDefinition? foundType)) { + if (_reflectionMarker.TryResolveTypeNameAndMark (typeName, _diagnosticContext, needsAssemblyName, out TypeDefinition? foundType)) { type = new (foundType); return true; } else { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs index 166a968b66aed..c445f7d61c209 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Mono.Cecil; using Mono.Cecil.Cil; namespace Mono.Linker.Dataflow @@ -40,11 +39,6 @@ public static HashSet ComputeBranchTargets (this MethodBody methodBody) } return branchTargets; } - - public static bool IsByRefOrPointer (this TypeReference typeRef) - { - return typeRef.IsByReference || typeRef.IsPointer; - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs new file mode 100644 index 0000000000000..81010a3a62579 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TrimAnalysis; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly record struct TrimAnalysisAssignmentPattern + { + public MultiValue Source { init; get; } + public MultiValue Target { init; get; } + public MessageOrigin Origin { init; get; } + + public TrimAnalysisAssignmentPattern (MultiValue source, MultiValue target, MessageOrigin origin) + { + Source = source.Clone (); + Target = target.Clone (); + Origin = origin; + } + + public TrimAnalysisAssignmentPattern Merge (ValueSetLattice lattice, TrimAnalysisAssignmentPattern other) + { + Debug.Assert (Origin == other.Origin); + + return new TrimAnalysisAssignmentPattern ( + lattice.Meet (Source, other.Source), + lattice.Meet (Target, other.Target), + Origin); + } + + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, LinkContext context) + { + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (Origin.Provider); + var diagnosticContext = new DiagnosticContext (Origin, diagnosticsEnabled, context); + + foreach (var sourceValue in Source) { + foreach (var targetValue in Target) { + if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers) + throw new NotImplementedException (); + + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (reflectionMarker, diagnosticContext); + requireDynamicallyAccessedMembersAction.Invoke (sourceValue, targetWithDynamicallyAccessedMembers); + } + } + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs new file mode 100644 index 0000000000000..be4219ae6d698 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using System.Diagnostics; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TrimAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker.Steps; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly record struct TrimAnalysisMethodCallPattern + { + public readonly Instruction Operation; + public readonly MethodReference CalledMethod; + public readonly MultiValue Instance; + public readonly ImmutableArray Arguments; + public readonly MessageOrigin Origin; + + public TrimAnalysisMethodCallPattern ( + Instruction operation, + MethodReference calledMethod, + MultiValue instance, + ImmutableArray arguments, + MessageOrigin origin) + { + Debug.Assert (origin.Provider is MethodDefinition); + Operation = operation; + CalledMethod = calledMethod; + Instance = instance.Clone (); + if (arguments.IsEmpty) { + Arguments = ImmutableArray.Empty; + } else { + var builder = ImmutableArray.CreateBuilder (); + foreach (var argument in arguments) + builder.Add (argument.Clone ()); + Arguments = builder.ToImmutableArray (); + } + Origin = origin; + } + + public TrimAnalysisMethodCallPattern Merge (ValueSetLattice lattice, TrimAnalysisMethodCallPattern other) + { + Debug.Assert (Operation == other.Operation); + Debug.Assert (Origin == other.Origin); + Debug.Assert (CalledMethod == other.CalledMethod); + Debug.Assert (Arguments.Length == other.Arguments.Length); + + var argumentsBuilder = ImmutableArray.CreateBuilder (); + for (int i = 0; i < Arguments.Length; i++) + argumentsBuilder.Add (lattice.Meet (Arguments[i], other.Arguments[i])); + + return new TrimAnalysisMethodCallPattern ( + Operation, + CalledMethod, + lattice.Meet (Instance, other.Instance), + argumentsBuilder.ToImmutable (), + Origin); + } + + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, MarkStep markStep, LinkContext context) + { + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (Origin.Provider); + var diagnosticContext = new DiagnosticContext (Origin, diagnosticsEnabled, context); + ReflectionMethodBodyScanner.HandleCall (Operation, CalledMethod, Instance, Arguments, + diagnosticContext, + reflectionMarker, + context, + markStep, + out MultiValue _); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs new file mode 100644 index 0000000000000..ee88e9d55da04 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TrimAnalysis; +using Mono.Linker.Steps; + +namespace Mono.Linker.Dataflow +{ + public readonly struct TrimAnalysisPatternStore + { + readonly Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> AssignmentPatterns; + readonly Dictionary MethodCallPatterns; + readonly ValueSetLattice Lattice; + readonly LinkContext _context; + + public TrimAnalysisPatternStore (ValueSetLattice lattice, LinkContext context) + { + AssignmentPatterns = new Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> (); + MethodCallPatterns = new Dictionary (); + Lattice = lattice; + _context = context; + } + + public void Add (TrimAnalysisAssignmentPattern pattern) + { + // In the linker, each pattern should have a unique origin (which has ILOffset) + // but we don't track the correct ILOffset for return instructions. + // https://github.com/dotnet/linker/issues/2778 + // For now, work around it with a separate bit. + bool isReturnValue = pattern.Target.AsSingleValue () is MethodReturnValue; + + if (!AssignmentPatterns.TryGetValue ((pattern.Origin, isReturnValue), out var existingPattern)) { + AssignmentPatterns.Add ((pattern.Origin, isReturnValue), pattern); + return; + } + + AssignmentPatterns[(pattern.Origin, isReturnValue)] = pattern.Merge (Lattice, existingPattern); + } + + public void Add (TrimAnalysisMethodCallPattern pattern) + { + if (!MethodCallPatterns.TryGetValue (pattern.Origin, out var existingPattern)) { + MethodCallPatterns.Add (pattern.Origin, pattern); + return; + } + + MethodCallPatterns[pattern.Origin] = pattern.Merge (Lattice, existingPattern); + } + + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, MarkStep markStep) + { + foreach (var pattern in AssignmentPatterns.Values) + pattern.MarkAndProduceDiagnostics (reflectionMarker, _context); + + foreach (var pattern in MethodCallPatterns.Values) + pattern.MarkAndProduceDiagnostics (reflectionMarker, markStep, _context); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs index 5a0df8f347652..81d5e0581c2f9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using ILLink.Shared; using MultiValue = ILLink.Shared.DataFlow.ValueSet; namespace Mono.Linker.Dataflow @@ -47,7 +48,7 @@ public override bool Equals (object? other) } } - public struct ValueBasicBlockPair + public struct ValueBasicBlockPair : IEquatable { public ValueBasicBlockPair (MultiValue value, int basicBlockIndex) { @@ -57,5 +58,11 @@ public ValueBasicBlockPair (MultiValue value, int basicBlockIndex) public MultiValue Value { get; } public int BasicBlockIndex { get; } + + public bool Equals (ValueBasicBlockPair other) => Value.Equals (other.Value) && BasicBlockIndex.Equals (other.BasicBlockIndex); + + public override bool Equals (object? obj) => obj is ValueBasicBlockPair other && Equals (other); + + public override int GetHashCode () => HashUtils.Combine (Value.GetHashCode (), BasicBlockIndex); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs new file mode 100644 index 0000000000000..e08b955df6f44 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +#nullable enable + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// Acts as the base class for all values that represent a reference to another value. These should only be held in a ref type or on the stack as a result of a 'load address' instruction (e.g. ldloca). + /// + public abstract record ReferenceValue : SingleValue { } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index 70f3d523ed9e4..7a396cfe0e18b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Reflection.Metadata; using ILCompiler.DependencyAnalysis; using ILCompiler.Logging; using ILLink.Shared; @@ -17,27 +19,30 @@ namespace ILCompiler.Dataflow { - internal class ReflectionMarker + public class ReflectionMarker { private DependencyList _dependencies = new DependencyList(); private readonly Logger _logger; - private readonly NodeFactory _factory; - private readonly FlowAnnotations _annotations; + public NodeFactory Factory { get; } + public FlowAnnotations Annotations { get; } private bool _typeHierarchyDataFlow; - private const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); - + private bool _enabled; public DependencyList Dependencies { get => _dependencies; } - public ReflectionMarker(Logger logger, NodeFactory factory, FlowAnnotations annotations, bool typeHierarchyDataFlow) + public ReflectionMarker(Logger logger, NodeFactory factory, FlowAnnotations annotations, bool typeHierarchyDataFlow, bool enabled) { _logger = logger; - _factory = factory; - _annotations = annotations; + Factory = factory; + Annotations = annotations; _typeHierarchyDataFlow = typeHierarchyDataFlow; + _enabled = enabled; } internal void MarkTypeForDynamicallyAccessedMembers(in MessageOrigin origin, TypeDesc typeDefinition, DynamicallyAccessedMemberTypes requiredMemberTypes, Origin memberWithRequirements, bool declaredOnly = false) { + if (!_enabled) + return; + foreach (var member in typeDefinition.GetDynamicallyAccessedMembers(requiredMemberTypes, declaredOnly)) { switch (member) @@ -63,21 +68,26 @@ internal void MarkTypeForDynamicallyAccessedMembers(in MessageOrigin origin, Typ } } - internal bool TryResolveTypeNameAndMark(string typeName, MessageOrigin origin, bool needsAssemblyName, Origin memberWithRequirements, [NotNullWhen(true)] out TypeDesc? type) + internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, Origin memberWithRequirements, [NotNullWhen(true)] out TypeDesc? type) { - ModuleDesc? callingModule = ((origin.MemberDefinition as MethodDesc)?.OwningType as MetadataType)?.Module; + ModuleDesc? callingModule = ((diagnosticContext.Origin.MemberDefinition as MethodDesc)?.OwningType as MetadataType)?.Module; - if (!ILCompiler.DependencyAnalysis.ReflectionMethodBodyScanner.ResolveType(typeName, callingModule, origin.MemberDefinition.Context, out TypeDesc foundType, out ModuleDesc referenceModule)) + // NativeAOT doesn't have a fully capable type name resolver yet + // Once this is implemented don't forget to wire up marking of type forwards which are used in generic parameters + if (!ILCompiler.DependencyAnalysis.ReflectionMethodBodyScanner.ResolveType(typeName, callingModule, diagnosticContext.Origin.MemberDefinition!.Context, out TypeDesc foundType, out ModuleDesc referenceModule)) { type = default; return false; } - // Also add module metadata in case this reference was through a type forward - if (_factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) - _dependencies.Add(_factory.ModuleMetadata(referenceModule), memberWithRequirements.ToString()); + if (_enabled) + { + // Also add module metadata in case this reference was through a type forward + if (Factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) + _dependencies.Add(Factory.ModuleMetadata(referenceModule), memberWithRequirements.ToString()); - MarkType(origin, foundType, memberWithRequirements); + MarkType(diagnosticContext.Origin, foundType, memberWithRequirements); + } type = foundType; return true; @@ -85,40 +95,37 @@ internal bool TryResolveTypeNameAndMark(string typeName, MessageOrigin origin, b internal void MarkType(in MessageOrigin origin, TypeDesc type, Origin memberWithRequirements) { - RootingHelpers.TryGetDependenciesForReflectedType(ref _dependencies, _factory, type, memberWithRequirements.ToString()); + if (!_enabled) + return; + + RootingHelpers.TryGetDependenciesForReflectedType(ref _dependencies, Factory, type, memberWithRequirements.ToString()); } internal void MarkMethod(in MessageOrigin origin, MethodDesc method, Origin memberWithRequirements) { - if (method.DoesMethodRequire(RequiresUnreferencedCodeAttribute, out _)) - { - if (_typeHierarchyDataFlow) - { - _logger.LogWarning(origin, DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode, - ((TypeOrigin)memberWithRequirements).GetDisplayName(), method.GetDisplayName()); - } - } + if (!_enabled) + return; - if (_annotations.ShouldWarnWhenAccessedForReflection(method) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(method, RequiresUnreferencedCodeAttribute)) - { - WarnOnReflectionAccess(origin, method, memberWithRequirements); - } + CheckAndWarnOnReflectionAccess(origin, method, memberWithRequirements); - RootingHelpers.TryGetDependenciesForReflectedMethod(ref _dependencies, _factory, method, memberWithRequirements.ToString()); + RootingHelpers.TryGetDependenciesForReflectedMethod(ref _dependencies, Factory, method, memberWithRequirements.ToString()); } void MarkField(in MessageOrigin origin, FieldDesc field, Origin memberWithRequirements) { - if (_annotations.ShouldWarnWhenAccessedForReflection(field) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, RequiresUnreferencedCodeAttribute)) - { - WarnOnReflectionAccess(origin, field, memberWithRequirements); - } + if (!_enabled) + return; + + CheckAndWarnOnReflectionAccess(origin, field, memberWithRequirements); - RootingHelpers.TryGetDependenciesForReflectedField(ref _dependencies, _factory, field, memberWithRequirements.ToString()); + RootingHelpers.TryGetDependenciesForReflectedField(ref _dependencies, Factory, field, memberWithRequirements.ToString()); } internal void MarkProperty(in MessageOrigin origin, PropertyPseudoDesc property, Origin memberWithRequirements) { + if (!_enabled) + return; + if (property.GetMethod != null) MarkMethod(origin, property.GetMethod, memberWithRequirements); if (property.SetMethod != null) @@ -127,6 +134,9 @@ internal void MarkProperty(in MessageOrigin origin, PropertyPseudoDesc property, void MarkEvent(in MessageOrigin origin, EventPseudoDesc @event, Origin memberWithRequirements) { + if (!_enabled) + return; + if (@event.AddMethod != null) MarkMethod(origin, @event.AddMethod, memberWithRequirements); if (@event.RemoveMethod != null) @@ -135,40 +145,70 @@ void MarkEvent(in MessageOrigin origin, EventPseudoDesc @event, Origin memberWit internal void MarkConstructorsOnType(in MessageOrigin origin, TypeDesc type, Func? filter, Origin memberWithRequirements, BindingFlags? bindingFlags = null) { + if (!_enabled) + return; + foreach (var ctor in type.GetConstructorsOnType(filter, bindingFlags)) MarkMethod(origin, ctor, memberWithRequirements); } internal void MarkFieldsOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func filter, Origin memberWithRequirements, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var field in type.GetFieldsOnTypeHierarchy(filter, bindingFlags)) MarkField(origin, field, memberWithRequirements); } internal void MarkPropertiesOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func filter, Origin memberWithRequirements, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var property in type.GetPropertiesOnTypeHierarchy(filter, bindingFlags)) MarkProperty(origin, property, memberWithRequirements); } internal void MarkEventsOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func filter, Origin memberWithRequirements, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var @event in type.GetEventsOnTypeHierarchy(filter, bindingFlags)) MarkEvent(origin, @event, memberWithRequirements); } internal void MarkStaticConstructor(in MessageOrigin origin, TypeDesc type) { - if (!type.IsGenericDefinition && !type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true) && type.HasStaticConstructor) + if (!_enabled) + return; + + if (!type.IsGenericDefinition && !type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true) && Factory.PreinitializationManager.HasLazyStaticConstructor(type)) { // Mark the GC static base - it contains a pointer to the class constructor, but also info // about whether the class constructor already executed and it's what is looked at at runtime. - _dependencies.Add(_factory.TypeNonGCStaticsSymbol((MetadataType)type), "RunClassConstructor reference"); + _dependencies.Add(Factory.TypeNonGCStaticsSymbol((MetadataType)type), "RunClassConstructor reference"); } } - void WarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, Origin memberWithRequirements) + void CheckAndWarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, Origin memberWithRequirements) { + if (entity.DoesMemberRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out CustomAttributeValue? requiresAttribute)) + { + if (_typeHierarchyDataFlow) + { + _logger.LogWarning(origin, DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode, + ((TypeOrigin)memberWithRequirements).GetDisplayName(), + entity.GetDisplayName(), + MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage(requiresAttribute.Value)), + MessageFormat.FormatRequiresAttributeUrlArg(DiagnosticUtilities.GetRequiresAttributeUrl(requiresAttribute.Value))); + } + } + + if (!Annotations.ShouldWarnWhenAccessedForReflection(entity)) + return; + if (_typeHierarchyDataFlow) { // Don't check whether the current scope is a RUC type or RUC method because these warnings @@ -180,7 +220,7 @@ void WarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, Or } else { - if (!ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, RequiresUnreferencedCodeAttribute)) + if (!_logger.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute)) { if (entity is FieldDesc) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index 18c5b082f60fe..6eecae4df64b8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -7,12 +7,13 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Reflection.Metadata; using ILCompiler.Logging; using ILLink.Shared; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; - +using ILLink.Shared.TypeSystemProxy; using Internal.IL; using Internal.TypeSystem; @@ -28,22 +29,21 @@ namespace ILCompiler.Dataflow { - class ReflectionMethodBodyScanner : MethodBodyScanner + sealed class ReflectionMethodBodyScanner : MethodBodyScanner { - private readonly FlowAnnotations _annotations; private readonly Logger _logger; private readonly NodeFactory _factory; - private readonly ReflectionMarker _reflectionMarker; - private const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); - private const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute); - private const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute); + private ReflectionMarker _reflectionMarker; + private readonly TrimAnalysisPatternStore TrimAnalysisPatterns; + + private MessageOrigin _origin; public static bool RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations flowAnnotations, MethodDesc methodDefinition) { return Intrinsics.GetIntrinsicIdForMethod(methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel || flowAnnotations.RequiresDataflowAnalysis(methodDefinition) || - methodDefinition.DoesMethodRequire(RequiresUnreferencedCodeAttribute, out _) || - methodDefinition.DoesMethodRequire(RequiresDynamicCodeAttribute, out _) || + methodDefinition.DoesMethodRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _) || + methodDefinition.DoesMethodRequire(DiagnosticUtilities.RequiresDynamicCodeAttribute, out _) || methodDefinition.IsPInvoke; } @@ -56,273 +56,80 @@ public static bool RequiresReflectionMethodBodyScannerForMethodBody(FlowAnnotati public static bool RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations flowAnnotations, FieldDesc fieldDefinition) { return flowAnnotations.RequiresDataflowAnalysis(fieldDefinition) || - fieldDefinition.DoesFieldRequire(RequiresUnreferencedCodeAttribute, out _) || - fieldDefinition.DoesFieldRequire(RequiresDynamicCodeAttribute, out _); + fieldDefinition.DoesFieldRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _) || + fieldDefinition.DoesFieldRequire(DiagnosticUtilities.RequiresDynamicCodeAttribute, out _); } - void CheckAndReportRequires(TypeSystemEntity calledMember, in MessageOrigin origin, string requiresAttributeName) + internal static void CheckAndReportRequires(in DiagnosticContext diagnosticContext, TypeSystemEntity calledMember, string requiresAttributeName) { - // If the caller of a method is already marked with `Requires` a new warning should not - // be produced for the callee. - if (ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, requiresAttributeName)) - return; - if (!calledMember.DoesMemberRequire(requiresAttributeName, out var requiresAttribute)) return; DiagnosticId diagnosticId = requiresAttributeName switch { - RequiresUnreferencedCodeAttribute => DiagnosticId.RequiresUnreferencedCode, - RequiresDynamicCodeAttribute => DiagnosticId.RequiresDynamicCode, - RequiresAssemblyFilesAttribute => DiagnosticId.RequiresAssemblyFiles, + DiagnosticUtilities.RequiresUnreferencedCodeAttribute => DiagnosticId.RequiresUnreferencedCode, + DiagnosticUtilities.RequiresDynamicCodeAttribute => DiagnosticId.RequiresDynamicCode, + DiagnosticUtilities.RequiresAssemblyFilesAttribute => DiagnosticId.RequiresAssemblyFiles, _ => throw new NotImplementedException($"{requiresAttributeName} is not a valid supported Requires attribute"), }; - ReportRequires(calledMember.GetDisplayName(), origin, diagnosticId, requiresAttribute.Value); - } - - internal static bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originMember, string requiresAttribute) - { - // Check if the current scope method has Requires on it - // since that attribute automatically suppresses all trim analysis warnings. - // Check both the immediate origin method as well as suppression context method - // since that will be different for compiler generated code. - if (originMember == null) - return false; - - if (originMember is not MethodDesc method) - return false; - - if (method.IsInRequiresScope(requiresAttribute)) - return true; - - MethodDesc userMethod = ILCompiler.Logging.CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method); - if (userMethod != null && - userMethod.IsInRequiresScope(requiresAttribute)) - return true; - - return false; - } - - void ReportRequires(string displayName, in MessageOrigin currentOrigin, DiagnosticId diagnosticId, CustomAttributeValue requiresAttribute) - { - string arg1 = MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage((CustomAttributeValue)requiresAttribute)); - string arg2 = MessageFormat.FormatRequiresAttributeUrlArg(DiagnosticUtilities.GetRequiresAttributeUrl((CustomAttributeValue)requiresAttribute)); + string arg1 = MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage(requiresAttribute.Value)); + string arg2 = MessageFormat.FormatRequiresAttributeUrlArg(DiagnosticUtilities.GetRequiresAttributeUrl(requiresAttribute.Value)); - _logger.LogWarning(currentOrigin, diagnosticId, displayName, arg1, arg2); + diagnosticContext.AddDiagnostic(diagnosticId, calledMember.GetDisplayName(), arg1, arg2); } - private enum ScanningPurpose + private ReflectionMethodBodyScanner(NodeFactory factory, FlowAnnotations annotations, Logger logger, MessageOrigin origin) + : base(annotations) { - Default, - GetTypeDataflow, - } - - private ScanningPurpose _purpose; - - private ReflectionMethodBodyScanner(NodeFactory factory, FlowAnnotations annotations, Logger logger, ScanningPurpose purpose = ScanningPurpose.Default) - { - _annotations = annotations; _logger = logger; _factory = factory; - _purpose = purpose; - _reflectionMarker = new ReflectionMarker(logger, factory, annotations, purpose == ScanningPurpose.GetTypeDataflow); + _origin = origin; + _reflectionMarker = new ReflectionMarker(logger, factory, annotations, typeHierarchyDataFlow: false, enabled: false); + TrimAnalysisPatterns = new TrimAnalysisPatternStore(MultiValueLattice, logger); } - public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, FlowAnnotations annotations, Logger logger, MethodIL methodBody) + public override void InterproceduralScan(MethodIL methodBody) { - var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger); + base.InterproceduralScan(methodBody); - Debug.Assert(methodBody.GetMethodILDefinition() == methodBody); - if (methodBody.OwningMethod.HasInstantiation || methodBody.OwningMethod.OwningType.HasInstantiation) - { - // We instantiate the body over the generic parameters. - // - // This will transform references like "call Foo.Method(!0 arg)" into - // "call Foo.Method(T arg)". We do this to avoid getting confused about what - // context the generic variables refer to - in the above example, we would see - // two !0's - one refers to the generic parameter of the type that owns the method with - // the call, but the other one (in the signature of "Method") actually refers to - // the generic parameter of Foo. - // - // If we don't do this translation, retrieving the signature of the called method - // would attempt to do bogus substitutions. - // - // By doing the following transformation, we ensure we don't see the generic variables - // that need to be bound to the context of the currently analyzed method. - methodBody = new InstantiatedMethodIL(methodBody.OwningMethod, methodBody); - } + // Replace the reflection marker with one which actually marks + _reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); + TrimAnalysisPatterns.MarkAndProduceDiagnostics(_reflectionMarker); + } - scanner.Scan(methodBody); + protected override void Scan(MethodIL methodBody, ref InterproceduralState interproceduralState) + { + _origin = new MessageOrigin(methodBody.OwningMethod); + base.Scan(methodBody, ref interproceduralState); if (!methodBody.OwningMethod.Signature.ReturnType.IsVoid) { var method = methodBody.OwningMethod; - var methodReturnValue = scanner._annotations.GetMethodReturnValue(method); + var methodReturnValue = _annotations.GetMethodReturnValue(method); if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) - { - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method), !ShouldSuppressAnalysisWarningsForRequires(method, RequiresUnreferencedCodeAttribute), scanner._logger); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, scanner.ReturnValue, methodReturnValue, new MethodReturnOrigin(method)); - } + HandleAssignmentPattern(_origin, ReturnValue, methodReturnValue, new MethodOrigin(method)); } - - return scanner._reflectionMarker.Dependencies; } - public static DependencyList? ProcessAttributeDataflow(NodeFactory factory, FlowAnnotations annotations, Logger logger, MethodDesc method, CustomAttributeValue arguments) + public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, FlowAnnotations annotations, Logger logger, MethodIL methodBody) { - DependencyList? result = null; + var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger, new MessageOrigin(methodBody.OwningMethod)); - // First do the dataflow for the constructor parameters if necessary. - if (annotations.RequiresDataflowAnalysis(method)) - { - for (int i = 0; i < method.Signature.Length; i++) - { - var parameterValue = annotations.GetMethodParameterValue(method, i); - if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) - { - MultiValue value = GetValueForCustomAttributeArgument(arguments.FixedArguments[i].Value); - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method), diagnosticsEnabled: true, logger); - var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, value, parameterValue, parameterValue.ParameterOrigin); - AddResults(scanner._reflectionMarker.Dependencies); - } - } - } - - // Named arguments next - TypeDesc attributeType = method.OwningType; - foreach (var namedArgument in arguments.NamedArguments) - { - MultiValue targetValues = new(); - Origin? targetContext = null; - if (namedArgument.Kind == CustomAttributeNamedArgumentKind.Field) - { - FieldDesc field = attributeType.GetField(namedArgument.Name); - if (field != null) - { - targetValues = GetFieldValue(field, annotations); - targetContext = new FieldOrigin(field); - } - } - else - { - Debug.Assert(namedArgument.Kind == CustomAttributeNamedArgumentKind.Property); - PropertyPseudoDesc property = ((MetadataType)attributeType).GetProperty(namedArgument.Name, null); - MethodDesc setter = property.SetMethod; - if (setter != null && setter.Signature.Length > 0 && !setter.Signature.IsStatic) - { - targetValues = annotations.GetMethodParameterValue(setter, 0); - targetContext = new ParameterOrigin(setter, 1); - } - } + scanner.InterproceduralScan(methodBody); - foreach (var targetValueCandidate in targetValues) - { - if (targetValueCandidate is not ValueWithDynamicallyAccessedMembers targetValue || - targetValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None) - continue; - - MultiValue valueNode = GetValueForCustomAttributeArgument(namedArgument.Value); - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method), diagnosticsEnabled: true, logger); - var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, valueNode, targetValue, targetContext!); - AddResults(scanner._reflectionMarker.Dependencies); - } - } - - return result; - - void AddResults(DependencyList dependencies) - { - if (result == null) - { - result = dependencies; - } - else - { - result.AddRange(dependencies); - } - } + return scanner._reflectionMarker.Dependencies; } public static DependencyList ProcessTypeGetTypeDataflow(NodeFactory factory, FlowAnnotations flowAnnotations, Logger logger, MetadataType type) { DynamicallyAccessedMemberTypes annotation = flowAnnotations.GetTypeAnnotation(type); Debug.Assert(annotation != DynamicallyAccessedMemberTypes.None); - var reflectionMarker = new ReflectionMarker(logger, factory, flowAnnotations, true); + var reflectionMarker = new ReflectionMarker(logger, factory, flowAnnotations, typeHierarchyDataFlow: true, enabled: true); reflectionMarker.MarkTypeForDynamicallyAccessedMembers(new MessageOrigin(type), type, annotation, new TypeOrigin(type)); return reflectionMarker.Dependencies; } - static MultiValue GetValueForCustomAttributeArgument(object? argument) - { - SingleValue? result = null; - if (argument is TypeDesc td) - { - result = new SystemTypeValue(td); - } - else if (argument is string str) - { - result = new KnownStringValue(str); - } - else - { - Debug.Assert(argument is null); - result = NullValue.Instance; - } - - Debug.Assert(result != null); - return result; - } - - public static DependencyList ProcessGenericArgumentDataFlow(NodeFactory factory, FlowAnnotations flowAnnotations, Logger logger, GenericParameterDesc genericParameter, TypeDesc genericArgument, TypeSystemEntity source) - { - var scanner = new ReflectionMethodBodyScanner(factory, flowAnnotations, logger); - - var genericParameterValue = flowAnnotations.GetGenericParameterValue(genericParameter); - Debug.Assert(genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); - - MultiValue genericArgumentValue = scanner.GetTypeValueNodeFromGenericArgument(genericArgument); - - bool enableDiagnostics = ShouldSuppressAnalysisWarningsForRequires(source, RequiresUnreferencedCodeAttribute); - var diagnosticContext = new DiagnosticContext(new MessageOrigin(source), diagnosticsEnabled: enableDiagnostics, logger); - var origin = new GenericParameterOrigin(genericParameter); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, genericArgumentValue, genericParameterValue, origin); - - return scanner._reflectionMarker.Dependencies; - } - - MultiValue GetTypeValueNodeFromGenericArgument(TypeDesc genericArgument) - { - if (genericArgument is GenericParameterDesc inputGenericParameter) - { - return _annotations.GetGenericParameterValue(inputGenericParameter); - } - else if (genericArgument is MetadataType genericArgumentType) - { - if (genericArgumentType.IsTypeOf(WellKnownType.System_Nullable_T)) - { - var innerGenericArgument = genericArgumentType.Instantiation.Length == 1 ? genericArgumentType.Instantiation[0] : null; - switch (innerGenericArgument) - { - case GenericParameterDesc gp: - return new NullableValueWithDynamicallyAccessedMembers(genericArgumentType, - new GenericParameterValue(gp, _annotations.GetGenericParameterAnnotation(gp))); - - case TypeDesc underlyingType: - return new NullableSystemTypeValue(genericArgumentType, new SystemTypeValue(underlyingType)); - } - } - // All values except for Nullable, including Nullable<> (with no type arguments) - return new SystemTypeValue(genericArgumentType); - } - else - { - return UnknownValue.Instance; - } - } - protected override void WarnAboutInvalidILInMethod(MethodIL method, int ilOffset) { // Serves as a debug helper to make sure valid IL is not considered invalid. @@ -350,70 +157,94 @@ ValueWithDynamicallyAccessedMembers GetMethodParameterValue(MethodDesc method, i return _annotations.GetMethodParameterValue(method, parameterIndex, dynamicallyAccessedMemberTypes); } - static MultiValue GetFieldValue(FieldDesc field, FlowAnnotations annotations) + protected override MultiValue GetFieldValue(FieldDesc field) => _annotations.GetFieldValue(field); + + private void HandleStoreValueWithDynamicallyAccessedMembers(MethodIL methodBody, int offset, ValueWithDynamicallyAccessedMembers targetValue, MultiValue sourceValue, Origin memberWithRequirements) { - switch (field.Name) + // We must record all field accesses since we need to check RUC/RDC/RAF attributes on them regardless of annotations + if (targetValue.DynamicallyAccessedMemberTypes != 0 || targetValue is FieldValue) { - case "EmptyTypes" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_Type): - { - return ArrayValue.Create(0, field.OwningType); - } - case "Empty" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_String): - { - return new KnownStringValue(string.Empty); - } - - default: - { - DynamicallyAccessedMemberTypes memberTypes = annotations.GetFieldAnnotation(field); - return new FieldValue(field, memberTypes); - } + _origin = _origin.WithInstructionOffset(methodBody, offset); + HandleAssignmentPattern(_origin, sourceValue, targetValue, memberWithRequirements); } } - protected override MultiValue GetFieldValue(FieldDesc field) - { - return GetFieldValue(field, _annotations); - } - protected override void HandleStoreField(MethodIL methodBody, int offset, FieldValue field, MultiValue valueToStore) - { - if (field.DynamicallyAccessedMemberTypes != 0) - { - var diagnosticContext = new DiagnosticContext(new MessageOrigin(methodBody, offset), !ShouldSuppressAnalysisWarningsForRequires(methodBody.OwningMethod, RequiresUnreferencedCodeAttribute), _logger); - RequireDynamicallyAccessedMembers(diagnosticContext, valueToStore, field, new FieldOrigin(field.Field)); - } + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, field, valueToStore, new FieldOrigin(field.Field)); - CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresUnreferencedCodeAttribute); - CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresDynamicCodeAttribute); - } + protected override void HandleStoreParameter(MethodIL methodBody, int offset, MethodParameterValue parameter, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, parameter, valueToStore, parameter.ParameterOrigin); + + protected override void HandleStoreMethodThisParameter(MethodIL methodBody, int offset, MethodThisParameterValue thisParameter, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, thisParameter, valueToStore, new ParameterOrigin(thisParameter.Method, 0)); - protected override void HandleStoreParameter(MethodIL method, int offset, MethodParameterValue parameter, MultiValue valueToStore) + protected override void HandleStoreMethodReturnValue(MethodIL methodBody, int offset, MethodReturnValue returnValue, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, returnValue, valueToStore, new MethodOrigin(returnValue.Method)); + + public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMethod, ILOpcode operation, int offset, ValueNodeList methodParams, out MultiValue methodReturnValue) { - if (parameter.DynamicallyAccessedMemberTypes != 0) + methodReturnValue = null; + Debug.Assert(callingMethodBody.OwningMethod == _origin.MemberDefinition); + + _origin = _origin.WithInstructionOffset(callingMethodBody, offset); + + MultiValue instanceValue; + ImmutableArray arguments; + if (!calledMethod.Signature.IsStatic) + { + instanceValue = methodParams[0]; + arguments = methodParams.Skip(1).ToImmutableArray(); + } + else { - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method, offset), !ShouldSuppressAnalysisWarningsForRequires(method.OwningMethod, RequiresUnreferencedCodeAttribute), _logger); - RequireDynamicallyAccessedMembers(diagnosticContext, valueToStore, parameter, parameter.ParameterOrigin); + instanceValue = MultiValueLattice.Top; + arguments = methodParams.ToImmutableArray(); } + + TrimAnalysisPatterns.Add(new TrimAnalysisMethodCallPattern( + callingMethodBody, + operation, + offset, + calledMethod, + instanceValue, + arguments, + _origin + )); + + var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: false, _logger); + return HandleCall( + callingMethodBody, + calledMethod, + operation, + offset, + instanceValue, + arguments, + diagnosticContext, + _reflectionMarker, + out methodReturnValue); } - public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMethod, ILOpcode operation, int offset, ValueNodeList methodParams, out MultiValue methodReturnValue) + public static bool HandleCall( + MethodIL callingMethodBody, + MethodDesc calledMethod, + ILOpcode operation, + int offset, + MultiValue instanceValue, + ImmutableArray argumentValues, + DiagnosticContext diagnosticContext, + ReflectionMarker reflectionMarker, + out MultiValue methodReturnValue) { - methodReturnValue = null; - MultiValue? maybeMethodReturnValue = null; - var callingMethodDefinition = callingMethodBody.OwningMethod; - bool shouldEnableReflectionWarnings = !ShouldSuppressAnalysisWarningsForRequires(callingMethodDefinition, RequiresUnreferencedCodeAttribute); - bool shouldEnableAotWarnings = !ShouldSuppressAnalysisWarningsForRequires(callingMethodDefinition, RequiresDynamicCodeAttribute); + Debug.Assert(callingMethodDefinition == diagnosticContext.Origin.MemberDefinition); - DynamicallyAccessedMemberTypes returnValueDynamicallyAccessedMemberTypes = 0; + bool requiresDataFlowAnalysis = reflectionMarker.Annotations.RequiresDataflowAnalysis(calledMethod); + var annotatedMethodReturnValue = reflectionMarker.Annotations.GetMethodReturnValue(calledMethod); + Debug.Assert(requiresDataFlowAnalysis || annotatedMethodReturnValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None); - bool requiresDataFlowAnalysis = _annotations.RequiresDataflowAnalysis(calledMethod); - returnValueDynamicallyAccessedMemberTypes = requiresDataFlowAnalysis ? - _annotations.GetReturnParameterAnnotation(calledMethod) : 0; + MultiValue? maybeMethodReturnValue = null; - var diagnosticContext = new DiagnosticContext(new MessageOrigin(callingMethodBody, offset), shouldEnableReflectionWarnings, _logger); - var handleCallAction = new HandleCallAction(_annotations, _reflectionMarker, diagnosticContext, callingMethodDefinition, new MethodOrigin(calledMethod)); + var handleCallAction = new HandleCallAction(reflectionMarker.Annotations, reflectionMarker, diagnosticContext, callingMethodDefinition, new MethodOrigin(calledMethod)); var intrinsicId = Intrinsics.GetIntrinsicIdForMethod(calledMethod); switch (intrinsicId) @@ -463,21 +294,14 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet || appDomainCreateInstance == IntrinsicId.AppDomain_CreateInstanceFromAndUnwrap: case IntrinsicId.Assembly_CreateInstance: { - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (!calledMethod.Signature.IsStatic) - { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip(1).ToImmutableList(); - } - bool result = handleCallAction.Invoke(calledMethod, instanceValue, parameterValues, out methodReturnValue, out _); + bool result = handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, out methodReturnValue, out _); // Special case some intrinsics for AOT handling (on top of the trimming handling done in the HandleCallAction) switch (intrinsicId) { case IntrinsicId.Type_MakeGenericType: case IntrinsicId.MethodInfo_MakeGenericMethod: - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); break; } @@ -512,26 +336,18 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet } } - var origin = new MessageOrigin(callingMethodBody, offset); - CheckAndReportRequires(calledMethod, origin, RequiresUnreferencedCodeAttribute); - CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); - CheckAndReportRequires(calledMethod, origin, RequiresAssemblyFilesAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresUnreferencedCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresAssemblyFilesAttribute); - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (!calledMethod.Signature.IsStatic) - { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip(1).ToImmutableList(); - } - return handleCallAction.Invoke(calledMethod, instanceValue, parameterValues, out methodReturnValue, out _); + return handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, out methodReturnValue, out _); } case IntrinsicId.TypeDelegator_Ctor: { // This is an identity function for analysis purposes if (operation == ILOpcode.newobj) - AddReturnValue(methodParams[1]); + AddReturnValue(argumentValues[0]); } break; @@ -553,7 +369,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet // type instead). // // At least until we have shared enum code, this needs extra handling to get it right. - foreach (var value in methodParams[0]) + foreach (var value in argumentValues[0]) { if (value is SystemTypeValue systemTypeValue && !systemTypeValue.RepresentedType.Type.IsGenericDefinition @@ -561,11 +377,11 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { if (systemTypeValue.RepresentedType.Type.IsEnum) { - _reflectionMarker.Dependencies.Add(_factory.ConstructedTypeSymbol(systemTypeValue.RepresentedType.Type.MakeArrayType()), "Enum.GetValues"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.ConstructedTypeSymbol(systemTypeValue.RepresentedType.Type.MakeArrayType()), "Enum.GetValues"); } } else - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); } } break; @@ -588,7 +404,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet ? 0 : 1; // We need the data to do struct marshalling. - foreach (var value in methodParams[paramIndex]) + foreach (var value in argumentValues[paramIndex]) { if (value is SystemTypeValue systemTypeValue && !systemTypeValue.RepresentedType.Type.IsGenericDefinition @@ -596,17 +412,17 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { if (systemTypeValue.RepresentedType.Type.IsDefType) { - _reflectionMarker.Dependencies.Add(_factory.StructMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.StructMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); if (intrinsicId == IntrinsicId.Marshal_PtrToStructure && systemTypeValue.RepresentedType.Type.GetParameterlessConstructor() is MethodDesc ctorMethod - && !_factory.MetadataManager.IsReflectionBlocked(ctorMethod)) + && !reflectionMarker.Factory.MetadataManager.IsReflectionBlocked(ctorMethod)) { - _reflectionMarker.Dependencies.Add(_factory.ReflectableMethod(ctorMethod), "Marshal API"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.ReflectableMethod(ctorMethod), "Marshal API"); } } } else - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); } } break; @@ -619,7 +435,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet case IntrinsicId.Marshal_GetDelegateForFunctionPointer: { // We need the data to do delegate marshalling. - foreach (var value in methodParams[1]) + foreach (var value in argumentValues[1]) { if (value is SystemTypeValue systemTypeValue && !systemTypeValue.RepresentedType.Type.IsGenericDefinition @@ -627,11 +443,11 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { if (systemTypeValue.RepresentedType.Type.IsDelegate) { - _reflectionMarker.Dependencies.Add(_factory.DelegateMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.DelegateMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); } } else - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); } } break; @@ -643,7 +459,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet // case IntrinsicId.Object_GetType: { - foreach (var valueNode in methodParams[0]) + foreach (var valueNode in instanceValue) { // Note that valueNode can be statically typed in IL as some generic argument type. // For example: @@ -664,7 +480,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet if (staticType is null || (!staticType.IsDefType && !staticType.IsArray)) { // We don't know anything about the type GetType was called on. Track this as a usual "result of a method call without any annotations" - AddReturnValue(_annotations.GetMethodReturnValue(calledMethod)); + AddReturnValue(reflectionMarker.Annotations.GetMethodReturnValue(calledMethod)); } else if (staticType.IsSealed() || staticType.IsTypeOf("System", "Delegate")) { @@ -687,19 +503,19 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { Debug.Assert(staticType is MetadataType || staticType.IsArray); MetadataType closestMetadataType = staticType is MetadataType mdType ? - mdType : (MetadataType)_factory.TypeSystemContext.GetWellKnownType(Internal.TypeSystem.WellKnownType.Array); + mdType : (MetadataType)reflectionMarker.Factory.TypeSystemContext.GetWellKnownType(Internal.TypeSystem.WellKnownType.Array); - var annotation = _annotations.GetTypeAnnotation(staticType); + var annotation = reflectionMarker.Annotations.GetTypeAnnotation(staticType); if (annotation != default) { - _reflectionMarker.Dependencies.Add(_factory.ObjectGetTypeFlowDependencies(closestMetadataType), "GetType called on this type"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.ObjectGetTypeFlowDependencies(closestMetadataType), "GetType called on this type"); } // Return a value which is "unknown type" with annotation. For now we'll use the return value node // for the method, which means we're loosing the information about which staticType this // started with. For now we don't need it, but we can add it later on. - AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, annotation)); + AddReturnValue(reflectionMarker.Annotations.GetMethodReturnValue(calledMethod, annotation)); } } } @@ -715,16 +531,16 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet bool returnsVoid = calledMethod.Signature.ReturnType.IsVoid; methodReturnValue = maybeMethodReturnValue ?? (returnsVoid ? MultiValueLattice.Top : - _annotations.GetMethodReturnValue(calledMethod, returnValueDynamicallyAccessedMemberTypes)!); + annotatedMethodReturnValue); // Validate that the return value has the correct annotations as per the method return value annotations - if (returnValueDynamicallyAccessedMemberTypes != 0) + if (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes != 0) { foreach (var uniqueValue in methodReturnValue) { if (uniqueValue is ValueWithDynamicallyAccessedMembers methodReturnValueWithMemberTypes) { - if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag(returnValueDynamicallyAccessedMemberTypes)) + if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag(annotatedMethodReturnValue.DynamicallyAccessedMemberTypes)) throw new InvalidOperationException($"Internal linker error: processing of call from {callingMethodDefinition.GetDisplayName()} to {calledMethod.GetDisplayName()} returned value which is not correctly annotated with the expected dynamic member access kinds."); } else if (uniqueValue is SystemTypeValue) @@ -747,7 +563,7 @@ void AddReturnValue(MultiValue value) } } - bool IsComInterop(MarshalAsDescriptor? marshalInfoProvider, TypeDesc parameterType) + static bool IsComInterop(MarshalAsDescriptor? marshalInfoProvider, TypeDesc parameterType) { // This is best effort. One can likely find ways how to get COM without triggering these alarms. // AsAny marshalling of a struct with an object-typed field would be one, for example. @@ -826,10 +642,13 @@ bool IsComInterop(MarshalAsDescriptor? marshalInfoProvider, TypeDesc parameterTy return false; } - void RequireDynamicallyAccessedMembers(in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue, Origin memberWithRequirements) + void HandleAssignmentPattern( + in MessageOrigin origin, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue, + Origin memberWithRequirements) { - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(_reflectionMarker, diagnosticContext, memberWithRequirements); - requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); + TrimAnalysisPatterns.Add(new TrimAnalysisAssignmentPattern(value, targetValue, origin, memberWithRequirements)); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs index 69e03cf2d3945..0b6abdb478fae 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs @@ -27,7 +27,7 @@ partial struct RequireDynamicallyAccessedMembersAction public partial bool TryResolveTypeNameAndMark(string typeName, bool needsAssemblyName, out TypeProxy type) { - if (_reflectionMarker.TryResolveTypeNameAndMark(typeName, _diagnosticContext.Origin, needsAssemblyName, _memberWithRequirements, out TypeDesc? foundType)) + if (_reflectionMarker.TryResolveTypeNameAndMark(typeName, _diagnosticContext, needsAssemblyName, _memberWithRequirements, out TypeDesc? foundType)) { type = new(foundType); return true; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs index bfb7999e2b47e..4a63effd60802 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs @@ -90,10 +90,5 @@ public static HashSet ComputeBranchTargets(this MethodIL methodBody) } return branchTargets; } - - public static bool IsByRefOrPointer(this TypeDesc type) - { - return type.IsByRef || type.IsPointer; - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs new file mode 100644 index 0000000000000..0ceead7dcb856 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs @@ -0,0 +1,74 @@ +// 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.Diagnostics; +using ILCompiler.Logging; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TrimAnalysis; + +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly record struct TrimAnalysisAssignmentPattern + { + public MultiValue Source { init; get; } + public MultiValue Target { init; get; } + public MessageOrigin Origin { init; get; } + internal Origin MemberWithRequirements { init; get; } + + internal TrimAnalysisAssignmentPattern(MultiValue source, MultiValue target, MessageOrigin origin, Origin memberWithRequirements) + { + Source = source.Clone(); + Target = target.Clone(); + Origin = origin; + MemberWithRequirements = memberWithRequirements; + } + + public TrimAnalysisAssignmentPattern Merge(ValueSetLattice lattice, TrimAnalysisAssignmentPattern other) + { + Debug.Assert(Origin == other.Origin); + + return new TrimAnalysisAssignmentPattern( + lattice.Meet(Source, other.Source), + lattice.Meet(Target, other.Target), + Origin, + MemberWithRequirements); + } + + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) + { + var diagnosticContext = new DiagnosticContext( + Origin, + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), + logger); + + foreach (var sourceValue in Source) + { + foreach (var targetValue in Target) + { + if (targetValue is FieldValue fieldValue) + { + // Once this is removed, please also cleanup ReflectionMethodBodyScanner.HandleStoreValueWithDynamicallyAccessedMembers + // which has to special case FieldValue right now, should not be needed after removal of this + ReflectionMethodBodyScanner.CheckAndReportRequires(diagnosticContext, fieldValue.Field, DiagnosticUtilities.RequiresUnreferencedCodeAttribute); + ReflectionMethodBodyScanner.CheckAndReportRequires(diagnosticContext, fieldValue.Field, DiagnosticUtilities.RequiresDynamicCodeAttribute); + // ?? Should this be enabled (was not so far) + //ReflectionMethodBodyScanner.CheckAndReportRequires(diagnosticContext, fieldValue.Field, DiagnosticUtilities.RequiresAssemblyFilesAttribute); + } + + if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers) + throw new NotImplementedException(); + + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, MemberWithRequirements); + requireDynamicallyAccessedMembersAction.Invoke(sourceValue, targetWithDynamicallyAccessedMembers); + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs new file mode 100644 index 0000000000000..d58b9684280fe --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Diagnostics; +using ILCompiler.Logging; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TrimAnalysis; +using Internal.IL; +using Internal.TypeSystem; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly record struct TrimAnalysisMethodCallPattern + { + public readonly MethodIL MethodBody; + public readonly ILOpcode Operation; + public readonly int Offset; + public readonly MethodDesc CalledMethod; + public readonly MultiValue Instance; + public readonly ImmutableArray Arguments; + public readonly MessageOrigin Origin; + + public TrimAnalysisMethodCallPattern( + MethodIL methodBody, + ILOpcode operation, + int offset, + MethodDesc calledMethod, + MultiValue instance, + ImmutableArray arguments, + MessageOrigin origin) + { + Debug.Assert(origin.MemberDefinition is MethodDesc); + MethodBody = methodBody; + Operation = operation; + Offset = offset; + CalledMethod = calledMethod; + Instance = instance.Clone(); + if (arguments.IsEmpty) + { + Arguments = ImmutableArray.Empty; + } + else + { + var builder = ImmutableArray.CreateBuilder(); + foreach (var argument in arguments) + builder.Add(argument.Clone()); + Arguments = builder.ToImmutableArray(); + } + Origin = origin; + } + + public TrimAnalysisMethodCallPattern Merge(ValueSetLattice lattice, TrimAnalysisMethodCallPattern other) + { + Debug.Assert(MethodBody.OwningMethod == other.MethodBody.OwningMethod); + Debug.Assert(Operation == other.Operation); + Debug.Assert(Offset == other.Offset); + Debug.Assert(Origin == other.Origin); + Debug.Assert(CalledMethod == other.CalledMethod); + Debug.Assert(Arguments.Length == other.Arguments.Length); + + var argumentsBuilder = ImmutableArray.CreateBuilder(); + for (int i = 0; i < Arguments.Length; i++) + argumentsBuilder.Add(lattice.Meet(Arguments[i], other.Arguments[i])); + + return new TrimAnalysisMethodCallPattern( + MethodBody, + Operation, + Offset, + CalledMethod, + lattice.Meet(Instance, other.Instance), + argumentsBuilder.ToImmutable(), + Origin); + } + + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) + { + var diagnosticContext = new DiagnosticContext( + Origin, + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), + logger); + ReflectionMethodBodyScanner.HandleCall(MethodBody, CalledMethod, Operation, Offset, Instance, Arguments, + diagnosticContext, + reflectionMarker, + out MultiValue _); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs new file mode 100644 index 0000000000000..e728a5599ff85 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using ILCompiler.Logging; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TrimAnalysis; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly struct TrimAnalysisPatternStore + { + readonly Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> AssignmentPatterns; + readonly Dictionary MethodCallPatterns; + readonly ValueSetLattice Lattice; + readonly Logger _logger; + + public TrimAnalysisPatternStore(ValueSetLattice lattice, Logger logger) + { + AssignmentPatterns = new Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern>(); + MethodCallPatterns = new Dictionary(); + Lattice = lattice; + _logger = logger; + } + + public void Add(TrimAnalysisAssignmentPattern pattern) + { + // In the linker, each pattern should have a unique origin (which has ILOffset) + // but we don't track the correct ILOffset for return instructions. + // https://github.com/dotnet/linker/issues/2778 + // For now, work around it with a separate bit. + bool isReturnValue = pattern.Target.AsSingleValue() is MethodReturnValue; + + if (!AssignmentPatterns.TryGetValue((pattern.Origin, isReturnValue), out var existingPattern)) + { + AssignmentPatterns.Add((pattern.Origin, isReturnValue), pattern); + return; + } + + AssignmentPatterns[(pattern.Origin, isReturnValue)] = pattern.Merge(Lattice, existingPattern); + } + + public void Add(TrimAnalysisMethodCallPattern pattern) + { + if (!MethodCallPatterns.TryGetValue(pattern.Origin, out var existingPattern)) + { + MethodCallPatterns.Add(pattern.Origin, pattern); + return; + } + + MethodCallPatterns[pattern.Origin] = pattern.Merge(Lattice, existingPattern); + } + + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker) + { + foreach (var pattern in AssignmentPatterns.Values) + pattern.MarkAndProduceDiagnostics(reflectionMarker, _logger); + + foreach (var pattern in MethodCallPatterns.Values) + pattern.MarkAndProduceDiagnostics(reflectionMarker, _logger); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs index 9a8254de64c3d..8e22a0c9fc96f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; - +using ILLink.Shared; using MultiValue = ILLink.Shared.DataFlow.ValueSet; #nullable enable @@ -51,7 +51,7 @@ public override bool Equals(object? other) } } - public struct ValueBasicBlockPair + public struct ValueBasicBlockPair : IEquatable { public ValueBasicBlockPair(MultiValue value, int basicBlockIndex) { @@ -61,5 +61,11 @@ public ValueBasicBlockPair(MultiValue value, int basicBlockIndex) public MultiValue Value { get; } public int BasicBlockIndex { get; } + + public bool Equals(ValueBasicBlockPair other) => Value.Equals(other.Value) && BasicBlockIndex.Equals(other.BasicBlockIndex); + + public override bool Equals(object? obj) => obj is ValueBasicBlockPair other && Equals(other); + + public override int GetHashCode() => HashUtils.Combine(Value.GetHashCode(), BasicBlockIndex); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs index bde3bc13eabb4..2935553dba001 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs @@ -9,6 +9,7 @@ using Internal.Runtime; using Internal.IL; +using ILCompiler.Dataflow; using ILCompiler.DependencyAnalysisFramework; namespace ILCompiler.DependencyAnalysis @@ -23,6 +24,7 @@ public class DataflowAnalyzedMethodNode : DependencyNodeCore public DataflowAnalyzedMethodNode(MethodIL methodIL) { Debug.Assert(methodIL.OwningMethod.IsTypicalMethodDefinition); + Debug.Assert(!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodIL.OwningMethod)); _methodIL = methodIL; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 07a9e0842570b..0482731b0432b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -3,23 +3,27 @@ using System; using System.Collections.Generic; -using System.Reflection.Metadata; +using System.Diagnostics; using System.IO; +using System.Reflection.Metadata; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; +using ILCompiler.Dataflow; using ILCompiler.Logging; using ILLink.Shared; using ILSequencePoint = Internal.IL.ILSequencePoint; using MethodIL = Internal.IL.MethodIL; +using Internal.IL; namespace ILCompiler { public class Logger { private readonly ILogWriter _logWriter; + private readonly CompilerGeneratedState _compilerGeneratedState; private readonly HashSet _suppressedWarnings; @@ -29,13 +33,21 @@ public class Logger private readonly HashSet _trimWarnedAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _aotWarnedAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); - public static Logger Null = new Logger(new TextLogWriter(TextWriter.Null), false); + public static Logger Null = new Logger(new TextLogWriter(TextWriter.Null), null, false); public bool IsVerbose { get; } - public Logger(ILogWriter writer, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) + public Logger( + ILogWriter writer, + ILProvider ilProvider, + bool isVerbose, + IEnumerable suppressedWarnings, + bool singleWarn, + IEnumerable singleWarnEnabledModules, + IEnumerable singleWarnDisabledModules) { _logWriter = writer; + _compilerGeneratedState = ilProvider == null ? null : new CompilerGeneratedState(ilProvider, this); IsVerbose = isVerbose; _suppressedWarnings = new HashSet(suppressedWarnings); _isSingleWarn = singleWarn; @@ -43,18 +55,18 @@ public Logger(ILogWriter writer, bool isVerbose, IEnumerable suppressedWarn _singleWarnDisabledAssemblies = new HashSet(singleWarnDisabledModules, StringComparer.OrdinalIgnoreCase); } - public Logger(TextWriter writer, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) - : this(new TextLogWriter(writer), isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) + : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) { } - public Logger(ILogWriter writer, bool isVerbose) - : this(writer, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) + public Logger(ILogWriter writer, ILProvider ilProvider, bool isVerbose) + : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) { } - public Logger(TextWriter writer, bool isVerbose) - : this(new TextLogWriter(writer), isVerbose) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose) + : this(new TextLogWriter(writer), ilProvider, isVerbose) { } @@ -139,20 +151,60 @@ internal bool IsWarningSuppressed(int code, MessageOrigin origin) if (_suppressedWarnings.Contains(code)) return true; - IEnumerable> suppressions = null; - // TODO: Suppressions with different scopes - if (origin.MemberDefinition is TypeDesc type) + TypeSystemEntity member = origin.MemberDefinition; + if (IsSuppressed(code, member)) + return true; + + MethodDesc owningMethod; + if (_compilerGeneratedState != null) + { + while (_compilerGeneratedState?.TryGetOwningMethodForCompilerGeneratedMember(member, out owningMethod) == true) + { + Debug.Assert(owningMethod != member); + if (IsSuppressed(code, owningMethod)) + return true; + member = owningMethod; + } + } + + return false; + } + + bool IsSuppressed(int id, TypeSystemEntity warningOrigin) + { + TypeSystemEntity warningOriginMember = warningOrigin; + while (warningOriginMember != null) + { + if (IsSuppressedOnElement(id, warningOriginMember)) + return true; + + warningOriginMember = warningOriginMember.GetOwningType(); + } + + // TODO: Assembly-level suppressions + + return false; + } + + bool IsSuppressedOnElement(int id, TypeSystemEntity provider) + { + if (provider == null) + return false; + + // TODO: Assembly-level suppressions + + IEnumerable> suppressions = null; + + if (provider is TypeDesc type) { var ecmaType = type.GetTypeDefinition() as EcmaType; suppressions = ecmaType?.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "UnconditionalSuppressMessageAttribute"); } - if (origin.MemberDefinition is MethodDesc method) + if (provider is MethodDesc method) { - method = CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method) ?? method; - var ecmaMethod = method.GetTypicalMethodDefinition() as EcmaMethod; suppressions = ecmaMethod?.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "UnconditionalSuppressMessageAttribute"); } @@ -171,7 +223,7 @@ internal bool IsWarningSuppressed(int code, MessageOrigin origin) continue; } - if (code == suppressedCode) + if (id == suppressedCode) { return true; } @@ -234,5 +286,30 @@ private static string GetModuleFileName(ModuleDesc module) } return assemblyName; } + + internal bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originMember, string requiresAttribute) + { + // Check if the current scope method has Requires on it + // since that attribute automatically suppresses all trim analysis warnings. + // Check both the immediate origin method as well as suppression context method + // since that will be different for compiler generated code. + if (originMember is MethodDesc method && + method.IsInRequiresScope(requiresAttribute)) + return true; + + MethodDesc owningMethod; + if (_compilerGeneratedState != null) + { + while (_compilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember(originMember, out owningMethod)) + { + Debug.Assert(owningMethod != originMember); + if (owningMethod.IsInRequiresScope(requiresAttribute)) + return true; + originMember = owningMethod; + } + } + + return false; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs deleted file mode 100644 index c2d861e62b2f6..0000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -using Internal.TypeSystem; -using Internal.TypeSystem.Ecma; - -namespace ILCompiler.Logging -{ - // Currently this is implemented using heuristics - public class CompilerGeneratedState - { - private static bool HasRoslynCompilerGeneratedName(DefType type) => - type.Name.Contains('<') || (type.ContainingType != null && HasRoslynCompilerGeneratedName(type.ContainingType)); - - public static MethodDesc GetUserDefinedMethodForCompilerGeneratedMember(MethodDesc sourceMember) - { - var compilerGeneratedType = sourceMember.OwningType.GetTypeDefinition() as EcmaType; - if (compilerGeneratedType == null) - return null; - - // Only handle async or iterator state machine - // So go to the declaring type and check if it's compiler generated (as a perf optimization) - if (!HasRoslynCompilerGeneratedName(compilerGeneratedType) || compilerGeneratedType.ContainingType == null) - return null; - - // Now go to its declaring type and search all methods to find the one which points to the type as its - // state machine implementation. - foreach (EcmaMethod method in compilerGeneratedType.ContainingType.GetMethods()) - { - var decodedAttribute = method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute") - ?? method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncStateMachineAttribute") - ?? method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "IteratorStateMachineAttribute"); - - if (!decodedAttribute.HasValue) - continue; - - if (decodedAttribute.Value.FixedArguments.Length != 1 - || decodedAttribute.Value.FixedArguments[0].Value is not TypeDesc stateMachineType) - continue; - - if (stateMachineType == compilerGeneratedType) - return method; - } - - return null; - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs index 04ba20872a784..b2d002cca090f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs @@ -3,22 +3,28 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection.Metadata; using System.Text; using Internal.IL; using Internal.TypeSystem; +#nullable enable + namespace ILCompiler.Logging { - public struct MessageOrigin + public struct MessageOrigin : #if false - : IComparable, IEquatable + IComparable, #endif + IEquatable { - public string FileName { get; } - public TypeSystemEntity MemberDefinition { get; } + public string? FileName { get; } + public TypeSystemEntity? MemberDefinition { get; } public int? SourceLine { get; } public int? SourceColumn { get; } + public int? ILOffset { get; } public MessageOrigin(string fileName, int? sourceLine = null, int? sourceColumn = null) { @@ -26,22 +32,24 @@ public MessageOrigin(string fileName, int? sourceLine = null, int? sourceColumn SourceLine = sourceLine; SourceColumn = sourceColumn; MemberDefinition = null; + ILOffset = null; } - public MessageOrigin(TypeSystemEntity memberDefinition, string fileName = null, int? sourceLine = 0, int? sourceColumn = 0) + public MessageOrigin(TypeSystemEntity memberDefinition, string? fileName = null, int? sourceLine = 0, int? sourceColumn = 0) { FileName = fileName; MemberDefinition = memberDefinition; SourceLine = sourceLine; SourceColumn = sourceColumn; + ILOffset = null; } - public MessageOrigin(MethodIL origin, int ilOffset) + public MessageOrigin(MethodIL methodBody, int ilOffset) { - string document = null; + string? document = null; int? lineNumber = null; - IEnumerable sequencePoints = origin.GetDebugInfo()?.GetSequencePoints(); + IEnumerable? sequencePoints = methodBody.GetDebugInfo()?.GetSequencePoints(); if (sequencePoints != null) { foreach (var sequencePoint in sequencePoints) @@ -54,12 +62,19 @@ public MessageOrigin(MethodIL origin, int ilOffset) } } FileName = document; - MemberDefinition = origin.OwningMethod; + MemberDefinition = methodBody.OwningMethod; SourceLine = lineNumber; SourceColumn = null; + ILOffset = ilOffset; + } + + public MessageOrigin WithInstructionOffset(MethodIL methodBody, int ilOffset) + { + Debug.Assert(methodBody.OwningMethod == MemberDefinition); + return new MessageOrigin(methodBody, ilOffset); } - public override string ToString() + public override string? ToString() { if (FileName == null) return null; @@ -77,30 +92,36 @@ public override string ToString() return sb.ToString(); } -#if false public bool Equals(MessageOrigin other) => (FileName, MemberDefinition, SourceLine, SourceColumn, ILOffset) == (other.FileName, other.MemberDefinition, other.SourceLine, other.SourceColumn, other.ILOffset); - public override bool Equals(object obj) => obj is MessageOrigin messageOrigin && Equals(messageOrigin); - public override int GetHashCode() => (FileName, MemberDefinition, SourceLine, SourceColumn).GetHashCode(); + public override bool Equals(object? obj) => obj is MessageOrigin messageOrigin && Equals(messageOrigin); + public override int GetHashCode() => (FileName, MemberDefinition, SourceLine, SourceColumn, ILOffset).GetHashCode(); public static bool operator ==(MessageOrigin lhs, MessageOrigin rhs) => lhs.Equals(rhs); public static bool operator !=(MessageOrigin lhs, MessageOrigin rhs) => !lhs.Equals(rhs); +#if false public int CompareTo(MessageOrigin other) { if (MemberDefinition != null && other.MemberDefinition != null) { - TypeDefinition thisTypeDef = (MemberDefinition as TypeDefinition) ?? MemberDefinition.DeclaringType; - TypeDefinition otherTypeDef = (other.MemberDefinition as TypeDefinition) ?? other.MemberDefinition.DeclaringType; - int result = (thisTypeDef?.Module?.Assembly?.Name?.Name, thisTypeDef?.Name, MemberDefinition?.Name).CompareTo - ((otherTypeDef?.Module?.Assembly?.Name?.Name, otherTypeDef?.Name, other.MemberDefinition?.Name)); - if (result != 0) - return result; - if (ILOffset != null && other.ILOffset != null) - return ILOffset.Value.CompareTo (other.ILOffset); - return ILOffset == null ? (other.ILOffset == null ? 0 : 1) : -1; + var thisMember = Provider as IMemberDefinition; + var otherMember = other.Provider as IMemberDefinition; + TypeDefinition? thisTypeDef = (Provider as TypeDefinition) ?? (Provider as IMemberDefinition)?.DeclaringType; + TypeDefinition? otherTypeDef = (other.Provider as TypeDefinition) ?? (other.Provider as IMemberDefinition)?.DeclaringType; + var thisAssembly = thisTypeDef?.Module.Assembly ?? Provider as AssemblyDefinition; + var otherAssembly = otherTypeDef?.Module.Assembly ?? other.Provider as AssemblyDefinition; + int result = (thisAssembly?.Name.Name, thisTypeDef?.Name, thisMember?.Name).CompareTo + ((otherAssembly?.Name.Name, otherTypeDef?.Name, otherMember?.Name)); + if (result != 0) + return result; + + if (ILOffset != null && other.ILOffset != null) + return ILOffset.Value.CompareTo(other.ILOffset); + + return ILOffset == null ? (other.ILOffset == null ? 0 : 1) : -1; } - else if (MemberDefinition == null && other.MemberDefinition == null) + else if (Provider == null && other.Provider == null) { if (FileName != null && other.FileName != null) { @@ -114,7 +135,7 @@ public int CompareTo(MessageOrigin other) return (FileName == null) ? 1 : -1; } - return (MemberDefinition == null) ? 1 : -1; + return (Provider == null) ? 1 : -1; } #endif } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs deleted file mode 100644 index 3c22da1f5478b..0000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using ILLink.Shared; -using Mono.Cecil; - -namespace Mono.Linker -{ - // Currently this is implemented using heuristics - public class CompilerGeneratedState - { - readonly LinkContext _context; - readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; - readonly HashSet _typesWithPopulatedCache; - - public CompilerGeneratedState (LinkContext context) - { - _context = context; - _compilerGeneratedTypeToUserCodeMethod = new Dictionary (); - _typesWithPopulatedCache = new HashSet (); - } - - static bool HasRoslynCompilerGeneratedName (TypeDefinition type) => - type.Name.Contains ('<') || (type.DeclaringType != null && HasRoslynCompilerGeneratedName (type.DeclaringType)); - - void PopulateCacheForType (TypeDefinition type) - { - // Avoid repeat scans of the same type - if (!_typesWithPopulatedCache.Add (type)) - return; - - foreach (MethodDefinition method in type.Methods) { - if (!method.HasCustomAttributes) - continue; - - 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) { - if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd (stateMachineType, method)) { - var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; - _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), stateMachineType.GetDisplayName ()); - } - } - - break; - } - } - } - } - - static TypeDefinition? GetFirstConstructorArgumentAsType (CustomAttribute attribute) - { - if (!attribute.HasConstructorArguments) - return null; - - return attribute.ConstructorArguments[0].Value as TypeDefinition; - } - - public MethodDefinition? GetUserDefinedMethodForCompilerGeneratedMember (IMemberDefinition sourceMember) - { - if (sourceMember == null) - return null; - - TypeDefinition compilerGeneratedType = (sourceMember as TypeDefinition) ?? sourceMember.DeclaringType; - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (compilerGeneratedType, out MethodDefinition? userDefinedMethod)) - return userDefinedMethod; - - // Only handle async or iterator state machine - // So go to the declaring type and check if it's compiler generated (as a perf optimization) - if (!HasRoslynCompilerGeneratedName (compilerGeneratedType) || compilerGeneratedType.DeclaringType == null) - return null; - - // Now go to its declaring type and search all methods to find the one which points to the type as its - // state machine implementation. - PopulateCacheForType (compilerGeneratedType.DeclaringType); - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (compilerGeneratedType, out userDefinedMethod)) - return userDefinedMethod; - - return null; - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index bf36d53b6f633..e366e80708f4b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -3,31 +3,29 @@ using System; using System.Collections.Generic; -using System.Reflection.PortableExecutable; -using System.Reflection.Metadata; using System.IO; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Xml; -using Internal.IL; -using Internal.TypeSystem; -using Internal.TypeSystem.Ecma; - -using ILCompiler.Metadata; +using ILCompiler.Dataflow; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.Metadata; using ILLink.Shared; -using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; -using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; -using Debug = System.Diagnostics.Debug; +using CustomAttributeHandle = System.Reflection.Metadata.CustomAttributeHandle; using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue; +using Debug = System.Diagnostics.Debug; +using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; using EcmaModule = Internal.TypeSystem.Ecma.EcmaModule; using EcmaType = Internal.TypeSystem.Ecma.EcmaType; -using CustomAttributeHandle = System.Reflection.Metadata.CustomAttributeHandle; -using CustomAttributeTypeProvider = Internal.TypeSystem.Ecma.CustomAttributeTypeProvider; -using MetadataExtensions = Internal.TypeSystem.Ecma.MetadataExtensions; -using ILCompiler.Dataflow; +using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; namespace ILCompiler { @@ -514,8 +512,7 @@ protected override void GetDependenciesDueToMethodCodePresenceInternal(ref Depen { if (FlowAnnotations.RequiresDataflowAnalysis(method)) { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodIL.GetMethodILDefinition()), "Method has annotated parameters"); + AddDataflowDependency(ref dependencies, factory, methodIL, "Method has annotated parameters"); } if ((method.HasInstantiation && !method.IsCanonicalMethod(CanonicalFormKind.Any))) @@ -631,8 +628,7 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations, writtenField)) { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodIL.GetMethodILDefinition()), "Access to interesting field"); + AddDataflowDependency(ref dependencies, factory, methodIL, "Access to interesting field"); } string reason = "Use of a field"; @@ -695,8 +691,7 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations, calledMethod)) { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodIL.GetMethodILDefinition()), "Call to interesting method"); + AddDataflowDependency(ref dependencies, factory, methodIL, "Call to interesting method"); } } @@ -705,7 +700,7 @@ public override DependencyList GetDependenciesForCustomAttribute(NodeFactory fac bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection) { - return Dataflow.ReflectionMethodBodyScanner.ProcessAttributeDataflow(factory, FlowAnnotations, Logger, attributeCtor, decodedValue); + return (new AttributeDataFlow(Logger, factory, FlowAnnotations, new Logging.MessageOrigin(attributeCtor))).ProcessAttributeDataflow(attributeCtor, decodedValue); } return null; @@ -720,7 +715,7 @@ private void GetFlowDependenciesForInstantiation(ref DependencyList dependencies { try { - var deps = ILCompiler.Dataflow.ReflectionMethodBodyScanner.ProcessGenericArgumentDataFlow(factory, FlowAnnotations, Logger, genericParameter, instantiation[i], source); + var deps = (new ILCompiler.Dataflow.GenericArgumentDataFlow(Logger, factory, FlowAnnotations, new Logging.MessageOrigin(source))).ProcessGenericArgumentDataFlow(genericParameter, instantiation[i]); if (deps.Count > 0) { if (dependencies == null) @@ -909,6 +904,22 @@ public MetadataManager ToAnalysisBasedMetadataManager() reflectableFields.ToEnumerable(), _customAttributesWithMetadata, rootedCctorContexts); } + private void AddDataflowDependency(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, string reason) + { + MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); + if (FlowAnnotations.CompilerGeneratedState.TryGetUserMethodForCompilerGeneratedMember(methodILDefinition.OwningMethod, out var userMethod)) + { + Debug.Assert(userMethod != methodILDefinition.OwningMethod); + + // It is possible that this will try to add the DatadlowAnalyzedMethod node multiple times for the same method + // but that's OK since the node factory will only add actually one node. + methodILDefinition = FlowAnnotations.ILProvider.GetMethodIL(userMethod); + } + + dependencies = dependencies ?? new DependencyList(); + dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), reason); + } + private struct ReflectableEntityBuilder { private Dictionary _dictionary; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 2909446593dbb..ad9b7e0ed3e77 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -20,6 +20,11 @@ + + + all + runtime + @@ -322,26 +327,40 @@ + + + + + + + + + + - + + + + + @@ -561,7 +580,6 @@ - diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 1692bce7376c9..8e00759aaa857 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -17,6 +17,7 @@ using Debug = System.Diagnostics.Debug; using InstructionSet = Internal.JitInterface.InstructionSet; +using ILCompiler.Dataflow; namespace ILCompiler { @@ -755,7 +756,8 @@ static string ILLinkify(string rootedAssembly) } ilProvider = new FeatureSwitchManager(ilProvider, featureSwitches); - var logger = new Logger(Console.Out, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); + var logger = new Logger(Console.Out, ilProvider, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger); var stackTracePolicy = _emitStackTraceData ? (StackTraceEmissionPolicy)new EcmaMethodStackTraceEmissionPolicy() : new NoStackTraceEmissionPolicy(); @@ -789,7 +791,7 @@ static string ILLinkify(string rootedAssembly) DynamicInvokeThunkGenerationPolicy invokeThunkGenerationPolicy = new DefaultDynamicInvokeThunkGenerationPolicy(); - var flowAnnotations = new ILLink.Shared.TrimAnalysis.FlowAnnotations(logger, ilProvider); + var flowAnnotations = new ILLink.Shared.TrimAnalysis.FlowAnnotations(logger, ilProvider, compilerGeneratedState); MetadataManager metadataManager = new UsageBasedMetadataManager( compilationGroup, diff --git a/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs b/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs index 679f70ca621ea..1d8221463ace3 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs @@ -27,6 +27,8 @@ public struct DefaultValueDictionary : IEquatable (Dictionary, DefaultValue) = (null, defaultValue); + private DefaultValueDictionary (TValue defaultValue, Dictionary dictionary) => (Dictionary, DefaultValue) = (dictionary, defaultValue); + public DefaultValueDictionary (DefaultValueDictionary other) { Dictionary = other.Dictionary == null ? null : new Dictionary (other.Dictionary); @@ -65,6 +67,10 @@ public bool Equals (DefaultValueDictionary other) return true; } + public override bool Equals (object? obj) => obj is DefaultValueDictionary other && Equals (other); + + public int Count => Dictionary?.Count ?? 0; + public IEnumerator> GetEnumerator () { return Dictionary?.GetEnumerator () ?? Enumerable.Empty> ().GetEnumerator (); @@ -84,5 +90,24 @@ public override string ToString () sb.Append (Environment.NewLine).Append ("}"); return sb.ToString (); } + + public DefaultValueDictionary Clone () + { + var defaultValue = DefaultValue is IDeepCopyValue copyDefaultValue ? copyDefaultValue.DeepCopy () : DefaultValue; + if (Dictionary == null) + return new DefaultValueDictionary (defaultValue); + + var dict = new Dictionary (); + foreach (var kvp in Dictionary) { + var key = kvp.Key; + var value = kvp.Value; + dict.Add (key, value is IDeepCopyValue copyValue ? copyValue.DeepCopy () : value); + } + return new DefaultValueDictionary (defaultValue, dict); + } + + // Prevent warning CS0659 https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0659. + // This type should never be used as a dictionary key. + public override int GetHashCode () => throw new NotImplementedException (); } } diff --git a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs index ac417f4ee58e6..c7f5028f2d9d7 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs @@ -4,6 +4,8 @@ // This is needed due to NativeAOT which doesn't enable nullable globally yet #nullable enable +using System; + namespace ILLink.Shared { public enum DiagnosticId @@ -55,6 +57,7 @@ public enum DiagnosticId CouldNotResolveCustomAttributeTypeValue = 1044, UnexpectedAttributeArgumentType = 1045, InvalidMetadataOption = 1046, + InvalidDependenciesFileFormat = 1047, // Linker diagnostic ids. TypeHasNoFieldsToPreserve = 2001, @@ -179,6 +182,9 @@ public enum DiagnosticId DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithDynamicallyAccessedMembers = 2115, RequiresUnreferencedCodeOnStaticConstructor = 2116, MethodsAreAssociatedWithUserMethod = 2117, + CompilerGeneratedMemberAccessedViaReflection = 2118, + DynamicallyAccessedMembersOnTypeReferencesCompilerGeneratedMember = 2119, + DynamicallyAccessedMembersOnTypeReferencesCompilerGeneratedMemberOnBase = 2120, // Single-file diagnostic ids. AvoidAssemblyLocationInSingleFile = 3000, @@ -215,10 +221,18 @@ public static class DiagnosticIdExtensions 2103 => MessageSubCategory.TrimAnalysis, 2106 => MessageSubCategory.TrimAnalysis, 2107 => MessageSubCategory.TrimAnalysis, - >= 2109 and <= 2116 => MessageSubCategory.TrimAnalysis, + >= 2109 and <= 2120 => MessageSubCategory.TrimAnalysis, >= 3050 and <= 3052 => MessageSubCategory.AotAnalysis, >= 3054 and <= 3055 => MessageSubCategory.AotAnalysis, _ => MessageSubCategory.None, }; + + public static string GetDiagnosticCategory (this DiagnosticId diagnosticId) => + (int) diagnosticId switch { + > 2000 and < 3000 => DiagnosticCategory.Trimming, + >= 3000 and < 3050 => DiagnosticCategory.SingleFile, + >= 3050 and <= 6000 => DiagnosticCategory.AOT, + _ => throw new ArgumentException ($"The provided diagnostic id '{diagnosticId}' does not fall into the range of supported warning codes 2001 to 6000 (inclusive).") + }; } } diff --git a/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems b/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems index 66dd85465ebfd..8b16e377892a2 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems +++ b/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems @@ -23,4 +23,4 @@ Designer - \ No newline at end of file + diff --git a/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx b/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx index 61e172189bd5c..5f85cc3f450f2 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx +++ b/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx @@ -38,7 +38,7 @@ The mimetype is used for serialized objects, and tells the ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: + extensible. For a given mimetype the value must be set accordingly Note - application/x-microsoft.net.object.binary.base64 is the format that the ResXResourceWriter will generate, however the reader can @@ -1071,6 +1071,24 @@ Methods '{0}' and '{1}' are both associated with lambda or local function '{2}'. This is currently unsupported and may lead to incorrectly reported warnings. + + Compiler-generated member is accessed via reflection. Trimmer can't guarantee availability of the requirements of the member. + + + Compiler-generated member '{0}' is accessed via reflection. Trimmer can't guarantee availability of the requirements of the member. + + + 'DynamicallyAccessedMemberAttribute' on a type or one of its base types references a compiler-generated member. + + + 'DynamicallyAccessedMemberAttribute' on '{0}' or one of its base types references compiler-generated member '{1}'. + + + 'DynamicallyAccessedMemberAttribute' on a type or one of its base types references a compiler-generated member. + + + 'DynamicallyAccessedMemberAttribute' on '{0}' or one of its base types references compiler-generated member '{1}'. + Avoid accessing Assembly file path when publishing as a single file @@ -1167,4 +1185,10 @@ Unrecognized internal attribute '{0}' + + The only allowed file types are Xml or Dgml. + + + Unrecognized dependencies file type. + \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs index 06353c222852e..95c984d7fda51 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs @@ -8,6 +8,17 @@ namespace ILLink.Shared.TrimAnalysis { readonly partial struct DiagnosticContext { + /// + /// The diagnostic context may be entirely disabled or some kinds of warnings may be suppressed. + /// The suppressions are determined based on the . + /// Typically the suppressions will be based on diagnostic category : + /// - Trimmer warnings (suppressed by RequiresUnreferencedCodeAttribute) + /// - AOT warnings (suppressed by RequiresDynamicCodeAttribute) + /// - Single-file warnings (suppressed by RequiresAssemblyFilesAttribute) + /// Note that not all categories are used/supported by all tools, for example the ILLink only handles trimmer warnings and ignores the rest. + /// + /// The diagnostic ID, this will be used to determine the category of diagnostic (trimmer, AOT, single-file) + /// The arguments for diagnostic message. public partial void AddDiagnostic (DiagnosticId id, params string[] args); } } diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs index 942310a0090a5..ded6275f216fe 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs @@ -1154,6 +1154,8 @@ GenericParameterValue genericParam _requireDynamicallyAccessedMembersAction.Invoke (instanceValue, _annotations.GetMethodThisParameterValue (calledMethod)); } for (int argumentIndex = 0; argumentIndex < argumentValues.Count; argumentIndex++) { + if (calledMethod.ParameterReferenceKind (argumentIndex) == ReferenceKind.Out) + continue; _requireDynamicallyAccessedMembersAction.Invoke (argumentValues[argumentIndex], _annotations.GetMethodParameterValue (calledMethod, argumentIndex)); } } @@ -1162,6 +1164,10 @@ GenericParameterValue genericParam // Disable warnings for all unimplemented intrinsics. Some intrinsic methods have annotations, but analyzing them // would produce unnecessary warnings even for cases that are intrinsically handled. So we disable handling these calls // until a proper intrinsic handling is made + // NOTE: Currently this is done "for the analyzer" and it relies on linker/NativeAOT to not call HandleCallAction + // for intrinsics which linker/NativeAOT need special handling for or those which are not implemented here and only there. + // Ideally we would run everything through HandleCallAction and it would return "false" for intrinsics it doesn't handle + // like it already does for Activator.CreateInstance for example. default: methodReturnValue = MultiValueLattice.Top; return true; @@ -1319,7 +1325,7 @@ void ProcessCreateInstanceByName (MethodProxy calledMethod, IReadOnlyList IntrinsicId.Nullable_GetUnderlyingType, + _ => IntrinsicId.None, }; } diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs new file mode 100644 index 0000000000000..1c46b2dbeba0b --- /dev/null +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace ILLink.Shared.TypeSystemProxy +{ + public enum ReferenceKind + { + Ref, + In, + Out, + None + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs index 9d6f580c06d29..999f8f4dc95f2 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; +using StaticCs; // This is needed due to NativeAOT which doesn't enable nullable globally yet #nullable enable namespace ILLink.Shared.TypeSystemProxy { + [Closed] public enum WellKnownType { System_String, @@ -37,7 +38,6 @@ public static (string Namespace, string Name) GetNamespaceAndName (this WellKnow WellKnownType.System_NotSupportedException => ("System", "NotSupportedException"), WellKnownType.System_Runtime_CompilerServices_DisablePrivateReflectionAttribute => ("System.Runtime.CompilerServices", "DisablePrivateReflectionAttribute"), WellKnownType.System_Void => ("System", "Void"), - _ => throw new ArgumentException ($"{nameof (type)} is not a well-known type."), }; } public static string GetNamespace (this WellKnownType type) => GetNamespaceAndName (type).Namespace; diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs index a0eff369ee98a..b970f5ac587f8 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Runtime.InteropServices; using ILCompiler; +using ILCompiler.Dataflow; using ILLink.Shared.TrimAnalysis; using Internal.IL; using Internal.TypeSystem; @@ -56,7 +57,8 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) ilProvider = new FeatureSwitchManager (ilProvider, options.FeatureSwitches); - Logger logger = new Logger (logWriter, isVerbose: true); + Logger logger = new Logger (logWriter, ilProvider, isVerbose: true); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState (ilProvider, logger); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager ( compilationGroup, @@ -66,7 +68,7 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) logFile: null, new NoStackTraceEmissionPolicy (), new NoDynamicInvokeThunkGenerationPolicy (), - new FlowAnnotations (logger, ilProvider), + new FlowAnnotations (logger, ilProvider, compilerGeneratedState), UsageBasedMetadataGenerationOptions.ReflectionILScanning, logger, Array.Empty> (), diff --git a/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs b/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs index 0ebfcb06546f2..5f27838b5cd07 100644 --- a/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs +++ b/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs @@ -27,6 +27,7 @@ static int Main() TestDynamicDependencyWithGenerics.Run(); TestObjectGetTypeDataflow.Run(); TestMarshalIntrinsics.Run(); + TestCompilerGeneratedCode.Run(); return 100; } @@ -606,6 +607,57 @@ static void SanityTest() } } } + + class TestCompilerGeneratedCode + { + private static void ReflectionInLambda() + { + var func = () => { + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + }; + + func(); + } + + private static void ReflectionInLocalFunction() + { + func(); + + void func() + { + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + }; + } + + private static async void ReflectionInAsync() + { + await System.Threading.Tasks.Task.Delay(100); + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + } + + private static async void ReflectionInLambdaAsync() + { + await System.Threading.Tasks.Task.Delay(100); + + var func = () => { + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + }; + + func(); + } + + public static void Run() + { + ReflectionInLambda(); + ReflectionInLocalFunction(); + ReflectionInAsync(); + ReflectionInLambdaAsync(); + } + } } static class Assert