diff options
11 files changed, 213 insertions, 3 deletions
diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 3112fed3d..608505b60 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -2151,6 +2151,15 @@ namespace Mono.Linker.Steps { { TypeDefinition returnTypeDefinition = method.ReturnType.Resolve (); + if (!string.IsNullOrEmpty(_context.PInvokesListFile) && method.IsPInvokeImpl) { + _context.PInvokes.Add (new PInvokeInfo { + AssemblyName = method.DeclaringType.Module.Name, + EntryPoint = method.PInvokeInfo.EntryPoint, + FullName = method.FullName, + ModuleName = method.PInvokeInfo.Module.Name + }); + } + const bool includeStaticFields = false; if (returnTypeDefinition != null && !returnTypeDefinition.IsImport) { MarkDefaultConstructor (returnTypeDefinition); diff --git a/src/linker/Linker.Steps/OutputStep.cs b/src/linker/Linker.Steps/OutputStep.cs index 2adfefc35..06d2fba8e 100644 --- a/src/linker/Linker.Steps/OutputStep.cs +++ b/src/linker/Linker.Steps/OutputStep.cs @@ -30,7 +30,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; - +using System.Runtime.Serialization.Json; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.PE; @@ -76,6 +76,7 @@ namespace Mono.Linker.Steps { protected override void Process () { CheckOutputDirectory (); + OutputPInvokes (); Tracer.Finish (); } @@ -160,6 +161,19 @@ namespace Mono.Linker.Steps { } } + private void OutputPInvokes () + { + if (Context.PInvokesListFile == null) + return; + + using (var fs = File.Open (Path.Combine (Context.OutputDirectory, Context.PInvokesListFile), FileMode.Create)) { + Context.PInvokes.Distinct (); + Context.PInvokes.Sort (); + var jsonSerializer = new DataContractJsonSerializer (typeof (List<PInvokeInfo>)); + jsonSerializer.WriteObject (fs, Context.PInvokes); + } + } + protected virtual void DeleteAssembly(AssemblyDefinition assembly, string directory) { var target = GetAssemblyFileName (assembly, directory); diff --git a/src/linker/Linker/Driver.cs b/src/linker/Linker/Driver.cs index 8807a73dd..256bd2bbc 100644 --- a/src/linker/Linker/Driver.cs +++ b/src/linker/Linker/Driver.cs @@ -347,6 +347,12 @@ namespace Mono.Linker { continue; + case "--output-pinvokes": + if (!GetStringParam (token, l => context.PInvokesListFile = l)) + return false; + + continue; + case "--version": Version (); return true; @@ -872,6 +878,7 @@ namespace Mono.Linker { Console.WriteLine (" --ignore-descriptors Skips reading embedded descriptors (short -z). Defaults to false"); Console.WriteLine (" --keep-facades Keep assemblies with type-forwarders (short -t). Defaults to false"); Console.WriteLine (" --skip-unresolved Ignore unresolved types, methods, and assemblies. Defaults to false"); + Console.WriteLine (" --output-pinvokes PATH Output a JSON file with all modules and entry points of the P/Invokes found"); Console.WriteLine (); Console.WriteLine ("Linking"); diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index be851f014..4d6231967 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -120,6 +120,10 @@ namespace Mono.Linker { public List<string> Substitutions { get; private set; } + public List<PInvokeInfo> PInvokes { get; private set; } + + public string PInvokesListFile; + public System.Collections.IDictionary Actions { get { return _actions; } } @@ -195,6 +199,7 @@ namespace Mono.Linker { ReflectionPatternRecorder = new LoggingReflectionPatternRecorder (this); MarkedKnownMembers = new KnownMembers (); StripResources = true; + PInvokes = new List<PInvokeInfo> (); // See https://github.com/mono/linker/issues/612 const CodeOptimizations defaultOptimizations = diff --git a/src/linker/Linker/PInvokeInfo.cs b/src/linker/Linker/PInvokeInfo.cs new file mode 100644 index 000000000..e9f12fd11 --- /dev/null +++ b/src/linker/Linker/PInvokeInfo.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.Serialization; + +namespace Mono.Linker +{ + [DataContract] + public class PInvokeInfo : IComparable + { + [DataMember (Name = "assembly")] + internal string AssemblyName { get; set; } + + [DataMember (Name = "entryPoint")] + internal string EntryPoint { get; set; } + + [DataMember (Name = "fullName")] + internal string FullName { get; set; } + + [DataMember (Name = "moduleName")] + internal string ModuleName { get; set; } + + public int CompareTo (object obj) + { + if (obj == null) return 1; + + PInvokeInfo compareTo = obj as PInvokeInfo; + int compareField = string.Compare (this.AssemblyName, compareTo.AssemblyName, StringComparison.Ordinal); + if (compareField != 0) return compareField; + + compareField = string.Compare (this.ModuleName, compareTo.ModuleName, StringComparison.Ordinal); + if (compareField != 0) return compareField; + + compareField = string.Compare (this.FullName, compareTo.FullName, StringComparison.Ordinal); + if (compareField != 0) return compareField; + + return string.Compare (this.EntryPoint, compareTo.EntryPoint, StringComparison.Ordinal); + } + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/CanOutputPInvokes.cs b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/CanOutputPInvokes.cs new file mode 100644 index 000000000..7cb2ed65f --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/CanOutputPInvokes.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; +using Mono.Linker.Tests.Cases.Expectations.Metadata; +using Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.Dependencies; + +namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Individual +{ + [SetupLinkerAction ("copy", "copyassembly")] + [SetupLinkerAction ("link", "linkassembly")] + [SetupCompileBefore ("copyassembly.dll", new [] { typeof (CanOutputPInvokes_CopyAssembly) })] + [SetupCompileBefore ("linkassembly.dll", new [] { typeof (CanOutputPInvokes_LinkAssembly) })] + [SetupLinkerArgument ("--output-pinvokes", new [] { "pinvokes.json" })] + + public class CanOutputPInvokes + { + public static void Main () + { + var foo = FooEntryPoint (); + var bar = CustomEntryPoint (); + var baz = CustomEntryPoint0 (); + + var copyAssembly = new CanOutputPInvokes_CopyAssembly (); + } + + class Foo + { + public Foo () + { + } + } + + [DllImport ("lib")] + private static extern Foo FooEntryPoint (); + + [DllImport ("lib", EntryPoint = "CustomEntryPoint")] + private static extern Foo CustomEntryPoint (); + + [DllImport ("lib", EntryPoint = "CustomEntryPoint")] + private static extern Foo CustomEntryPoint0 (); + + [DllImport ("lib")] + private static extern Foo UnreachableDllImport (); + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/Dependencies/CanOutputPInvokes_CopyAssembly.cs b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/Dependencies/CanOutputPInvokes_CopyAssembly.cs new file mode 100644 index 000000000..905cd323e --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/Dependencies/CanOutputPInvokes_CopyAssembly.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.Dependencies +{ + public class CanOutputPInvokes_CopyAssembly + { + public CanOutputPInvokes_CopyAssembly () + { + var foo = FooEntryPoint (); + var bar = CustomEntryPoint (); + } + + [DllImport ("lib_copyassembly")] + private static extern CanOutputPInvokes_CopyAssembly FooEntryPoint (); + + [DllImport ("lib_copyassembly", EntryPoint = "CustomEntryPoint")] + private static extern CanOutputPInvokes_CopyAssembly CustomEntryPoint (); + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/Dependencies/CanOutputPInvokes_LinkAssembly.cs b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/Dependencies/CanOutputPInvokes_LinkAssembly.cs new file mode 100644 index 000000000..1bee84379 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Individual/Dependencies/CanOutputPInvokes_LinkAssembly.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.Dependencies +{ + public class CanOutputPInvokes_LinkAssembly + { + [DllImport ("lib_linkassembly")] + private static extern CanOutputPInvokes_LinkAssembly UnreachableWhenLinked (); + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj b/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj index e4b13a53c..2e93533ee 100644 --- a/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj +++ b/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj @@ -64,6 +64,12 @@ <PackageReference Include="NUnit3TestAdapter" Version="3.10.0" /> </ItemGroup> + <ItemGroup> + <None Update="TestCases\Dependencies\PInvokesExpectations.json"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + </ItemGroup> + <Target Name="RunTestsOnMono" Condition="'$(MonoBuild)' != ''"> <Exec Command="mono $(PkgNUnit_ConsoleRunner)/tools/nunit3-console.exe --result=TestResults.xml $(OutputPath)Mono.Linker.Tests.dll" /> </Target> @@ -72,8 +78,7 @@ <!-- Arcade's custom test imports assume that we are using xunit. --> <!-- Map the Arcade "Test" target to the "VSTest" target used by "dotnet test" --> - <Target Name="Test" - Condition="'$(MonoBuild)' == ''"> + <Target Name="Test" Condition="'$(MonoBuild)' == ''"> <MSBuild Projects="$(MSBuildProjectFile)" Targets="VSTest" /> </Target> diff --git a/test/Mono.Linker.Tests/TestCases/Dependencies/PInvokesExpectations.json b/test/Mono.Linker.Tests/TestCases/Dependencies/PInvokesExpectations.json new file mode 100644 index 000000000..872915eca --- /dev/null +++ b/test/Mono.Linker.Tests/TestCases/Dependencies/PInvokesExpectations.json @@ -0,0 +1,32 @@ +[ + { + "assembly": "copyassembly.dll", + "entryPoint": "CustomEntryPoint", + "fullName": "Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.Dependencies.CanOutputPInvokes_CopyAssembly Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.Dependencies.CanOutputPInvokes_CopyAssembly::CustomEntryPoint()", + "moduleName": "lib_copyassembly" + }, + { + "assembly": "copyassembly.dll", + "entryPoint": "FooEntryPoint", + "fullName": "Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.Dependencies.CanOutputPInvokes_CopyAssembly Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.Dependencies.CanOutputPInvokes_CopyAssembly::FooEntryPoint()", + "moduleName": "lib_copyassembly" + }, + { + "assembly": "test.exe", + "entryPoint": "CustomEntryPoint", + "fullName": "Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.CanOutputPInvokes\/Foo Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.CanOutputPInvokes::CustomEntryPoint()", + "moduleName": "lib" + }, + { + "assembly": "test.exe", + "entryPoint": "CustomEntryPoint", + "fullName": "Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.CanOutputPInvokes\/Foo Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.CanOutputPInvokes::CustomEntryPoint0()", + "moduleName": "lib" + }, + { + "assembly": "test.exe", + "entryPoint": "FooEntryPoint", + "fullName": "Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.CanOutputPInvokes\/Foo Mono.Linker.Tests.Cases.Interop.PInvoke.Individual.CanOutputPInvokes::FooEntryPoint()", + "moduleName": "lib" + } +]
\ No newline at end of file diff --git a/test/Mono.Linker.Tests/TestCases/IndividualTests.cs b/test/Mono.Linker.Tests/TestCases/IndividualTests.cs index 439caf37e..7ded3ee80 100644 --- a/test/Mono.Linker.Tests/TestCases/IndividualTests.cs +++ b/test/Mono.Linker.Tests/TestCases/IndividualTests.cs @@ -1,12 +1,18 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Json; using System.Xml; using Mono.Cecil; using Mono.Linker.Tests.Cases.CommandLine.Mvid; +using Mono.Linker.Tests.Cases.Interop.PInvoke.Individual; using Mono.Linker.Tests.Cases.References.Individual; using Mono.Linker.Tests.Cases.Tracing.Individual; using Mono.Linker.Tests.Extensions; using Mono.Linker.Tests.TestCasesRunner; using NUnit.Framework; +using NUnit.Framework.Internal; namespace Mono.Linker.Tests.TestCases { @@ -27,6 +33,28 @@ namespace Mono.Linker.Tests.TestCases } [Test] + public void CanOutputPInvokes () + { + var testcase = CreateIndividualCase (typeof (CanOutputPInvokes)); + var result = Run (testcase); + + var outputPath = result.OutputAssemblyPath.Parent.Combine ("pinvokes.json"); + if (!outputPath.Exists ()) + Assert.Fail ($"The json file with the list of all the PInvokes found by the linker is missing. Expected it to exist at {outputPath}"); + + var jsonSerializer = new DataContractJsonSerializer (typeof (List<PInvokeInfo>)); + + using (var fsActual = File.Open(outputPath, FileMode.Open)) + using (var fsExpected = File.Open("TestCases/Dependencies/PInvokesExpectations.json", FileMode.Open)) { + var actual = jsonSerializer.ReadObject (fsActual) as List<PInvokeInfo>; + var expected = jsonSerializer.ReadObject (fsExpected) as List<PInvokeInfo>; + foreach (var pinvokePair in Enumerable.Zip(actual, expected, (fst, snd) => Tuple.Create(fst, snd))) { + Assert.That (pinvokePair.Item1.CompareTo (pinvokePair.Item2), Is.EqualTo (0)); + } + } + } + + [Test] public void CanEnableDependenciesDump () { var testcase = CreateIndividualCase (typeof (CanEnableDependenciesDump)); |