diff --git a/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs b/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs index aaf80b72536e..769cbff8852b 100644 --- a/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs +++ b/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs @@ -57,8 +57,13 @@ private static bool IsInRequiresScope (this ISymbol member, string requiresAttri if (member is ITypeSymbol) return false; - if (member.HasAttribute (requiresAttribute) && !member.IsStaticConstructor ()) - return true; + while (true) { + if (member.HasAttribute (requiresAttribute) && !member.IsStaticConstructor ()) + return true; + if (member.ContainingSymbol is not IMethodSymbol method) + break; + member = method; + } if (member.ContainingType is ITypeSymbol containingType && containingType.HasAttribute (requiresAttribute)) return true; diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 90deb75047b7..33b0c15580fc 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -2901,13 +2901,15 @@ internal bool ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode () if (originMember is not IMemberDefinition member) return false; - MethodDefinition? userDefinedMethod = Context.CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember (member); - if (userDefinedMethod == null) - return false; - - Debug.Assert (userDefinedMethod != originMember); + MethodDefinition? owningMethod; + while (Context.CompilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember (member, out owningMethod)) { + Debug.Assert (owningMethod != member); + if (Annotations.IsMethodInRequiresUnreferencedCodeScope (owningMethod)) + return true; + member = owningMethod; + } - return Annotations.IsMethodInRequiresUnreferencedCodeScope (userDefinedMethod); + return false; } internal void CheckAndReportRequiresUnreferencedCode (MethodDefinition method) diff --git a/src/linker/Linker/Annotations.cs b/src/linker/Linker/Annotations.cs index d51c4ea484dd..8845271415fd 100644 --- a/src/linker/Linker/Annotations.cs +++ b/src/linker/Linker/Annotations.cs @@ -593,18 +593,21 @@ public bool SetPreservedStaticCtor (TypeDefinition type) /// /// Unlike only static methods /// and .ctors are reported as requiring unreferenced code when the declaring type has RUC on it. - internal bool DoesMethodRequireUnreferencedCode (MethodDefinition method, [NotNullWhen (returnValue: true)] out RequiresUnreferencedCodeAttribute? attribute) + internal bool DoesMethodRequireUnreferencedCode (MethodDefinition originalMethod, [NotNullWhen (returnValue: true)] out RequiresUnreferencedCodeAttribute? attribute) { - if (method.IsStaticConstructor ()) { - attribute = null; - return false; - } - if (TryGetLinkerAttribute (method, out attribute)) - return true; + MethodDefinition? method = originalMethod; + do { + if (method.IsStaticConstructor ()) { + attribute = null; + return false; + } + if (TryGetLinkerAttribute (method, out attribute)) + return true; - if ((method.IsStatic || method.IsConstructor) && method.DeclaringType is not null && - TryGetLinkerAttribute (method.DeclaringType, out attribute)) - return true; + if ((method.IsStatic || method.IsConstructor) && method.DeclaringType is not null && + TryGetLinkerAttribute (method.DeclaringType, out attribute)) + return true; + } while (context.CompilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember (method, out method)); return false; } diff --git a/src/linker/Linker/CallGraph.cs b/src/linker/Linker/CallGraph.cs new file mode 100644 index 000000000000..065a20329f59 --- /dev/null +++ b/src/linker/Linker/CallGraph.cs @@ -0,0 +1,43 @@ +// 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 Mono.Cecil; + +namespace Mono.Linker +{ + class CallGraph + { + readonly Dictionary> callGraph; + + public CallGraph () => callGraph = new Dictionary> (); + + public void TrackCall (MethodDefinition fromMethod, MethodDefinition toMethod) + { + if (!callGraph.TryGetValue (fromMethod, out HashSet? toMethods)) { + toMethods = new HashSet (); + callGraph.Add (fromMethod, toMethods); + } + toMethods.Add (toMethod); + } + + public IEnumerable GetReachableMethods (MethodDefinition start) + { + Queue queue = new (); + HashSet visited = new (); + visited.Add (start); + queue.Enqueue (start); + while (queue.TryDequeue (out MethodDefinition? method)) { + if (!callGraph.TryGetValue (method, out HashSet? callees)) + continue; + + foreach (var callee in callees) { + if (visited.Add (callee)) { + queue.Enqueue (callee); + yield return callee; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/CompilerGeneratedNames.cs b/src/linker/Linker/CompilerGeneratedNames.cs new file mode 100644 index 000000000000..9532c1497800 --- /dev/null +++ b/src/linker/Linker/CompilerGeneratedNames.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Mono.Linker +{ + 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 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[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[i + 1] == 'g'; + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/CompilerGeneratedState.cs b/src/linker/Linker/CompilerGeneratedState.cs index 3c22da1f5478..9e8a76c7d0a5 100644 --- a/src/linker/Linker/CompilerGeneratedState.cs +++ b/src/linker/Linker/CompilerGeneratedState.cs @@ -1,9 +1,12 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 System.Diagnostics.CodeAnalysis; using ILLink.Shared; using Mono.Cecil; +using Mono.Cecil.Cil; namespace Mono.Linker { @@ -12,17 +15,29 @@ public class CompilerGeneratedState { readonly LinkContext _context; readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; + readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; readonly HashSet _typesWithPopulatedCache; public CompilerGeneratedState (LinkContext context) { _context = context; _compilerGeneratedTypeToUserCodeMethod = new Dictionary (); + _compilerGeneratedMethodToUserCodeMethod = new Dictionary (); _typesWithPopulatedCache = new HashSet (); } - static bool HasRoslynCompilerGeneratedName (TypeDefinition type) => - type.Name.Contains ('<') || (type.DeclaringType != null && HasRoslynCompilerGeneratedName (type.DeclaringType)); + 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; + } + } void PopulateCacheForType (TypeDefinition type) { @@ -30,9 +45,38 @@ void PopulateCacheForType (TypeDefinition type) if (!_typesWithPopulatedCache.Add (type)) return; - foreach (MethodDefinition method in type.Methods) { + var callGraph = new CallGraph (); + var callingMethods = new HashSet (); + + void ProcessMethod (MethodDefinition method) + { + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)) { + // If it's not a nested function, track as an entry point to the call graph. + var added = callingMethods.Add (method); + Debug.Assert (added); + } + + // Discover calls or references to lambdas or local functions. This includes + // calls to local functions, and lambda assignments (which use ldftn). + if (method.Body != null) { + foreach (var instruction in method.Body.Instructions) { + if (instruction.OpCode.OperandType != OperandType.InlineMethod) + continue; + + MethodDefinition? lambdaOrLocalFunction = _context.TryResolve ((MethodReference) instruction.Operand); + if (lambdaOrLocalFunction == null) + continue; + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (lambdaOrLocalFunction.Name)) + continue; + + callGraph.TrackCall (method, lambdaOrLocalFunction); + } + } + + // Discover state machine methods. if (!method.HasCustomAttributes) - continue; + return; foreach (var attribute in method.CustomAttributes) { if (attribute.AttributeType.Namespace != "System.Runtime.CompilerServices") @@ -43,17 +87,53 @@ void PopulateCacheForType (TypeDefinition type) 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 ()); - } + if (stateMachineType == null) + break; + Debug.Assert (stateMachineType.DeclaringType == type || + (CompilerGeneratedNames.IsGeneratedMemberName (stateMachineType.DeclaringType.Name) && + stateMachineType.DeclaringType.DeclaringType == type)); + if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd (stateMachineType, method)) { + var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; + _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), stateMachineType.GetDisplayName ()); } break; } } } + + // Look for state machine methods, and methods which call local functions. + foreach (MethodDefinition method in type.Methods) + ProcessMethod (method); + + // Also scan compiler-generated state machine methods (in case they have calls to nested functions), + // and nested functions inside compiler-generated closures (in case they call other nested functions). + + // State machines can be emitted into lambda display classes, so we need to go down at least two + // levels to find calls from iterator nested functions to other nested functions. We just recurse into + // all compiler-generated nested types to avoid depending on implementation details. + + foreach (var nestedType in GetCompilerGeneratedNestedTypes (type)) { + foreach (var method in nestedType.Methods) + ProcessMethod (method); + } + + // Now we've discovered the call graphs for calls to nested functions. + // Use this to map back from nested functions to the declaring user methods. + + // Note: This maps all nested functions back to the user code, not to the immediately + // declaring local function. The IL doesn't contain enough information in general for + // us to determine the nesting of local functions and lambdas. + + // Note: this only discovers nested functions which are referenced from the user + // code or its referenced nested functions. There is no reliable way to determine from + // IL which user code an unused nested function belongs to. + foreach (var userDefinedMethod in callingMethods) { + foreach (var nestedFunction in callGraph.GetReachableMethods (userDefinedMethod)) { + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (nestedFunction.Name)); + _compilerGeneratedMethodToUserCodeMethod.Add (nestedFunction, userDefinedMethod); + } + } } static TypeDefinition? GetFirstConstructorArgumentAsType (CustomAttribute attribute) @@ -64,27 +144,54 @@ void PopulateCacheForType (TypeDefinition type) return attribute.ConstructorArguments[0].Value as TypeDefinition; } - public MethodDefinition? GetUserDefinedMethodForCompilerGeneratedMember (IMemberDefinition sourceMember) + // 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 null; + return false; - TypeDefinition compilerGeneratedType = (sourceMember as TypeDefinition) ?? sourceMember.DeclaringType; - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (compilerGeneratedType, out MethodDefinition? userDefinedMethod)) - return userDefinedMethod; + MethodDefinition? compilerGeneratedMethod = sourceMember as MethodDefinition; + if (compilerGeneratedMethod != null) { + if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue (compilerGeneratedMethod, out owningMethod)) + return true; + } - // 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; + TypeDefinition sourceType = (sourceMember as TypeDefinition) ?? sourceMember.DeclaringType; + + if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (sourceType, out owningMethod)) + return true; + + if (!CompilerGeneratedNames.IsGeneratedMemberName (sourceMember.Name) && !CompilerGeneratedNames.IsGeneratedMemberName (sourceType.Name)) + return false; + + // sourceType is a state machine type, or the type containing a lambda or local function. + var typeToCache = sourceType; - // Now go to its declaring type and search all methods to find the one which points to the type as its + // Look in the declaring type if this is a compiler-generated type (state machine or display class). + // State machines can be emitted into display classes, so we may also need to go one more level up. + // To avoid depending on implementation details, we go up until we see a non-compiler-generated type. + // This is the counterpart to GetCompilerGeneratedNestedTypes. + while (typeToCache != null && CompilerGeneratedNames.IsGeneratedMemberName (typeToCache.Name)) + typeToCache = typeToCache.DeclaringType; + + if (typeToCache == null) + return false; + + // Search all methods to find the one which points to the type as its // state machine implementation. - PopulateCacheForType (compilerGeneratedType.DeclaringType); - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (compilerGeneratedType, out userDefinedMethod)) - return userDefinedMethod; + PopulateCacheForType (typeToCache); + if (compilerGeneratedMethod != null) { + if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue (compilerGeneratedMethod, out owningMethod)) + return true; + } + + if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (sourceType, out owningMethod)) + return true; - return null; + return false; } } } diff --git a/src/linker/Linker/UnconditionalSuppressMessageAttributeState.cs b/src/linker/Linker/UnconditionalSuppressMessageAttributeState.cs index 2820fe35f90a..a06035ff53bf 100644 --- a/src/linker/Linker/UnconditionalSuppressMessageAttributeState.cs +++ b/src/linker/Linker/UnconditionalSuppressMessageAttributeState.cs @@ -55,13 +55,15 @@ public bool IsSuppressed (int id, MessageOrigin warningOrigin, out SuppressMessa if (provider is not IMemberDefinition member) return false; - MethodDefinition? userDefinedMethod = _context.CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember (member); - if (userDefinedMethod == null) - return false; - - Debug.Assert (userDefinedMethod != provider); + MethodDefinition? owningMethod; + while (_context.CompilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember (member, out owningMethod)) { + Debug.Assert (owningMethod != member); + if (IsSuppressed (id, owningMethod, out info)) + return true; + member = owningMethod; + } - return IsSuppressed (id, userDefinedMethod, out info); + return false; } bool IsSuppressed (int id, ICustomAttributeProvider warningOrigin, out SuppressMessageInfo info) diff --git a/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInCompilerGeneratedCode.cs b/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInCompilerGeneratedCode.cs index cadebd061f4b..4be81c17ec23 100644 --- a/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInCompilerGeneratedCode.cs +++ b/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInCompilerGeneratedCode.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -27,6 +27,8 @@ public static void Main () WarnInLocalFunction.Test (); SuppressInLocalFunction.Test (); + WarnInNonNestedLocalFunctionTest (); + SuppressInNonNestedLocalFunctionTest (); WarnInLambda.Test (); SuppressInLambda.Test (); @@ -35,6 +37,8 @@ public static void Main () SuppressInComplex.Test (); StateMachinesOnlyReferencedViaReflection.Test (); + LocalFunctionsReferencedViaReflection.Test (); + LambdasReferencedViaReflection.Test (); ComplexCases.AsyncBodyCallingMethodWithRequires.Test (); ComplexCases.GenericAsyncBodyCallingMethodWithRequires.Test (); @@ -574,6 +578,15 @@ static void TestCall () void LocalFunction () => MethodWithRequires (); } + static void TestCallUnused () + { + // Analyzer emits warnings for code in unused local functions. + [ExpectedWarning ("IL2026", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3002", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + void LocalFunction () => MethodWithRequires (); + } + static void TestCallWithClosure (int p = 0) { LocalFunction (); @@ -588,6 +601,19 @@ void LocalFunction () } } + static void TestCallWithClosureUnused (int p = 0) + { + // Analyzer emits warnings for code in unused local functions. + [ExpectedWarning ("IL2026", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3002", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + void LocalFunction () + { + p++; + MethodWithRequires (); + } + } + static void TestReflectionAccess () { LocalFunction (); @@ -637,7 +663,9 @@ static void TestDynamicallyAccessedMethod () public static void Test () { TestCall (); + TestCallUnused (); TestCallWithClosure (); + TestCallWithClosureUnused (); TestReflectionAccess (); TestLdftn (); TestLazyDelegate (); @@ -647,9 +675,6 @@ public static void Test () class SuppressInLocalFunction { - // Requires doesn't propagate into local functions yet - // so its suppression effect also doesn't propagate - [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] @@ -657,12 +682,24 @@ static void TestCall () { LocalFunction (); - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] void LocalFunction () => MethodWithRequires (); } + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestCallFromNestedLocalFunction () + { + LocalFunction (); + + void LocalFunction () + { + NestedLocalFunction (); + + void NestedLocalFunction () => MethodWithRequires (); + } + } + [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] @@ -670,9 +707,6 @@ static void TestCallWithClosure (int p = 0) { LocalFunction (); - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] void LocalFunction () { p++; @@ -687,7 +721,6 @@ static async void TestReflectionAccess () { LocalFunction (); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => typeof (RequiresInCompilerGeneratedCode) .GetMethod ("MethodWithRequires", System.Reflection.BindingFlags.NonPublic) .Invoke (null, new object[] { }); @@ -700,9 +733,6 @@ static async void TestLdftn () { LocalFunction (); - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] void LocalFunction () { var action = new Action (MethodWithRequires); @@ -731,7 +761,6 @@ static async void TestDynamicallyAccessedMethod () { LocalFunction (); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => typeof (TypeWithMethodWithRequires).RequiresNonPublicMethods (); } @@ -742,7 +771,6 @@ static async void TestMethodParameterWithRequirements (Type unknownType = null) { LocalFunction (); - [ExpectedWarning ("IL2077", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => unknownType.RequiresNonPublicMethods (); } @@ -753,7 +781,6 @@ static void TestGenericMethodParameterRequirement () { LocalFunction (); - [ExpectedWarning ("IL2091", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => MethodWithGenericWhichRequiresMethods (); } @@ -764,7 +791,6 @@ static void TestGenericTypeParameterRequirement () { LocalFunction (); - [ExpectedWarning ("IL2091", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => new TypeWithGenericWhichRequiresNonPublicFields (); } @@ -788,8 +814,6 @@ static void TestGenericLocalFunctionInner () { LocalFunction (); - [ExpectedWarning ("IL2087", ProducedBy = ProducedBy.Trimmer)] - [ExpectedWarning ("IL2087", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () { typeof (TUnknown).RequiresPublicMethods (); @@ -827,9 +851,6 @@ static void TestCallMethodWithRequiresInLtftnLocalFunction () { var _ = new Action (LocalFunction); - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] void LocalFunction () => MethodWithRequires (); } @@ -842,9 +863,25 @@ public static void TestCallMethodWithRequiresInDynamicallyAccessedLocalFunction { typeof (DynamicallyAccessedLocalFunction).RequiresNonPublicMethods (); - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] + LocalFunction (); + + void LocalFunction () => MethodWithRequires (); + } + } + + class DynamicallyAccessedLocalFunctionUnused + { + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + public static void TestCallMethodWithRequiresInDynamicallyAccessedLocalFunction () + { + typeof (DynamicallyAccessedLocalFunctionUnused).RequiresNonPublicMethods (); + + // This local function is unused except for the dynamic reference above, + // so the linker isn't able to figure out which user method it belongs to, + // and the warning is not suppressed. + [ExpectedWarning ("IL2026", "--MethodWithRequires--", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => MethodWithRequires (); } } @@ -852,9 +889,9 @@ public static void TestCallMethodWithRequiresInDynamicallyAccessedLocalFunction [ExpectedWarning ("IL2026")] [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] - static void TestSuppressionLocalFunction () + static void TestSuppressionOnLocalFunction () { - LocalFunction (); // This will produce a warning since the location function has Requires on it + LocalFunction (); // This will produce a warning since the local function has Requires on it [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] @@ -866,6 +903,27 @@ void LocalFunction (Type unknownType = null) } } + [ExpectedWarning ("IL2026")] + [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] + static void TestSuppressionOnLocalFunctionWithNestedLocalFunction () + { + LocalFunction (); // This will produce a warning since the local function has Requires on it + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + void LocalFunction () + { + NestedLocalFunction (); + + // The linker doesn't have enough information to associate the Requires on LocalFunction + // with this nested local function. + [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] + void NestedLocalFunction () => MethodWithRequires (); + } + } + [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] @@ -883,12 +941,45 @@ void LocalFunction (Type unknownType = null) } } + class TestSuppressionOnOuterWithSameName + { + [ExpectedWarning ("IL2026", nameof (Outer) + "()")] + [ExpectedWarning ("IL3002", nameof (Outer) + "()", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", nameof (Outer) + "()", ProducedBy = ProducedBy.Analyzer)] + public static void Test () + { + Outer (); + Outer (0); + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void Outer () + { + // Even though this method has the same name as Outer(int i), + // it should not suppress warnings originating from compiler-generated + // code for the lambda contained in Outer(int i). + } + + static void Outer (int i) + { + LocalFunction (); + + [ExpectedWarning ("IL2026", "--MethodWithRequires--")] + [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] + void LocalFunction () => MethodWithRequires (); + } + } + [UnconditionalSuppressMessage ("Trimming", "IL2026")] [UnconditionalSuppressMessage ("SingleFile", "IL3002")] [UnconditionalSuppressMessage ("AOT", "IL3050")] public static void Test () { TestCall (); + TestCallFromNestedLocalFunction (); TestCallWithClosure (); TestReflectionAccess (); TestLdftn (); @@ -903,14 +994,56 @@ public static void Test () TestGenericLocalFunctionWithAnnotationsAndClosure (); TestCallMethodWithRequiresInLtftnLocalFunction (); DynamicallyAccessedLocalFunction.TestCallMethodWithRequiresInDynamicallyAccessedLocalFunction (); - TestSuppressionLocalFunction (); + DynamicallyAccessedLocalFunctionUnused.TestCallMethodWithRequiresInDynamicallyAccessedLocalFunction (); + TestSuppressionOnLocalFunction (); + TestSuppressionOnLocalFunctionWithNestedLocalFunction (); TestSuppressionOnOuterAndLocalFunction (); + TestSuppressionOnOuterWithSameName.Test (); } } + static void WarnInNonNestedLocalFunctionTest () + { + LocalFunction (); + + [ExpectedWarning ("IL2026", "--MethodWithRequires--")] + [ExpectedWarning ("IL3002", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + static void LocalFunction () => MethodWithRequires (); + } + + [ExpectedWarning ("IL2026", "--MethodWithNonNestedLocalFunction--")] + [ExpectedWarning ("IL3002", "--MethodWithNonNestedLocalFunction--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--MethodWithNonNestedLocalFunction--", ProducedBy = ProducedBy.Analyzer)] + static void SuppressInNonNestedLocalFunctionTest () + { + MethodWithNonNestedLocalFunction (); + } + + [RequiresUnreferencedCode ("--MethodWithNonNestedLocalFunction--")] + [RequiresAssemblyFiles ("--MethodWithNonNestedLocalFunction--")] + [RequiresDynamicCode ("--MethodWithNonNestedLocalFunction--")] + static void MethodWithNonNestedLocalFunction () + { + LocalFunction (); + + static void LocalFunction () => MethodWithRequires (); + } + class WarnInLambda { static void TestCall () + { + Action lambda = + [ExpectedWarning ("IL2026", "--MethodWithRequires--")] + [ExpectedWarning ("IL3002", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + () => MethodWithRequires (); + + lambda (); + } + + static void TestCallUnused () { Action _ = [ExpectedWarning ("IL2026", "--MethodWithRequires--")] @@ -920,6 +1053,20 @@ static void TestCall () } static void TestCallWithClosure (int p = 0) + { + Action lambda = + [ExpectedWarning ("IL2026", "--MethodWithRequires--")] + [ExpectedWarning ("IL3002", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--MethodWithRequires--", ProducedBy = ProducedBy.Analyzer)] + () => { + p++; + MethodWithRequires (); + }; + + lambda (); + } + + static void TestCallWithClosureUnused (int p = 0) { Action _ = [ExpectedWarning ("IL2026", "--MethodWithRequires--")] @@ -977,7 +1124,9 @@ static void TestDynamicallyAccessedMethod () public static void Test () { TestCall (); + TestCallUnused (); TestCallWithClosure (); + TestCallWithClosureUnused (); TestReflectionAccess (); TestLdftn (); TestLazyDelegate (); @@ -987,24 +1136,25 @@ public static void Test () class SuppressInLambda { - // Bug https://github.com/dotnet/linker/issues/2001 - // Requires should propagate into lambdas - - // C# 10 allows attributes on lambdas - // - This would be useful as a workaround for the limitation as Requires could be applied to the lambda directly - [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] static void TestCall () { Action _ = - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] () => MethodWithRequires (); } + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestCallFromNestedLambda () + { + Action lambda = () => { + Action nestedLambda = () => MethodWithRequires (); + }; + } + [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] @@ -1012,7 +1162,6 @@ static void TestCallWithReflectionAnalysisWarning () { // This should not produce warning because the Requires Action _ = - [ExpectedWarning ("IL2067", ProducedBy = ProducedBy.Trimmer)] (t) => t.RequiresPublicMethods (); } @@ -1022,9 +1171,6 @@ static void TestCallWithReflectionAnalysisWarning () static void TestCallWithClosure (int p = 0) { Action _ = - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] () => { p++; MethodWithRequires (); @@ -1037,8 +1183,6 @@ static void TestCallWithClosure (int p = 0) static void TestReflectionAccess () { Action _ = - // Analyzer doesn't recognize reflection access - so doesn't warn in this case - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] () => { typeof (RequiresInCompilerGeneratedCode) .GetMethod ("MethodWithRequires", System.Reflection.BindingFlags.NonPublic) @@ -1052,9 +1196,6 @@ static void TestReflectionAccess () static void TestLdftn () { Action _ = - [ExpectedWarning ("IL2026")] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] () => { var action = new Action (MethodWithRequires); }; @@ -1078,8 +1219,6 @@ static void TestLazyDelegate () static void TestDynamicallyAccessedMethod () { Action _ = - // Analyzer doesn't apply DAM - so won't see this warnings - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] () => { typeof (TypeWithMethodWithRequires).RequiresNonPublicMethods (); }; @@ -1091,26 +1230,20 @@ static void TestDynamicallyAccessedMethod () static async void TestMethodParameterWithRequirements (Type unknownType = null) { Action _ = - // TODO: Fix the discrepancy between linker and analyzer - // https://github.com/dotnet/linker/issues/2350 - [ExpectedWarning ("IL2077", ProducedBy = ProducedBy.Trimmer)] () => unknownType.RequiresNonPublicMethods (); } - // The warning is currently not detected by roslyn analyzer since it doesn't analyze DAM yet - [ExpectedWarning ("IL2091", CompilerGeneratedCode = true, ProducedBy = ProducedBy.Trimmer)] [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] static void TestGenericMethodParameterRequirement () { - Action _ = () => { + Action _ = + () => { MethodWithGenericWhichRequiresMethods (); }; } - // The warning is currently not detected by roslyn analyzer since it doesn't analyze DAM yet - [ExpectedWarning ("IL2091", CompilerGeneratedCode = true, ProducedBy = ProducedBy.Trimmer)] [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] @@ -1121,12 +1254,100 @@ static void TestGenericTypeParameterRequirement () }; } + [ExpectedWarning ("IL2026")] + [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] + static void TestSuppressionOnLambda () + { + var lambda = + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + () => MethodWithRequires (); + + lambda (); // This will produce a warning since the lambda has Requires on it + } + + [ExpectedWarning ("IL2026")] + [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] + static void TestSuppressionOnLambdaWithNestedLambda () + { + var lambda = + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + () => { + // The linker doesn't try to associate the Requires on lambda with this nested + // lambda. It would be possible to do this because the declaration site will contain + // an IL reference to the generated lambda method, unlike local functions. + // However, we don't make this association, for consistency with local functions. + var nestedLambda = + [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] + () => MethodWithRequires (); + }; + + lambda (); // This will produce a warning since the lambda has Requires on it + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestSuppressionOnOuterAndLambda () + { + var lambda = + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + (Type unknownType) => { + MethodWithRequires (); + unknownType.RequiresNonPublicMethods (); + }; + + lambda (null); + } + + class TestSuppressionOnOuterWithSameName + { + [ExpectedWarning ("IL2026", nameof (Outer) + "()")] + [ExpectedWarning ("IL3002", nameof (Outer) + "()", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", nameof (Outer) + "()", ProducedBy = ProducedBy.Analyzer)] + public static void Test () + { + Outer (); + Outer (0); + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void Outer () + { + // Even though this method has the same name as Outer(int i), + // it should not suppress warnings originating from compiler-generated + // code for the lambda contained in Outer(int i). + } + + static void Outer (int i) + { + var lambda = + [ExpectedWarning ("IL2026", "--MethodWithRequires--")] + [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] + () => MethodWithRequires (); + + lambda (); + } + } + + [UnconditionalSuppressMessage ("Trimming", "IL2026")] [UnconditionalSuppressMessage ("SingleFile", "IL3002")] [UnconditionalSuppressMessage ("AOT", "IL3050")] public static void Test () { TestCall (); + TestCallFromNestedLambda (); TestCallWithReflectionAnalysisWarning (); TestCallWithClosure (); TestReflectionAccess (); @@ -1136,6 +1357,10 @@ public static void Test () TestMethodParameterWithRequirements (); TestGenericMethodParameterRequirement (); TestGenericTypeParameterRequirement (); + TestSuppressionOnLambda (); + TestSuppressionOnLambdaWithNestedLambda (); + TestSuppressionOnOuterAndLambda (); + TestSuppressionOnOuterWithSameName.Test (); } } @@ -1174,6 +1399,128 @@ public static void Test () class SuppressInComplex { + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestIteratorLocalFunction () + { + LocalFunction (); + + IEnumerable LocalFunction () + { + yield return 0; + MethodWithRequires (); + yield return 1; + } + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestAsyncLocalFunction () + { + LocalFunction (); + + async Task LocalFunction () + { + await MethodAsync (); + MethodWithRequires (); + return 1; + } + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestIteratorLocalFunctionWithClosure (int p = 0) + { + LocalFunction (); + + IEnumerable LocalFunction () + { + p++; + yield return 0; + MethodWithRequires (); + yield return 1; + } + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestAsyncLocalFunctionWithClosure (int p = 0) + { + LocalFunction (); + + async Task LocalFunction () + { + p++; + await MethodAsync (); + MethodWithRequires (); + return 1; + } + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestCallToLocalFunctionInIteratorLocalFunctionWithClosure (int p = 0) + { + LocalFunction (); + + IEnumerable LocalFunction () + { + p++; + yield return 0; + LocalFunction2 (); + yield return 1; + + void LocalFunction2 () + { + MethodWithRequires (); + } + } + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestAsyncLambda () + { + Func> _ = async Task () => { + await MethodAsync (); + MethodWithRequires (); + return 1; + }; + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestAsyncLambdaWithClosure (int p = 0) + { + Func> _ = async Task () => { + p++; + await MethodAsync (); + MethodWithRequires (); + return 1; + }; + } + + [RequiresUnreferencedCode ("Suppress in body")] + [RequiresAssemblyFiles ("Suppress in body")] + [RequiresDynamicCode ("Suppress in body")] + static void TestLambdaInAsyncLambdaWithClosure (int p = 0) + { + Func> _ = async Task () => { + p++; + await MethodAsync (); + var lambda = () => MethodWithRequires (); + return 1; + }; + } + [RequiresUnreferencedCode ("Suppress in body")] [RequiresAssemblyFiles ("Suppress in body")] [RequiresDynamicCode ("Suppress in body")] @@ -1203,9 +1550,6 @@ static async void TestIteratorLocalFunctionInAsyncWithoutInner () LocalFunction (); await MethodAsync (); - [ExpectedWarning ("IL2026", CompilerGeneratedCode = true)] - [ExpectedWarning ("IL3002", ProducedBy = ProducedBy.Analyzer)] - [ExpectedWarning ("IL3050", ProducedBy = ProducedBy.Analyzer)] IEnumerable LocalFunction () { yield return 0; @@ -1228,6 +1572,14 @@ static IEnumerable TestDynamicallyAccessedMethodViaGenericMethodParameterIn [UnconditionalSuppressMessage ("AOT", "IL3050")] public static void Test () { + TestIteratorLocalFunction (); + TestAsyncLocalFunction (); + TestIteratorLocalFunctionWithClosure (); + TestAsyncLocalFunctionWithClosure (); + TestCallToLocalFunctionInIteratorLocalFunctionWithClosure (); + TestAsyncLambda (); + TestAsyncLambdaWithClosure (); + TestLambdaInAsyncLambdaWithClosure (); TestIteratorLocalFunctionInAsync (); TestIteratorLocalFunctionInAsyncWithoutInner (); TestDynamicallyAccessedMethodViaGenericMethodParameterInIterator (); @@ -1274,13 +1626,206 @@ static async void TestAsyncOnlyReferencedViaReflectionWhichShouldWarn () [ExpectedWarning ("IL2026", "Requires to suppress")] [ExpectedWarning ("IL2026", "Requires to suppress")] - public static void Test () + // Analyzer doesn't emit additional warnings about reflection access to the compiler-generated + // state machine members. + [ExpectedWarning ("IL2026", "Requires to suppress", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "Requires to suppress", ProducedBy = ProducedBy.Trimmer)] + static void TestAll () { - // This is not a 100% reliable test, since in theory it can be marked in any order and so it could happen that the - // user method is marked before the nested state machine gets marked. But it's the best we can do right now. - // (Note that currently linker will mark the state machine first actually so the test is effective). typeof (StateMachinesOnlyReferencedViaReflection).RequiresAll (); } + + [ExpectedWarning ("IL2026", "Requires to suppress")] + [ExpectedWarning ("IL2026", "Requires to suppress")] + // NonPublicMethods doesn't warn for members emitted into compiler-generated state machine types. + static void TestNonPublicMethods () + { + typeof (StateMachinesOnlyReferencedViaReflection).RequiresNonPublicMethods (); + } + + public static void Test () + { + TestAll (); + TestNonPublicMethods (); + } + } + + class LocalFunctionsReferencedViaReflection + { + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithRequires--")] + [ExpectedWarning ("IL3002", "--TestLocalFunctionWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--TestLocalFunctionWithRequires--", ProducedBy = ProducedBy.Analyzer)] + static void TestLocalFunctionWithRequires () + { + LocalFunction (); + + [RequiresUnreferencedCode ("--TestLocalFunctionWithRequires--")] + [RequiresAssemblyFiles ("--TestLocalFunctionWithRequires--")] + [RequiresDynamicCode ("--TestLocalFunctionWithRequires--")] + void LocalFunction () => MethodWithRequires (); + } + + [ExpectedWarning ("IL2026", "LocalFunction")] + [ExpectedWarning ("IL3002", "LocalFunction", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "LocalFunction", ProducedBy = ProducedBy.Analyzer)] + static void TestLocalFunctionWithClosureWithRequires (int p = 0) + { + LocalFunction (); + + [RequiresUnreferencedCode ("--TestLocalFunctionWithClosureWithRequires--")] + [RequiresAssemblyFiles ("--TestLocalFunctionWithClosureWithRequires--")] + [RequiresDynamicCode ("--TestLocalFunctionWithClosureWithRequires--")] + void LocalFunction () + { + p++; + MethodWithRequires (); + } + } + + [RequiresUnreferencedCode ("--TestLocalFunctionInMethodWithRequires--")] + [RequiresAssemblyFiles ("--TestLocalFunctionInMethodWithRequires--")] + [RequiresDynamicCode ("--TestLocalFunctionInMethodWithRequires--")] + static void TestLocalFunctionInMethodWithRequires () + { + LocalFunction (); + + void LocalFunction () => MethodWithRequires (); + } + + [RequiresUnreferencedCode ("--TestLocalFunctionWithClosureInMethodWithRequires--")] + [RequiresAssemblyFiles ("--TestLocalFunctionWithClosureInMethodWithRequires--")] + [RequiresDynamicCode ("--TestLocalFunctionWithClosureInMethodWithRequires--")] + static void TestLocalFunctionWithClosureInMethodWithRequires (int p = 0) + { + LocalFunction (); + + void LocalFunction () + { + p++; + MethodWithRequires (); + } + } + + // Warnings for Reflection access to methods with Requires + [ExpectedWarning ("IL2026", "--TestLocalFunctionInMethodWithRequires--")] + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithClosureInMethodWithRequires--")] + // The linker correctly emits warnings about reflection access to local functions with Requires + // or which inherit Requires from the containing method. The analyzer doesn't bind to local functions + // so does not warn here. + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithClosureWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLocalFunctionInMethodWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithClosureInMethodWithRequires--", ProducedBy = ProducedBy.Trimmer)] + static void TestAll () + { + typeof (LocalFunctionsReferencedViaReflection).RequiresAll (); + } + + // Warnings for Reflection access to methods with Requires + [ExpectedWarning ("IL2026", "--TestLocalFunctionInMethodWithRequires--")] + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithClosureInMethodWithRequires--")] + // NonPublicMethods warns for local functions not emitted into display classes. + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithClosureWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLocalFunctionInMethodWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLocalFunctionWithClosureInMethodWithRequires--", ProducedBy = ProducedBy.Trimmer)] + static void TestNonPublicMethods () + { + typeof (LocalFunctionsReferencedViaReflection).RequiresNonPublicMethods (); + } + + public static void Test () + { + TestAll (); + TestNonPublicMethods (); + } + } + + class LambdasReferencedViaReflection + { + [ExpectedWarning ("IL2026", "--TestLambdaWithRequires--")] + [ExpectedWarning ("IL3002", "--TestLambdaWithRequires--", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "--TestLambdaWithRequires--", ProducedBy = ProducedBy.Analyzer)] + static void TestLambdaWithRequires () + { + var lambda = + [RequiresUnreferencedCode ("--TestLambdaWithRequires--")] + [RequiresAssemblyFiles ("--TestLambdaWithRequires--")] + [RequiresDynamicCode ("--TestLambdaWithRequires--")] + () => MethodWithRequires (); + + lambda (); + } + + [ExpectedWarning ("IL2026", "Lambda")] + [ExpectedWarning ("IL3002", "Lambda", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL3050", "Lambda", ProducedBy = ProducedBy.Analyzer)] + static void TestLambdaWithClosureWithRequires (int p = 0) + { + var lambda = + [RequiresUnreferencedCode ("--TestLambdaWithClosureWithRequires--")] + [RequiresAssemblyFiles ("--TestLambdaWithClosureWithRequires--")] + [RequiresDynamicCode ("--TestLambdaWithClosureWithRequires--")] + () => { + p++; + MethodWithRequires (); + }; + + lambda (); + } + + [RequiresUnreferencedCode ("--TestLambdaInMethodWithRequires--")] + [RequiresAssemblyFiles ("--TestLambdaInMethodWithRequires--")] + [RequiresDynamicCode ("--TestLambdaInMethodWithRequires--")] + static void TestLambdaInMethodWithRequires () + { + var lambda = () => MethodWithRequires (); + + lambda (); + } + + [RequiresUnreferencedCode ("--TestLambdaWithClosureInMethodWithRequires--")] + [RequiresAssemblyFiles ("--TestLambdaWithClosureInMethodWithRequires--")] + [RequiresDynamicCode ("--TestLambdaWithClosureInMethodWithRequires--")] + static void TestLambdaWithClosureInMethodWithRequires (int p = 0) + { + var lambda = () => { + p++; + MethodWithRequires (); + }; + + lambda (); + } + + // Warnings for Reflection access to methods with Requires + [ExpectedWarning ("IL2026", "--TestLambdaInMethodWithRequires--")] + [ExpectedWarning ("IL2026", "--TestLambdaWithClosureInMethodWithRequires--")] + // The linker correctly emits warnings about reflection access to lambdas with Requires + // or which inherit Requires from the containing method. The analyzer doesn't bind to lambdas + // so does not warn here. + [ExpectedWarning ("IL2026", "--TestLambdaWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLambdaWithClosureWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLambdaInMethodWithRequires--", ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2026", "--TestLambdaWithClosureInMethodWithRequires--", ProducedBy = ProducedBy.Trimmer)] + static void TestAll () + { + typeof (LambdasReferencedViaReflection).RequiresAll (); + } + + // Warnings for Reflection access to methods with Requires + [ExpectedWarning ("IL2026", "--TestLambdaInMethodWithRequires--")] + [ExpectedWarning ("IL2026", "--TestLambdaWithClosureInMethodWithRequires--")] + // NonPublicMethods doesn't warn for lambdas emitted into display class types. + static void TestNonPublicMethods () + { + typeof (LambdasReferencedViaReflection).RequiresNonPublicMethods (); + } + + public static void Test () + { + TestAll (); + TestNonPublicMethods (); + } } class ComplexCases diff --git a/test/Mono.Linker.Tests.Cases/Warnings/WarningSuppression/SuppressWarningsInCompilerGeneratedCode.cs b/test/Mono.Linker.Tests.Cases/Warnings/WarningSuppression/SuppressWarningsInCompilerGeneratedCode.cs index c57dc3afd4ce..29fb45818112 100644 --- a/test/Mono.Linker.Tests.Cases/Warnings/WarningSuppression/SuppressWarningsInCompilerGeneratedCode.cs +++ b/test/Mono.Linker.Tests.Cases/Warnings/WarningSuppression/SuppressWarningsInCompilerGeneratedCode.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -158,14 +158,17 @@ public static void Test () class SuppressInLocalFunction { - // Suppression currently doesn't propagate to local functions - [UnconditionalSuppressMessage ("Test", "IL2026")] static void TestCallRUCMethod () { LocalFunction (); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] + void LocalFunction () => RequiresUnreferencedCodeMethod (); + } + + [UnconditionalSuppressMessage ("Test", "IL2026")] + static void TestCallRUCMethodUnused () + { void LocalFunction () => RequiresUnreferencedCodeMethod (); } @@ -174,7 +177,6 @@ static void TestReflectionAccessRUCMethod () { LocalFunction (); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => typeof (SuppressWarningsInCompilerGeneratedCode) .GetMethod ("RequiresUnreferencedCodeMethod", System.Reflection.BindingFlags.NonPublic) .Invoke (null, new object[] { }); @@ -185,7 +187,6 @@ static void TestLdftnOnRUCMethod () { LocalFunction (); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () { var _ = new Action (RequiresUnreferencedCodeMethod); } } @@ -195,7 +196,6 @@ static void TestDynamicallyAccessedMethod () { LocalFunction (); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => typeof (TypeWithRUCMethod).RequiresNonPublicMethods (); } @@ -204,7 +204,6 @@ static void TestMethodParameterWithRequirements (Type unknownType = null) { LocalFunction (); - [ExpectedWarning ("IL2077", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => unknownType.RequiresNonPublicMethods (); } @@ -213,7 +212,6 @@ static void TestGenericMethodParameterRequirement () { LocalFunction (); - [ExpectedWarning ("IL2091", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => MethodWithGenericWhichRequiresMethods (); } @@ -222,7 +220,6 @@ static void TestGenericTypeParameterRequirement () { LocalFunction (); - [ExpectedWarning ("IL2091", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => new TypeWithGenericWhichRequiresNonPublicFields (); } @@ -242,8 +239,6 @@ static void TestGenericLocalFunctionInner () { LocalFunction (); - [ExpectedWarning ("IL2087", ProducedBy = ProducedBy.Trimmer)] - [ExpectedWarning ("IL2087", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () { typeof (TUnknown).RequiresPublicMethods (); @@ -279,7 +274,6 @@ static void TestCallRUCMethodInLtftnLocalFunction () { var _ = new Action (LocalFunction); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => RequiresUnreferencedCodeMethod (); } @@ -290,7 +284,23 @@ public static void TestCallRUCMethodInDynamicallyAccessedLocalFunction () { typeof (DynamicallyAccessedLocalFunction).RequiresNonPublicMethods (); - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] + LocalFunction (); + + void LocalFunction () => RequiresUnreferencedCodeMethod (); + } + } + + class DynamicallyAccessedLocalFunctionUnused + { + [UnconditionalSuppressMessage ("Test", "IL2026")] + public static void TestCallRUCMethodInDynamicallyAccessedLocalFunction () + { + typeof (DynamicallyAccessedLocalFunctionUnused).RequiresNonPublicMethods (); + + // This local function is unused except for the dynamic reference above, + // so the linker isn't able to figure out which user method it belongs to, + // and the warning is not suppressed. + [ExpectedWarning ("IL2026", "--RequiresUnreferencedCodeMethod--", ProducedBy = ProducedBy.Trimmer)] void LocalFunction () => RequiresUnreferencedCodeMethod (); } } @@ -311,7 +321,6 @@ static void TestSuppressionOnOuterAndLocalFunction () { LocalFunction (); - [ExpectedWarning ("IL2067", ProducedBy = ProducedBy.Trimmer)] [UnconditionalSuppressMessage ("Test", "IL2026")] // This supresses the RequiresUnreferencedCodeMethod void LocalFunction (Type unknownType = null) { @@ -320,9 +329,35 @@ void LocalFunction (Type unknownType = null) } } + class TestSuppressionOnOuterWithSameName + { + public static void Test () + { + Outer (); + Outer (0); + } + + [UnconditionalSuppressMessage ("Test", "IL2026")] + static void Outer () + { + // Even though this method has the same name as Outer(int i), + // it should not suppress warnings originating from compiler-generated + // code for the lambda contained in Outer(int i). + } + + static void Outer (int i) + { + LocalFunction (); + + [ExpectedWarning ("IL2026", "--RequiresUnreferencedCodeMethod--")] + void LocalFunction () => RequiresUnreferencedCodeMethod (); + } + } + public static void Test () { TestCallRUCMethod (); + TestCallRUCMethodUnused (); TestReflectionAccessRUCMethod (); TestLdftnOnRUCMethod (); TestDynamicallyAccessedMethod (); @@ -335,20 +370,28 @@ public static void Test () TestGenericLocalFunctionWithAnnotationsAndClosure (); TestCallRUCMethodInLtftnLocalFunction (); DynamicallyAccessedLocalFunction.TestCallRUCMethodInDynamicallyAccessedLocalFunction (); + DynamicallyAccessedLocalFunctionUnused.TestCallRUCMethodInDynamicallyAccessedLocalFunction (); TestSuppressionOnLocalFunction (); TestSuppressionOnOuterAndLocalFunction (); + TestSuppressionOnOuterWithSameName.Test (); } } class SuppressInLambda { - // Suppression currently doesn't propagate to lambdas - [UnconditionalSuppressMessage ("Test", "IL2026")] static void TestCallRUCMethod () + { + Action lambda = + () => RequiresUnreferencedCodeMethod (); + + lambda (); + } + + [UnconditionalSuppressMessage ("Test", "IL2026")] + static void TestCallRUCMethodUnused () { Action _ = - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] () => RequiresUnreferencedCodeMethod (); } @@ -356,7 +399,6 @@ static void TestCallRUCMethod () static void TestReflectionAccessRUCMethod () { Action _ = - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] () => typeof (SuppressWarningsInCompilerGeneratedCode) .GetMethod ("RequiresUnreferencedCodeMethod", System.Reflection.BindingFlags.NonPublic) .Invoke (null, new object[] { }); @@ -366,7 +408,6 @@ static void TestReflectionAccessRUCMethod () static void TestLdftnOnRUCMethod () { Action _ = - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] () => { var _ = new Action (RequiresUnreferencedCodeMethod); }; } @@ -374,7 +415,6 @@ static void TestLdftnOnRUCMethod () static void TestDynamicallyAccessedMethod () { Action _ = - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer)] () => typeof (TypeWithRUCMethod).RequiresNonPublicMethods (); } @@ -382,7 +422,6 @@ static void TestDynamicallyAccessedMethod () static void TestMethodParameterWithRequirements (Type unknownType = null) { Action _ = - [ExpectedWarning ("IL2077", ProducedBy = ProducedBy.Trimmer)] () => unknownType.RequiresNonPublicMethods (); } @@ -390,7 +429,6 @@ static void TestMethodParameterWithRequirements (Type unknownType = null) static void TestGenericMethodParameterRequirement () { Action _ = - [ExpectedWarning ("IL2091", ProducedBy = ProducedBy.Trimmer)] () => MethodWithGenericWhichRequiresMethods (); } @@ -398,24 +436,119 @@ static void TestGenericMethodParameterRequirement () static void TestGenericTypeParameterRequirement () { Action _ = - [ExpectedWarning ("IL2091", ProducedBy = ProducedBy.Trimmer)] () => new TypeWithGenericWhichRequiresNonPublicFields (); } + class DynamicallyAccessedLambda + { + [UnconditionalSuppressMessage ("Test", "IL2026")] + public static void TestCallRUCMethodInDynamicallyAccessedLambda () + { + typeof (DynamicallyAccessedLambda).RequiresNonPublicMethods (); + + Action lambda = () => RequiresUnreferencedCodeMethod (); + + lambda (); + } + } + + class DynamicallyAccessedLambdaUnused + { + [UnconditionalSuppressMessage ("Test", "IL2026")] + public static void TestCallRUCMethodInDynamicallyAccessedLambda () + { + typeof (DynamicallyAccessedLambdaUnused).RequiresNonPublicMethods (); + + Action _ = () => RequiresUnreferencedCodeMethod (); + } + } + + static void TestSuppressionOnLambda () + { + var lambda = + // https://github.com/dotnet/roslyn/issues/59746 + [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Analyzer)] + [UnconditionalSuppressMessage ("Test", "IL2026")] + () => RequiresUnreferencedCodeMethod (); + + lambda (); + } + + [UnconditionalSuppressMessage ("Test", "IL2067")] + static void TestSuppressionOnOuterAndLambda () + { + var lambda = + // https://github.com/dotnet/roslyn/issues/59746 + [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Analyzer)] + [UnconditionalSuppressMessage ("Test", "IL2026")] + (Type unknownType) => { + RequiresUnreferencedCodeMethod (); + unknownType.RequiresNonPublicMethods (); + }; + + lambda (null); + } + + class TestSuppressionOnOuterWithSameName + { + public static void Test () + { + Outer (); + Outer (0); + } + + [UnconditionalSuppressMessage ("Test", "IL2026")] + static void Outer () + { + // Even though this method has the same name as Outer(int i), + // it should not suppress warnings originating from compiler-generated + // code for the lambda contained in Outer(int i). + } + + static void Outer (int i) + { + var lambda = + [ExpectedWarning ("IL2026", "--RequiresUnreferencedCodeMethod--")] + () => RequiresUnreferencedCodeMethod (); + + lambda (); + } + } + public static void Test () { TestCallRUCMethod (); + TestCallRUCMethodUnused (); TestReflectionAccessRUCMethod (); TestLdftnOnRUCMethod (); TestDynamicallyAccessedMethod (); TestMethodParameterWithRequirements (); TestGenericMethodParameterRequirement (); TestGenericTypeParameterRequirement (); + DynamicallyAccessedLambda.TestCallRUCMethodInDynamicallyAccessedLambda (); + DynamicallyAccessedLambdaUnused.TestCallRUCMethodInDynamicallyAccessedLambda (); + TestSuppressionOnLambda (); + TestSuppressionOnOuterAndLambda (); + TestSuppressionOnOuterWithSameName.Test (); + } } class SuppressInComplex { + [UnconditionalSuppressMessage ("Test", "IL2026")] + static void TestIteratorLocalFunction () + { + LocalFunction (); + + IEnumerable LocalFunction () + { + yield return 0; + RequiresUnreferencedCodeMethod (); + yield return 1; + } + } + [UnconditionalSuppressMessage ("Test", "IL2026")] static async void TestIteratorLocalFunctionInAsync () { @@ -439,7 +572,6 @@ static async void TestIteratorLocalFunctionInAsyncWithoutInner () LocalFunction (); await MethodAsync (); - [ExpectedWarning ("IL2026", CompilerGeneratedCode = true, ProducedBy = ProducedBy.Trimmer)] IEnumerable LocalFunction () { yield return 0; @@ -457,6 +589,7 @@ static IEnumerable TestDynamicallyAccessedMethodViaGenericMethodParameterIn public static void Test () { + TestIteratorLocalFunction (); TestIteratorLocalFunctionInAsync (); TestIteratorLocalFunctionInAsyncWithoutInner (); TestDynamicallyAccessedMethodViaGenericMethodParameterInIterator ();