diff --git a/.editorconfig b/.editorconfig
index 0c33dcb86480..19907117e95f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -123,6 +123,9 @@ dotnet_analyzer_diagnostic.category-reliability.severity = warning
# CA1834: Use 'StringBuilder.Append(char)'
dotnet_diagnostic.CA1834.severity = none
+# RS2008 Ignore analyzer release tracking
+dotnet_diagnostic.RS2008.severity = none
+
[external**]
dotnet_analyzer_diagnostic.severity = none
generated_code = true
diff --git a/eng/Versions.props b/eng/Versions.props
index fdbfbba4ce5f..e44cef3c62c3 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -22,6 +22,8 @@
15.4.8
6.0.0-beta.20525.1
5.0.0-beta.20471.1
+ 3.7.0
+ 1.0.1-beta1.*
diff --git a/illink.sln b/illink.sln
index d26f2f3f7655..74a5b0227654 100644
--- a/illink.sln
+++ b/illink.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26124.0
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30524.135
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Linker", "src\linker\Mono.Linker.csproj", "{DD28E2B1-057B-4B4D-A04D-B2EBD9E76E46}"
EndProject
@@ -25,6 +25,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{03EB085F-3E2
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Linker", "src\linker\ref\Mono.Linker.csproj", "{57BE47DF-DCDF-44EE-B77F-F8E8AD069076}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ILLink.RoslynAnalyzer", "src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj", "{F1A44A78-34EE-408B-8285-9A26F0E7D4F2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ILLink.RoslynAnalyzer.Tests", "test\ILLink.RoslynAnalyzer.Tests\ILLink.RoslynAnalyzer.Tests.csproj", "{90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -131,6 +135,30 @@ Global
{57BE47DF-DCDF-44EE-B77F-F8E8AD069076}.Release|x64.Build.0 = Release|Any CPU
{57BE47DF-DCDF-44EE-B77F-F8E8AD069076}.Release|x86.ActiveCfg = Release|Any CPU
{57BE47DF-DCDF-44EE-B77F-F8E8AD069076}.Release|x86.Build.0 = Release|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Debug|x64.Build.0 = Debug|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Debug|x86.Build.0 = Debug|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Release|x64.ActiveCfg = Release|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Release|x64.Build.0 = Release|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Release|x86.ActiveCfg = Release|Any CPU
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2}.Release|x86.Build.0 = Release|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Debug|x64.Build.0 = Debug|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Debug|x86.Build.0 = Debug|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Release|x64.ActiveCfg = Release|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Release|x64.Build.0 = Release|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Release|x86.ActiveCfg = Release|Any CPU
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -142,6 +170,8 @@ Global
{96182221-C5C4-436D-9BE0-EC499F9BAF17} = {AA0569FB-73E9-4B42-9A19-714BB1229DAE}
{5A27FA80-0E28-4243-88DF-EC8A22C8BFD0} = {C2969923-7BAA-4FE4-853C-F670B0D3C6C8}
{57BE47DF-DCDF-44EE-B77F-F8E8AD069076} = {03EB085F-3E2E-4A68-A7DF-951ADF59A0CC}
+ {F1A44A78-34EE-408B-8285-9A26F0E7D4F2} = {AA0569FB-73E9-4B42-9A19-714BB1229DAE}
+ {90D64CE4-C891-4B98-AF59-EE9B04BA1CBE} = {C2969923-7BAA-4FE4-853C-F670B0D3C6C8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E43A3901-42B0-48CA-BB36-5CD40A99A6EE}
diff --git a/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj b/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj
new file mode 100644
index 000000000000..fd79369e8496
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj
@@ -0,0 +1,15 @@
+
+
+
+ netstandard2.0
+ 8
+ enable
+ false
+
+
+
+
+
+
+
+
diff --git a/src/ILLink.RoslynAnalyzer/OperationExtensions.cs b/src/ILLink.RoslynAnalyzer/OperationExtensions.cs
new file mode 100644
index 000000000000..6498e5682daf
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/OperationExtensions.cs
@@ -0,0 +1,251 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.FlowAnalysis;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace ILLink.RoslynAnalyzer
+{
+ // Copied from https://github.com/dotnet/roslyn/blob/9c6d864baca08d7572871701ab583cec18279426/src/Compilers/Core/Portable/Operations/OperationExtensions.cs
+ internal static partial class OperationExtensions
+ {
+ ///
+ /// Returns the for the given operation.
+ /// This extension can be removed once https://github.com/dotnet/roslyn/issues/25057 is implemented.
+ ///
+ public static ValueUsageInfo GetValueUsageInfo (this IOperation operation, ISymbol containingSymbol)
+ {
+ /*
+ | code | Read | Write | ReadableRef | WritableRef | NonReadWriteRef |
+ | x.Prop = 1 | | ✔️ | | | |
+ | x.Prop += 1 | ✔️ | ✔️ | | | |
+ | x.Prop++ | ✔️ | ✔️ | | | |
+ | Foo(x.Prop) | ✔️ | | | | |
+ | Foo(x.Prop), | | | ✔️ | | |
+ where void Foo(in T v)
+ | Foo(out x.Prop) | | | | ✔️ | |
+ | Foo(ref x.Prop) | | | ✔️ | ✔️ | |
+ | nameof(x) | | | | | ✔️ | ️
+ | sizeof(x) | | | | | ✔️ | ️
+ | typeof(x) | | | | | ✔️ | ️
+ | out var x | | ✔️ | | | | ️
+ | case X x: | | ✔️ | | | | ️
+ | obj is X x | | ✔️ | | | |
+ | ref var x = | | | ✔️ | ✔️ | |
+ | ref readonly var x = | | | ✔️ | | |
+
+ */
+ if (operation is ILocalReferenceOperation localReference &&
+ localReference.IsDeclaration &&
+ !localReference.IsImplicit) // Workaround for https://github.com/dotnet/roslyn/issues/30753
+ {
+ // Declaration expression is a definition (write) for the declared local.
+ return ValueUsageInfo.Write;
+ } else if (operation is IDeclarationPatternOperation) {
+ while (operation.Parent is IBinaryPatternOperation ||
+ operation.Parent is INegatedPatternOperation ||
+ operation.Parent is IRelationalPatternOperation) {
+ operation = operation.Parent;
+ }
+
+ switch (operation.Parent) {
+ case IPatternCaseClauseOperation _:
+ // A declaration pattern within a pattern case clause is a
+ // write for the declared local.
+ // For example, 'x' is defined and assigned the value from 'obj' below:
+ // switch (obj)
+ // {
+ // case X x:
+ //
+ return ValueUsageInfo.Write;
+
+ case IRecursivePatternOperation _:
+ // A declaration pattern within a recursive pattern is a
+ // write for the declared local.
+ // For example, 'x' is defined and assigned the value from 'obj' below:
+ // (obj) switch
+ // {
+ // (X x) => ...
+ // };
+ //
+ return ValueUsageInfo.Write;
+
+ case ISwitchExpressionArmOperation _:
+ // A declaration pattern within a switch expression arm is a
+ // write for the declared local.
+ // For example, 'x' is defined and assigned the value from 'obj' below:
+ // obj switch
+ // {
+ // X x => ...
+ //
+ return ValueUsageInfo.Write;
+
+ case IIsPatternOperation _:
+ // A declaration pattern within an is pattern is a
+ // write for the declared local.
+ // For example, 'x' is defined and assigned the value from 'obj' below:
+ // if (obj is X x)
+ //
+ return ValueUsageInfo.Write;
+
+ case IPropertySubpatternOperation _:
+ // A declaration pattern within a property sub-pattern is a
+ // write for the declared local.
+ // For example, 'x' is defined and assigned the value from 'obj.Property' below:
+ // if (obj is { Property : int x })
+ //
+ return ValueUsageInfo.Write;
+
+ default:
+ Debug.Fail ("Unhandled declaration pattern context");
+
+ // Conservatively assume read/write.
+ return ValueUsageInfo.ReadWrite;
+ }
+ }
+
+ if (operation.Parent is IAssignmentOperation assignmentOperation &&
+ assignmentOperation.Target == operation) {
+ return operation.Parent.IsAnyCompoundAssignment ()
+ ? ValueUsageInfo.ReadWrite
+ : ValueUsageInfo.Write;
+ } else if (operation.Parent is IIncrementOrDecrementOperation) {
+ return ValueUsageInfo.ReadWrite;
+ } else if (operation.Parent is IParenthesizedOperation parenthesizedOperation) {
+ // Note: IParenthesizedOperation is specific to VB, where the parens cause a copy, so this cannot be classified as a write.
+ Debug.Assert (parenthesizedOperation.Language == LanguageNames.VisualBasic);
+
+ return parenthesizedOperation.GetValueUsageInfo (containingSymbol) &
+ ~(ValueUsageInfo.Write | ValueUsageInfo.Reference);
+ } else if (operation.Parent is INameOfOperation ||
+ operation.Parent is ITypeOfOperation ||
+ operation.Parent is ISizeOfOperation) {
+ return ValueUsageInfo.Name;
+ } else if (operation.Parent is IArgumentOperation argumentOperation) {
+ switch (argumentOperation.Parameter.RefKind) {
+ case RefKind.RefReadOnly:
+ return ValueUsageInfo.ReadableReference;
+
+ case RefKind.Out:
+ return ValueUsageInfo.WritableReference;
+
+ case RefKind.Ref:
+ return ValueUsageInfo.ReadableWritableReference;
+
+ default:
+ return ValueUsageInfo.Read;
+ }
+ } else if (operation.Parent is IReturnOperation returnOperation) {
+ return returnOperation.GetRefKind (containingSymbol) switch
+ {
+ RefKind.RefReadOnly => ValueUsageInfo.ReadableReference,
+ RefKind.Ref => ValueUsageInfo.ReadableWritableReference,
+ _ => ValueUsageInfo.Read,
+ };
+ } else if (operation.Parent is IConditionalOperation conditionalOperation) {
+ if (operation == conditionalOperation.WhenTrue
+ || operation == conditionalOperation.WhenFalse) {
+ return GetValueUsageInfo (conditionalOperation, containingSymbol);
+ } else {
+ return ValueUsageInfo.Read;
+ }
+ } else if (operation.Parent is IReDimClauseOperation reDimClauseOperation &&
+ reDimClauseOperation.Operand == operation) {
+ return (reDimClauseOperation.Parent as IReDimOperation)?.Preserve == true
+ ? ValueUsageInfo.ReadWrite
+ : ValueUsageInfo.Write;
+ } else if (operation.Parent is IDeclarationExpressionOperation declarationExpression) {
+ return declarationExpression.GetValueUsageInfo (containingSymbol);
+ } else if (operation.IsInLeftOfDeconstructionAssignment (out _)) {
+ return ValueUsageInfo.Write;
+ } else if (operation.Parent is IVariableInitializerOperation variableInitializerOperation) {
+ if (variableInitializerOperation.Parent is IVariableDeclaratorOperation variableDeclaratorOperation) {
+ switch (variableDeclaratorOperation.Symbol.RefKind) {
+ case RefKind.Ref:
+ return ValueUsageInfo.ReadableWritableReference;
+
+ case RefKind.RefReadOnly:
+ return ValueUsageInfo.ReadableReference;
+ }
+ }
+ }
+
+ return ValueUsageInfo.Read;
+ }
+
+ public static RefKind GetRefKind (this IReturnOperation operation, ISymbol containingSymbol)
+ {
+ var containingMethod = TryGetContainingAnonymousFunctionOrLocalFunction (operation) ?? (containingSymbol as IMethodSymbol);
+ return containingMethod?.RefKind ?? RefKind.None;
+ }
+
+ public static IMethodSymbol? TryGetContainingAnonymousFunctionOrLocalFunction (this IOperation? operation)
+ {
+ operation = operation?.Parent;
+ while (operation != null) {
+ switch (operation.Kind) {
+ case OperationKind.AnonymousFunction:
+ return ((IAnonymousFunctionOperation) operation).Symbol;
+
+ case OperationKind.LocalFunction:
+ return ((ILocalFunctionOperation) operation).Symbol;
+ }
+
+ operation = operation.Parent;
+ }
+
+ return null;
+ }
+
+ public static bool IsInLeftOfDeconstructionAssignment (this IOperation operation, out IDeconstructionAssignmentOperation? deconstructionAssignment)
+ {
+ deconstructionAssignment = null;
+
+ var previousOperation = operation;
+ operation = operation.Parent;
+
+ while (operation != null) {
+ switch (operation.Kind) {
+ case OperationKind.DeconstructionAssignment:
+ deconstructionAssignment = (IDeconstructionAssignmentOperation) operation;
+ return deconstructionAssignment.Target == previousOperation;
+
+ case OperationKind.Tuple:
+ case OperationKind.Conversion:
+ case OperationKind.Parenthesized:
+ previousOperation = operation;
+ operation = operation.Parent;
+ continue;
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Retursn true if the given operation is a regular compound assignment,
+ /// i.e. such as a += b
,
+ /// or a special null coalescing compoud assignment, i.e.
+ /// such as a ??= b
.
+ ///
+ public static bool IsAnyCompoundAssignment (this IOperation operation)
+ {
+ switch (operation) {
+ case ICompoundAssignmentOperation _:
+ case ICoalesceAssignmentOperation _:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs b/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs
new file mode 100644
index 000000000000..cdb23970e84d
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace ILLink.RoslynAnalyzer
+{
+ [DiagnosticAnalyzer (LanguageNames.CSharp)]
+ public class RequiresUnreferencedCodeAnalyzer : DiagnosticAnalyzer
+ {
+ public const string DiagnosticId = "IL2026";
+
+ private static readonly LocalizableString s_title = new LocalizableResourceString (
+ nameof (RequiresUnreferencedCodeAnalyzer) + "Title",
+ Resources.ResourceManager,
+ typeof (Resources));
+ private static readonly LocalizableString s_messageFormat = new LocalizableResourceString (
+ nameof (RequiresUnreferencedCodeAnalyzer) + "Message",
+ Resources.ResourceManager,
+ typeof (Resources));
+ private const string s_category = "Trimming";
+
+ private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor (
+ DiagnosticId,
+ s_title,
+ s_messageFormat,
+ s_category,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create (s_rule);
+
+ public override void Initialize (AnalysisContext context)
+ {
+ context.EnableConcurrentExecution ();
+ context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.ReportDiagnostics);
+
+ context.RegisterCompilationStartAction (context => {
+ var compilation = context.Compilation;
+
+ context.RegisterOperationAction (operationContext => {
+ var call = (IInvocationOperation) operationContext.Operation;
+ CheckMethodOrCtorCall (operationContext, call.TargetMethod, call.Syntax.GetLocation ());
+ }, OperationKind.Invocation);
+
+ context.RegisterOperationAction (operationContext => {
+ var call = (IObjectCreationOperation) operationContext.Operation;
+ CheckMethodOrCtorCall (operationContext, call.Constructor, call.Syntax.GetLocation ());
+ }, OperationKind.ObjectCreation);
+
+ context.RegisterOperationAction (operationContext => {
+ var propAccess = (IPropertyReferenceOperation) operationContext.Operation;
+ var prop = propAccess.Property;
+ var usageInfo = propAccess.GetValueUsageInfo (prop);
+ if (usageInfo.HasFlag (ValueUsageInfo.Read) && prop.GetMethod != null) {
+ CheckMethodOrCtorCall (
+ operationContext,
+ prop.GetMethod,
+ propAccess.Syntax.GetLocation ());
+ }
+ if (usageInfo.HasFlag (ValueUsageInfo.Write) && prop.SetMethod != null) {
+ CheckMethodOrCtorCall (
+ operationContext,
+ prop.SetMethod,
+ propAccess.Syntax.GetLocation ());
+ }
+ }, OperationKind.PropertyReference);
+
+ void CheckMethodOrCtorCall (
+ OperationAnalysisContext operationContext,
+ IMethodSymbol method,
+ Location location)
+ {
+ var attributes = method.GetAttributes ();
+
+ foreach (var attr in attributes) {
+ if (attr.AttributeClass is { } attrClass &&
+ IsNamedType (attrClass, "System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute") &&
+ attr.ConstructorArguments.Length == 1 &&
+ attr.ConstructorArguments[0] is { Type: { SpecialType: SpecialType.System_String } } ctorArg) {
+ operationContext.ReportDiagnostic (Diagnostic.Create (
+ s_rule,
+ location,
+ method,
+ (string) ctorArg.Value!));
+ }
+ }
+ }
+ });
+ }
+
+ ///
+ /// Returns true if has the same name as
+ ///
+ internal static bool IsNamedType (INamedTypeSymbol type, string typeName)
+ {
+ var roSpan = typeName.AsSpan ();
+ INamespaceOrTypeSymbol? currentType = type;
+ while (roSpan.Length > 0) {
+ var dot = roSpan.LastIndexOf ('.');
+ var currentName = dot < 0 ? roSpan : roSpan.Slice (dot + 1);
+ if (currentType is null ||
+ !currentName.Equals (currentType.Name.AsSpan (), StringComparison.Ordinal)) {
+ return false;
+ }
+ currentType = (INamespaceOrTypeSymbol?) currentType.ContainingType ?? currentType.ContainingNamespace;
+ roSpan = roSpan.Slice (0, dot > 0 ? dot : 0);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ILLink.RoslynAnalyzer/Resources.resx b/src/ILLink.RoslynAnalyzer/Resources.resx
new file mode 100644
index 000000000000..7b279fa4d9a6
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/Resources.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ RequiresUnreferencedCodeAnalyzer
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/ValueUsageInfo.cs b/src/ILLink.RoslynAnalyzer/ValueUsageInfo.cs
new file mode 100644
index 000000000000..d7bb1a2771b8
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/ValueUsageInfo.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+// Copied from Roslyn
+namespace ILLink.RoslynAnalyzer
+{
+ [Flags]
+ internal enum ValueUsageInfo
+ {
+ ///
+ /// Represents default value indicating no usage.
+ ///
+ None = 0x0,
+
+ ///
+ /// Represents a value read.
+ /// For example, reading the value of a local/field/parameter.
+ ///
+ Read = 0x1,
+
+ ///
+ /// Represents a value write.
+ /// For example, assigning a value to a local/field/parameter.
+ ///
+ Write = 0x2,
+
+ ///
+ /// Represents a reference being taken for the symbol.
+ /// For example, passing an argument to an "in", "ref" or "out" parameter.
+ ///
+ Reference = 0x4,
+
+ ///
+ /// Represents a name-only reference that neither reads nor writes the underlying value.
+ /// For example, 'nameof(x)' or reference to a symbol 'x' in a documentation comment
+ /// does not read or write the underlying value stored in 'x'.
+ ///
+ Name = 0x8,
+
+ ///
+ /// Represents a value read and/or write.
+ /// For example, an increment or compound assignment operation.
+ ///
+ ReadWrite = Read | Write,
+
+ ///
+ /// Represents a readable reference being taken to the value.
+ /// For example, passing an argument to an "in" or "ref readonly" parameter.
+ ///
+ ReadableReference = Read | Reference,
+
+ ///
+ /// Represents a readable reference being taken to the value.
+ /// For example, passing an argument to an "out" parameter.
+ ///
+ WritableReference = Write | Reference,
+
+ ///
+ /// Represents a value read or write.
+ /// For example, passing an argument to a "ref" parameter.
+ ///
+ ReadableWritableReference = Read | Write | Reference
+ }
+
+ internal static class ValueUsageInfoExtensions
+ {
+ public static bool IsReadFrom (this ValueUsageInfo valueUsageInfo)
+ => (valueUsageInfo & ValueUsageInfo.Read) != 0;
+
+ public static bool IsWrittenTo (this ValueUsageInfo valueUsageInfo)
+ => (valueUsageInfo & ValueUsageInfo.Write) != 0;
+
+ public static bool IsNameOnly (this ValueUsageInfo valueUsageInfo)
+ => (valueUsageInfo & ValueUsageInfo.Name) != 0;
+
+ public static bool IsReference (this ValueUsageInfo valueUsageInfo)
+ => (valueUsageInfo & ValueUsageInfo.Reference) != 0;
+ }
+}
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.cs.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.cs.xlf
new file mode 100644
index 000000000000..75a240d8e302
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.cs.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.de.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.de.xlf
new file mode 100644
index 000000000000..c06e59183685
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.de.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.es.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.es.xlf
new file mode 100644
index 000000000000..ba4f5b3917b8
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.es.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.fr.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.fr.xlf
new file mode 100644
index 000000000000..ae016acd2695
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.fr.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.it.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.it.xlf
new file mode 100644
index 000000000000..a197d1497e4e
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.it.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.ja.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.ja.xlf
new file mode 100644
index 000000000000..c8d8a3de1ecb
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.ja.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.ko.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.ko.xlf
new file mode 100644
index 000000000000..480613dd57c9
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.ko.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.pl.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.pl.xlf
new file mode 100644
index 000000000000..4c39d8f02905
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.pl.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.pt-BR.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.pt-BR.xlf
new file mode 100644
index 000000000000..a969de028011
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.pt-BR.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.ru.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.ru.xlf
new file mode 100644
index 000000000000..91013bccf0e9
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.ru.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.tr.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.tr.xlf
new file mode 100644
index 000000000000..94d2d2c9f5af
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.tr.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hans.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hans.xlf
new file mode 100644
index 000000000000..7d95dcaf38db
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hans.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hant.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hant.xlf
new file mode 100644
index 000000000000..8264c1e1df72
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hant.xlf
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}.
+
+
+
+ RequiresUnreferencedCodeAnalyzer
+ RequiresUnreferencedCodeAnalyzer
+
+
+
+
+
\ No newline at end of file
diff --git a/test/ILLink.RoslynAnalyzer.Tests/AnalyzerTests.cs b/test/ILLink.RoslynAnalyzer.Tests/AnalyzerTests.cs
new file mode 100644
index 000000000000..04d87a85419e
--- /dev/null
+++ b/test/ILLink.RoslynAnalyzer.Tests/AnalyzerTests.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.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using Xunit;
+using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpAnalyzerVerifier<
+ ILLink.RoslynAnalyzer.RequiresUnreferencedCodeAnalyzer>;
+
+namespace ILLink.RoslynAnalyzer.Tests
+{
+ public class AnalyzerTests
+ {
+ [Fact]
+ public Task SimpleDiagnostic ()
+ {
+ var src = @"
+using System.Diagnostics.CodeAnalysis;
+
+class C
+{
+ [RequiresUnreferencedCodeAttribute(""message"")]
+ int M1() => 0;
+ int M2() => M1();
+}";
+ return VerifyCS.VerifyAnalyzerAsync (src,
+ // (8,17): warning IL2026: Calling 'System.Int32 C::M1()' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. message.
+ VerifyCS.Diagnostic ().WithSpan (8, 17, 8, 21).WithArguments ("C.M1()", "message")
+ );
+ }
+ }
+}
diff --git a/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj b/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj
new file mode 100644
index 000000000000..07aa8f3d710a
--- /dev/null
+++ b/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+
+ enable
+ latest
+ net5.0
+ $(DefineConstants);ILLINK
+
+
+
+
+
+
+
+
+
+
diff --git a/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs b/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs
new file mode 100644
index 000000000000..fe21ab18d3b3
--- /dev/null
+++ b/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Xunit;
+
+namespace ILLink.RoslynAnalyzer.Tests
+{
+ ///
+ /// Test cases stored in files
+ ///
+ public class LinkerTestCases : TestCaseUtils
+ {
+ [Theory]
+ [MemberData (nameof (GetTestData), parameters: nameof (RequiresCapability))]
+ public void RequiresCapability (MethodDeclarationSyntax m, List attrs)
+ {
+ switch (m.Identifier.ValueText) {
+ case "RequiresAndCallsOtherRequiresMethods":
+ case "TestRequiresWithMessageAndUrlOnMethod":
+ // Test failures because analyzer support is not complete
+ // Skip for now
+ return;
+ }
+
+ RunTest (m, attrs);
+ }
+ }
+}
diff --git a/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs b/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs
new file mode 100644
index 000000000000..8deb3f7d3f7f
--- /dev/null
+++ b/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs
@@ -0,0 +1,198 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Xunit;
+
+namespace ILLink.RoslynAnalyzer.Tests
+{
+ public class TestCaseUtils
+ {
+ public static IEnumerable