From 3a8bd14a25c2b3b436b995521f645f17d41a394c Mon Sep 17 00:00:00 2001 From: Mike Voorhees Date: Fri, 14 Jul 2017 17:15:11 -0400 Subject: Run peverify on the output assemblies --- .../Assertions/SkipPeVerifyAttribute.cs | 25 ++++ .../Mono.Linker.Tests.Cases.Expectations.csproj | 1 + .../CopyOfCoreLibrariesKeepsUnusedTypes.cs | 3 + .../LinkingOfCoreLibrariesRemovesUnusedMethods.cs | 3 + .../LinkingOfCoreLibrariesRemovesUnusedTypes.cs | 3 + .../ReferencesAreRemovedWhenAllUsagesAreRemoved.cs | 2 + linker/Tests/Mono.Linker.Tests.csproj | 1 + linker/Tests/TestCasesRunner/PeVerifier.cs | 128 +++++++++++++++++++++ linker/Tests/TestCasesRunner/ResultChecker.cs | 8 +- 9 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/SkipPeVerifyAttribute.cs create mode 100644 linker/Tests/TestCasesRunner/PeVerifier.cs (limited to 'linker') diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/SkipPeVerifyAttribute.cs b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/SkipPeVerifyAttribute.cs new file mode 100644 index 000000000..eaccabd25 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/SkipPeVerifyAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions { + + public enum SkipPeVerifyForToolchian + { + Pedump + } + + [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)] + public class SkipPeVerifyAttribute : BaseExpectedLinkedBehaviorAttribute + { + public SkipPeVerifyAttribute () + { + } + + public SkipPeVerifyAttribute (SkipPeVerifyForToolchian toolchain) + { + } + + public SkipPeVerifyAttribute (string assemblyName) + { + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj index f4332245f..a18529218 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj @@ -49,6 +49,7 @@ + diff --git a/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/CopyOfCoreLibrariesKeepsUnusedTypes.cs b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/CopyOfCoreLibrariesKeepsUnusedTypes.cs index 25ee13f24..498c88378 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/CopyOfCoreLibrariesKeepsUnusedTypes.cs +++ b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/CopyOfCoreLibrariesKeepsUnusedTypes.cs @@ -9,6 +9,9 @@ namespace Mono.Linker.Tests.Cases.CoreLink // These types are normally removed when the core libraries are linked [KeptTypeInAssembly ("mscorlib.dll", typeof (ConsoleKeyInfo))] [KeptTypeInAssembly ("mscorlib.dll", typeof (System.Collections.ObjectModel.KeyedCollection<,>))] + + // Can be removed once this bug is fixed https://bugzilla.xamarin.com/show_bug.cgi?id=58168 + [SkipPeVerify (SkipPeVerifyForToolchian.Pedump)] class CopyOfCoreLibrariesKeepsUnusedTypes { public static void Main() diff --git a/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedMethods.cs b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedMethods.cs index 78b460939..5f2db3ddb 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedMethods.cs +++ b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedMethods.cs @@ -13,6 +13,9 @@ namespace Mono.Linker.Tests.Cases.CoreLink // We can't check everything that should be removed, but we should be able to check a few niche things that // we known should be removed which will at least verify that the core library was processed [RemovedMemberInAssembly ("mscorlib.dll", typeof (Stack), ".ctor(System.Collections.ICollection)")] + + // Can be removed once this bug is fixed https://bugzilla.xamarin.com/show_bug.cgi?id=58168 + [SkipPeVerify (SkipPeVerifyForToolchian.Pedump)] class LinkingOfCoreLibrariesRemovesUnusedMethods { public static void Main() { diff --git a/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedTypes.cs b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedTypes.cs index c4d447e89..3b92282bf 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedTypes.cs +++ b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/LinkingOfCoreLibrariesRemovesUnusedTypes.cs @@ -16,6 +16,9 @@ namespace Mono.Linker.Tests.Cases.CoreLink { [RemovedTypeInAssembly ("mscorlib.dll", typeof (System.Resources.ResourceWriter))] [RemovedTypeInAssembly ("System.dll", typeof (System.CodeDom.Compiler.CodeCompiler))] + + // Can be removed once this bug is fixed https://bugzilla.xamarin.com/show_bug.cgi?id=58168 + [SkipPeVerify (SkipPeVerifyForToolchian.Pedump)] class LinkingOfCoreLibrariesRemovesUnusedTypes { public static void Main () { diff --git a/linker/Tests/Mono.Linker.Tests.Cases/References/ReferencesAreRemovedWhenAllUsagesAreRemoved.cs b/linker/Tests/Mono.Linker.Tests.Cases/References/ReferencesAreRemovedWhenAllUsagesAreRemoved.cs index 58968df67..2cc3f5c0d 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/References/ReferencesAreRemovedWhenAllUsagesAreRemoved.cs +++ b/linker/Tests/Mono.Linker.Tests.Cases/References/ReferencesAreRemovedWhenAllUsagesAreRemoved.cs @@ -10,6 +10,8 @@ namespace Mono.Linker.Tests.Cases.References { [IncludeBlacklistStep("false")] [Reference ("System.dll")] [RemovedAssembly ("System.dll")] + // Can be removed once this bug is fixed https://bugzilla.xamarin.com/show_bug.cgi?id=58168 + [SkipPeVerify(SkipPeVerifyForToolchian.Pedump)] class ReferencesAreRemovedWhenAllUsagesAreRemoved { public static void Main () { diff --git a/linker/Tests/Mono.Linker.Tests.csproj b/linker/Tests/Mono.Linker.Tests.csproj index 7e1b44418..bb3565063 100644 --- a/linker/Tests/Mono.Linker.Tests.csproj +++ b/linker/Tests/Mono.Linker.Tests.csproj @@ -72,6 +72,7 @@ + diff --git a/linker/Tests/TestCasesRunner/PeVerifier.cs b/linker/Tests/TestCasesRunner/PeVerifier.cs new file mode 100644 index 000000000..388fa1e5d --- /dev/null +++ b/linker/Tests/TestCasesRunner/PeVerifier.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.Win32; +using Mono.Cecil; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Extensions; +using NUnit.Framework; + +namespace Mono.Linker.Tests.TestCasesRunner { + public class PeVerifier + { + private readonly string _peExecutable; + + public PeVerifier () + { + _peExecutable = Environment.OSVersion.Platform == PlatformID.Win32NT ? FindPeExecutableFromRegistry ().ToString () : "pedump"; + } + + public PeVerifier (string peExecutable) + { + _peExecutable = peExecutable; + } + + public virtual void Check (LinkedTestCaseResult linkResult, AssemblyDefinition original) + { + bool skipCheckEntirely; + HashSet assembliesToSkip; + ProcessSkipAttributes (linkResult, original, out skipCheckEntirely, out assembliesToSkip); + + if (skipCheckEntirely) + return; + + foreach (var file in linkResult.OutputAssemblyPath.Parent.Files ()) { + if (file.ExtensionWithDot != ".exe" && file.ExtensionWithDot != ".dll") + continue; + + // Always skip the I18N assemblies, for some reason they end up in the output directory on OSX. + // verification of these fails due to native pointers + if (file.FileName.StartsWith ("I18N")) + continue; + + if (assembliesToSkip.Contains (file.FileName)) + continue; + + CheckAssembly (file); + } + } + + private void ProcessSkipAttributes (LinkedTestCaseResult linkResult, AssemblyDefinition original, out bool skipCheckEntirely, out HashSet assembliesToSkip) + { + var peVerifyAttrs = original.MainModule.GetType (linkResult.TestCase.ReconstructedFullTypeName).CustomAttributes.Where (attr => attr.AttributeType.Name == nameof (SkipPeVerifyAttribute)); + skipCheckEntirely = false; + assembliesToSkip = new HashSet (); + foreach (var attr in peVerifyAttrs) { + var ctorArg = attr.ConstructorArguments.FirstOrDefault (); + + if (!attr.HasConstructorArguments) { + skipCheckEntirely = true; + } else if (ctorArg.Type.Name == nameof (SkipPeVerifyForToolchian)) { + var skipToolchain = (SkipPeVerifyForToolchian)ctorArg.Value; + + if (skipToolchain == SkipPeVerifyForToolchian.Pedump) { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + skipCheckEntirely = true; + } + else + throw new ArgumentException($"Unhandled platform and toolchain values of {Environment.OSVersion.Platform} and {skipToolchain}"); + } else if (ctorArg.Type.Name == nameof (String)) { + assembliesToSkip.Add ((string)ctorArg.Value); + } else { + throw new ArgumentException($"Unhandled constructor argument type of {ctorArg.Type} on {nameof (SkipPeVerifyAttribute)}"); + } + } + } + + private void CheckAssembly (NPath assemblyPath) + { + var capturedOutput = new List (); + var exeArgs = Environment.OSVersion.Platform == PlatformID.Win32NT ? $"/nologo {assemblyPath.InQuotes ()}" : $"--verify metadata,code {assemblyPath.InQuotes ()}"; + var process = new Process (); + process.StartInfo.FileName = _peExecutable; + process.StartInfo.Arguments = exeArgs; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + process.StartInfo.RedirectStandardOutput = true; + process.OutputDataReceived += (sender, args) => capturedOutput.Add (args.Data); + process.Start (); + process.BeginOutputReadLine (); + process.WaitForExit (); + + if (process.ExitCode != 0) { + Assert.Fail ($"Invalid IL detected in {assemblyPath}\n{capturedOutput.Aggregate ((buff, s) => buff + Environment.NewLine + s)}"); + } + } + + public static NPath FindPeExecutableFromRegistry () + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + throw new InvalidOperationException ("This method should only be called on windows"); + + var key = Registry.LocalMachine.OpenSubKey (@"SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows"); + foreach (var sdkKeyName in key.GetSubKeyNames ().OrderBy (name => new Version (name.TrimStart ('v').TrimEnd ('A'))).Reverse ()) { + var sdkKey = Registry.LocalMachine.OpenSubKey ($"SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\{sdkKeyName}"); + + var sdkDir = (string)sdkKey.GetValue ("InstallationFolder"); + if (string.IsNullOrEmpty (sdkDir)) + continue; + + var binDir = sdkDir.ToNPath ().Combine ("bin"); + + if (!binDir.Exists ()) + continue; + + foreach (var netSdkDirs in binDir.Directories ().OrderBy (dir => dir.FileName)) { + var peVerifyPath = netSdkDirs.Combine ("PEVerify.exe"); + + if (peVerifyPath.FileExists ()) + return peVerifyPath; + } + } + + throw new InvalidOperationException ("Could not locate a peverify.exe executable"); + } + } +} diff --git a/linker/Tests/TestCasesRunner/ResultChecker.cs b/linker/Tests/TestCasesRunner/ResultChecker.cs index 9abf9dc73..fcc175346 100644 --- a/linker/Tests/TestCasesRunner/ResultChecker.cs +++ b/linker/Tests/TestCasesRunner/ResultChecker.cs @@ -12,16 +12,18 @@ namespace Mono.Linker.Tests.TestCasesRunner { { readonly BaseAssemblyResolver _originalsResolver; readonly BaseAssemblyResolver _linkedResolver; + readonly PeVerifier _peVerifier; public ResultChecker () - : this(new DefaultAssemblyResolver (), new DefaultAssemblyResolver ()) + : this(new DefaultAssemblyResolver (), new DefaultAssemblyResolver (), new PeVerifier ()) { } - public ResultChecker (BaseAssemblyResolver originalsResolver, BaseAssemblyResolver linkedResolver) + public ResultChecker (BaseAssemblyResolver originalsResolver, BaseAssemblyResolver linkedResolver, PeVerifier peVerifier) { _originalsResolver = originalsResolver; _linkedResolver = linkedResolver; + _peVerifier = peVerifier; } public virtual void Check (LinkedTestCaseResult linkResult) @@ -41,6 +43,8 @@ namespace Mono.Linker.Tests.TestCasesRunner { VerifyLinkingOfOtherAssemblies (original); + _peVerifier.Check (linkResult, original); + AdditionalChecking (linkResult, original, linked); } finally -- cgit v1.2.3