Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Schuster <36744439+jtschuster@users.noreply.github.com>2022-08-15 22:07:47 +0300
committerGitHub <noreply@github.com>2022-08-15 22:07:47 +0300
commitc710e8af4224a3d0d2975ba5c2b09f0398ee2383 (patch)
tree70a4f2229c7e129fec32e259b4e84f64c45f1381
parent33c3b2c60d0a2006162a6326db853fe5415439bd (diff)
Add IL Verification to tests (#2960)
Adds an ILVerifier to check that the IL produced by the linker is valid. Unsafe C# produced unverifiable code, so we skip verification when we pass that flag to the compiler. Also, there are a few warnings that are produced by valid C# with new features like static abstract interface methods and ref fields and ref returns. In the future, it may be nice to add better error messages with the type, method name, and IL offset that produced the error, and perhaps an [ExpectedILVerifyError] attribute instead of filtering all of a type of error, but those are non-trivial to implement and don't occur in many tests (<10), so I haven't done that yet.
-rw-r--r--eng/Versions.props1
-rw-r--r--test/Mono.Linker.Tests/Mono.Linker.Tests.csproj3
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/ILVerifier.cs132
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs62
4 files changed, 194 insertions, 4 deletions
diff --git a/eng/Versions.props b/eng/Versions.props
index ea29c2eae..cef1af03c 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -25,6 +25,7 @@
<MicrosoftNetCompilersToolsetVersion>$(MicrosoftCodeAnalysisVersion)</MicrosoftNetCompilersToolsetVersion>
<MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion>1.0.1-beta1.*</MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion>
<MicrosoftCodeAnalysisBannedApiAnalyzersVersion>3.3.2</MicrosoftCodeAnalysisBannedApiAnalyzersVersion>
+ <MicrosoftILVerificationVersion>7.0.0-preview.7.22375.6</MicrosoftILVerificationVersion>
<!-- This controls the version of the cecil package, or the version of cecil in the project graph
when we build the cecil submodule. The reference assembly package will depend on this version of cecil.
Keep this in sync with ProjectInfo.cs in the submodule. -->
diff --git a/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj b/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj
index 4a9ea7e7f..448601a8a 100644
--- a/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj
+++ b/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -32,6 +32,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" />
+ <PackageReference Include="Microsoft.ILVerification" Version="$(MicrosoftILVerificationVersion)" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<!-- This reference is purely so that the linker can resolve this
diff --git a/test/Mono.Linker.Tests/TestCasesRunner/ILVerifier.cs b/test/Mono.Linker.Tests/TestCasesRunner/ILVerifier.cs
new file mode 100644
index 000000000..bf935cc22
--- /dev/null
+++ b/test/Mono.Linker.Tests/TestCasesRunner/ILVerifier.cs
@@ -0,0 +1,132 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.PortableExecutable;
+using System.Runtime.Loader;
+using ILVerify;
+using Mono.Linker.Tests.Extensions;
+
+#nullable enable
+namespace Mono.Linker.Tests.TestCasesRunner
+{
+ class ILVerifier : ILVerify.IResolver
+ {
+ Verifier _verifier;
+ NPath _assemblyFolder;
+ NPath _frameworkFolder;
+ Dictionary<string, PEReader> _assemblyCache;
+ AssemblyLoadContext _alc;
+
+ public IEnumerable<VerificationResult> Results { get; private set; }
+
+ public ILVerifier (NPath assemblyPath)
+ {
+ var assemblyName = assemblyPath.FileNameWithoutExtension;
+ _assemblyFolder = assemblyPath.Parent;
+ _assemblyCache = new Dictionary<string, PEReader> ();
+ _frameworkFolder = typeof (object).Assembly.Location.ToNPath ().Parent;
+ _alc = new AssemblyLoadContext (_assemblyFolder.FileName);
+ LoadAssembly ("mscorlib");
+ LoadAssembly ("System.Private.CoreLib");
+ LoadAssemblyFromPath (assemblyName, assemblyPath);
+
+ _verifier = new ILVerify.Verifier (
+ this,
+ new ILVerify.VerifierOptions {
+ SanityChecks = true,
+ IncludeMetadataTokensInErrorMessages = true
+ });
+ _verifier.SetSystemModuleName (new AssemblyName ("mscorlib"));
+
+ var allResults = _verifier.Verify (Resolve (assemblyName))
+ ?? Enumerable.Empty<VerificationResult> ();
+
+ Results = allResults.Where (r => r.Code switch {
+ ILVerify.VerifierError.None
+ // Static interface methods cause this warning
+ or ILVerify.VerifierError.CallAbstract
+ // "Missing callVirt after constrained prefix - static interface methods cause this warning
+ or ILVerify.VerifierError.Constrained
+ // ex. localloc cannot be statically verified by ILVerify
+ or ILVerify.VerifierError.Unverifiable
+ // ref returning a ref local causes this warning but is okay
+ or VerifierError.ReturnPtrToStack
+ // Span indexing with indexer (ex. span[^4]) causes this warning
+ or VerifierError.InitOnly
+ => false,
+ _ => true
+ });
+ }
+
+ PEReader LoadAssembly (string assemblyName)
+ {
+ if (_assemblyCache.TryGetValue (assemblyName, out PEReader? reader))
+ return reader;
+ var assembly = _alc.LoadFromAssemblyName (new AssemblyName (assemblyName));
+ reader = new PEReader (File.OpenRead (assembly.Location));
+ _assemblyCache.Add (assemblyName, reader);
+ return reader;
+ }
+
+ PEReader LoadAssemblyFromPath (string assemblyName, NPath pathToAssembly)
+ {
+ if (_assemblyCache.TryGetValue (assemblyName, out PEReader? reader))
+ return reader;
+ var assembly = _alc.LoadFromAssemblyPath (pathToAssembly);
+ reader = new PEReader (File.OpenRead (assembly.Location));
+ _assemblyCache.Add (assemblyName, reader);
+ return reader;
+ }
+
+ bool TryLoadAssemblyFromFolder (string assemblyName, NPath folder, [NotNullWhen (true)] out PEReader? peReader)
+ {
+ Assembly? assembly = null;
+ string assemblyPath = Path.Join (folder.ToString (), assemblyName);
+ if (File.Exists (assemblyPath + ".dll"))
+ assembly = _alc.LoadFromAssemblyPath (assemblyPath + ".dll");
+ else if (File.Exists (assemblyPath + ".exe"))
+ assembly = _alc.LoadFromAssemblyPath (assemblyPath + ".exe");
+
+ if (assembly is not null) {
+ peReader = new PEReader (File.OpenRead (assembly.Location));
+ _assemblyCache.Add (assemblyName, peReader);
+ return true;
+ }
+ peReader = null;
+ return false;
+ }
+
+ PEReader? Resolve (string assemblyName)
+ {
+ PEReader? reader;
+ if (_assemblyCache.TryGetValue (assemblyName, out reader)) {
+ return reader;
+ }
+
+ if (TryLoadAssemblyFromFolder (assemblyName, _frameworkFolder, out reader))
+ return reader;
+
+ if (TryLoadAssemblyFromFolder (assemblyName, _assemblyFolder, out reader))
+ return reader;
+
+ return null;
+ }
+
+ PEReader? ILVerify.IResolver.ResolveAssembly (AssemblyName assemblyName)
+ => Resolve (assemblyName.Name ?? assemblyName.FullName);
+
+ PEReader? ILVerify.IResolver.ResolveModule (AssemblyName referencingModule, string fileName)
+ => Resolve (Path.GetFileNameWithoutExtension (fileName));
+
+ public string GetErrorMessage (VerificationResult result)
+ {
+ return $"IL Verification error:\n{result.Message}";
+ }
+ }
+}
+#nullable restore
diff --git a/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
index bd611721c..ccb76575f 100644
--- a/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
+++ b/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
@@ -13,6 +14,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Mono.Linker.Tests.Cases.Expectations.Metadata;
using Mono.Linker.Tests.Extensions;
using NUnit.Framework;
+using WellKnownType = ILLink.Shared.TypeSystemProxy.WellKnownType;
namespace Mono.Linker.Tests.TestCasesRunner
{
@@ -47,6 +49,32 @@ namespace Mono.Linker.Tests.TestCasesRunner
_linkedReaderParameters = linkedReaderParameters;
}
+ static void VerifyIL (NPath pathToAssembly)
+ {
+ var verifier = new ILVerifier (pathToAssembly);
+ foreach (var result in verifier.Results) {
+ if (result.Code == ILVerify.VerifierError.None)
+ continue;
+ Assert.Fail (verifier.GetErrorMessage (result));
+ }
+ }
+
+ static bool ShouldValidateIL (AssemblyDefinition inputAssembly)
+ {
+ if (HasAttribute (inputAssembly, nameof (SkipPeVerifyAttribute)))
+ return false;
+
+ var caaIsUnsafeFlag = (CustomAttributeArgument caa) =>
+ caa.Type.IsTypeOf (WellKnownType.System_String)
+ && (string) caa.Value == "/unsafe";
+ var customAttributeHasUnsafeFlag = (CustomAttribute ca) => ca.ConstructorArguments.Any (caaIsUnsafeFlag);
+ if (GetCustomAttributes (inputAssembly, nameof (SetupCompileArgumentAttribute))
+ .Any (customAttributeHasUnsafeFlag))
+ return false;
+
+ return true;
+ }
+
public virtual void Check (LinkedTestCaseResult linkResult)
{
InitializeResolvers (linkResult);
@@ -57,6 +85,9 @@ namespace Mono.Linker.Tests.TestCasesRunner
Assert.IsTrue (linkResult.OutputAssemblyPath.FileExists (), $"The linked output assembly was not found. Expected at {linkResult.OutputAssemblyPath}");
var linked = ResolveLinkedAssembly (linkResult.OutputAssemblyPath.FileNameWithoutExtension);
+ if (ShouldValidateIL (original))
+ VerifyIL (linkResult.OutputAssemblyPath);
+
InitialChecking (linkResult, original, linked);
PerformOutputAssemblyChecks (original, linkResult.OutputAssemblyPath.Parent);
@@ -1070,14 +1101,39 @@ namespace Mono.Linker.Tests.TestCasesRunner
static bool HasAttribute (ICustomAttributeProvider caProvider, string attributeName)
{
+ return TryGetCustomAttribute (caProvider, attributeName, out var _);
+ }
+
+#nullable enable
+ static bool TryGetCustomAttribute (ICustomAttributeProvider caProvider, string attributeName, [NotNullWhen (true)] out CustomAttribute? customAttribute)
+ {
+ if (caProvider is AssemblyDefinition assembly && assembly.EntryPoint != null) {
+ customAttribute = assembly.EntryPoint.DeclaringType.CustomAttributes
+ .FirstOrDefault (attr => attr!.AttributeType.Name == attributeName, null);
+ return customAttribute is not null;
+ }
+
+ if (caProvider is TypeDefinition type) {
+ customAttribute = type.CustomAttributes
+ .FirstOrDefault (attr => attr!.AttributeType.Name == attributeName, null);
+ return customAttribute is not null;
+ }
+ customAttribute = null;
+ return false;
+ }
+
+ static IEnumerable<CustomAttribute> GetCustomAttributes (ICustomAttributeProvider caProvider, string attributeName )
+ {
if (caProvider is AssemblyDefinition assembly && assembly.EntryPoint != null)
return assembly.EntryPoint.DeclaringType.CustomAttributes
- .Any (attr => attr.AttributeType.Name == attributeName);
+ .Where (attr => attr!.AttributeType.Name == attributeName);
if (caProvider is TypeDefinition type)
- return type.CustomAttributes.Any (attr => attr.AttributeType.Name == attributeName);
+ return type.CustomAttributes
+ .Where (attr => attr!.AttributeType.Name == attributeName);
- return false;
+ return Enumerable.Empty<CustomAttribute> ();
}
+#nullable restore
}
}