-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lambda and local function suppressions (#2689)
This adds support for RequiresUnreferencedCode and UnconditionalSuppressMessage on lambdas and local functions, relying on heuristics and knowledge of compiler implementation details to detect lambdas and local functions. This approach scans the code for IL references to lambdas and local functions, which has some limitations. - Unused local functions aren't referenced by the containing method, so warnings from these will not be suppressed by suppressions on the containing method. Lambdas don't seem to have this problem, because they contain a reference to the generated method as part of the delegate conversion. - The IL doesn't in general contain enough information to determine the nesting of the scopes of lambdas and local functions, so we make no attempt to do this. We only try to determine to which user method a lambda or local function belongs. So suppressions on a lambda or local function will not silence warnings from a nested lambda or local function in the same scope. This also adds warnings for reflection access to compiler-generated state machine members, and to lambdas or local functions. For these, the analyzer makes no attempt to determine what the actual IL corresponding to the user code will be, so it produces fewer warnings. The linker will warn for what is actually in IL.
- Loading branch information
Showing
9 changed files
with
1,033 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MethodDefinition, HashSet<MethodDefinition>> callGraph; | ||
|
||
public CallGraph () => callGraph = new Dictionary<MethodDefinition, HashSet<MethodDefinition>> (); | ||
|
||
public void TrackCall (MethodDefinition fromMethod, MethodDefinition toMethod) | ||
{ | ||
if (!callGraph.TryGetValue (fromMethod, out HashSet<MethodDefinition>? toMethods)) { | ||
toMethods = new HashSet<MethodDefinition> (); | ||
callGraph.Add (fromMethod, toMethods); | ||
} | ||
toMethods.Add (toMethod); | ||
} | ||
|
||
public IEnumerable<MethodDefinition> GetReachableMethods (MethodDefinition start) | ||
{ | ||
Queue<MethodDefinition> queue = new (); | ||
HashSet<MethodDefinition> visited = new (); | ||
visited.Add (start); | ||
queue.Enqueue (start); | ||
while (queue.TryDequeue (out MethodDefinition? method)) { | ||
if (!callGraph.TryGetValue (method, out HashSet<MethodDefinition>? callees)) | ||
continue; | ||
|
||
foreach (var callee in callees) { | ||
if (visited.Add (callee)) { | ||
queue.Enqueue (callee); | ||
yield return callee; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 "<UserMethod>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 "<UserMethod>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'; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.