diff --git a/.github/workflows/IKVM.yml b/.github/workflows/IKVM.yml index aefcff7cec..e0229fe73d 100644 --- a/.github/workflows/IKVM.yml +++ b/.github/workflows/IKVM.yml @@ -639,8 +639,8 @@ jobs: "-v:diag", "--results-directory", "TestResults", "--logger:console;verbosity=diag", - "--logger:trx", - "--collect", "Code Coverage" + "--logger:trx" + # "--collect", "Code Coverage" ) $runsettings = $(gi .\tests\$tst\$tfm\*.runsettings) diff --git a/Directory.Build.targets b/Directory.Build.targets index 23ab5af2fd..0c55181af6 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -2,4 +2,9 @@ + + win-x86 + win-x64 + true + diff --git a/README.md b/README.md index c767b9ec6e..737e389f66 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ The output assembly will be generated as part of your project's build process an MyClass.java;YourClass.java SomeExternalDependency.jar;SomeOtherExternalDependency.jar MyAssemblyAlias;helloworld2_0 - true + portable ``` @@ -139,7 +139,7 @@ The following values can be used as either an attribute or a nested element of ` | `Compile` | A semi-colon separated list of Java class path items to compile into the assembly. By default this value is the `Identity` of the item, if the identity of the item is an existing JAR file or directory (not yet supported). MSBuild globs are supported to reference multiple JAR or .class files. | | `Sources` | A semi-colon separated list of Java source files to use during documentation generation. (not yet supported) | | `References` | Optional semi-colon separated list of other `IkvmReference` identity values to specify as a reference to the current one. For example, if `foo.jar` depends on `bar.jar`, include both as `IkvmReference` items, but specify the identity of `bar.jar` on the `References` metadata of `foo.jar`. | -| `Debug` | Optional boolean indicating whether to generate debug symbols. By default this is determined based on the `` and `` properties of the project. Only full debug symbols are currently supported. | +| `Debug` | Optional value indicating how to generate debug symbols. By default this is determined based on the `` properties of the project. Only full debug symbols are currently supported. | | `Aliases` | A semi-colon separated list of aliases that can be used to reference the assembly in `References`. | | `ClassLoader` | A fully qualified classs name of the custom ClassLoader implementation to use as a delegation parent. Examples include `ikvm.runtime.AppDomainAssemblyClassLoader` and `ikvm.runtime.ClassPathAssemblyClassLoader`. | | All other metadata supported on the [`Reference`](https://docs.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items#reference) MSBuild item group definition. | | diff --git a/src/IKVM.ConsoleApp/IKVM.ConsoleApp.csproj b/src/IKVM.ConsoleApp/IKVM.ConsoleApp.csproj index 81aca88366..37c5897649 100644 --- a/src/IKVM.ConsoleApp/IKVM.ConsoleApp.csproj +++ b/src/IKVM.ConsoleApp/IKVM.ConsoleApp.csproj @@ -1,7 +1,7 @@  Exe - net481;net6.0 + net6.0;net481; 11 x64 diff --git a/src/IKVM.ConsoleApp/Program.cs b/src/IKVM.ConsoleApp/Program.cs index af3b03e04f..087f8ae107 100644 --- a/src/IKVM.ConsoleApp/Program.cs +++ b/src/IKVM.ConsoleApp/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; namespace IKVM.ConsoleApp { @@ -13,7 +14,6 @@ public static void Main(string[] args) public static void Foo() { new Bar(); - Environment.Exit(0); } class Bar @@ -25,11 +25,6 @@ public Bar() System.Console.WriteLine(java.net.InetAddress.getLocalHost().getHostName()); } - ~Bar() - { - - } - } } diff --git a/src/IKVM.Java.Tests/java/lang/ThreadTests.java b/src/IKVM.Java.Tests/java/lang/ThreadTests.java new file mode 100644 index 0000000000..34cf4b712a --- /dev/null +++ b/src/IKVM.Java.Tests/java/lang/ThreadTests.java @@ -0,0 +1,16 @@ +package ikvm.tests.java.java.lang; + +import java.lang.*; +import java.util.*; + +@cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.Annotation() +public class ThreadTests { + + @cli.Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.Annotation() + public void canPrintStackTrace() throws Throwable { + for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { + System.out.println(ste + "\n"); + } + } + +} diff --git a/src/IKVM.Java/map.xml b/src/IKVM.Java/map.xml index d72c5b7863..7e8081ad74 100644 --- a/src/IKVM.Java/map.xml +++ b/src/IKVM.Java/map.xml @@ -952,7 +952,8 @@ - + + @@ -1010,7 +1011,7 @@ - + @@ -1019,7 +1020,7 @@ - + @@ -1341,18 +1342,29 @@ - + + - + + + + + + - + + + + + + @@ -1398,14 +1410,21 @@ - + + + + + + + - + + diff --git a/src/IKVM.MSBuild.Tasks/IkvmCompiler.cs b/src/IKVM.MSBuild.Tasks/IkvmCompiler.cs index 1c84a19ea7..e4526d74a7 100644 --- a/src/IKVM.MSBuild.Tasks/IkvmCompiler.cs +++ b/src/IKVM.MSBuild.Tasks/IkvmCompiler.cs @@ -100,7 +100,7 @@ public class IkvmCompiler : IkvmToolExecTask public bool CompressResources { get; set; } - public bool Debug { get; set; } + public string Debug { get; set; } public bool NoAutoSerialization { get; set; } @@ -241,12 +241,6 @@ public override bool Execute() protected override async Task ExecuteAsync(IkvmToolTaskDiagnosticWriter writer, CancellationToken cancellationToken) { - if (Debug && RuntimeInformation.IsOSPlatform(OSPlatform.Windows) == false) - { - Log.LogWarning("Emitting debug symbols from ikvmc is not supported on platforms other than Windows. Continuing without."); - Debug = false; - } - var options = new IkvmImporterOptions(); options.ResponseFile = ResponseFile; options.Output = Output; @@ -302,7 +296,16 @@ protected override async Task ExecuteAsync(IkvmToolTaskDiagnosticWriter wr options.ExternalResources.Add(new IkvmImporterExternalResourceItem(resource.ItemSpec, resource.GetMetadata("ResourcePath"))); options.CompressResources = CompressResources; - options.Debug = Debug; + + options.Debug = Debug?.ToLower() switch + { + "none" or "" or null => IkvmImporterDebugMode.None, + "portable" => IkvmImporterDebugMode.Portable, + "full" or "pdbonly" => IkvmImporterDebugMode.Full, + "embedded" => IkvmImporterDebugMode.Embedded, + _ => throw new NotImplementedException($"Unknown Debug option '{Debug}'.") + }; + options.NoAutoSerialization = NoAutoSerialization; options.NoGlobbing = NoGlobbing; options.NoJNI = NoJNI; diff --git a/src/IKVM.MSBuild.Tasks/IkvmReferenceItem.cs b/src/IKVM.MSBuild.Tasks/IkvmReferenceItem.cs index cddd1c597d..f1e900a330 100644 --- a/src/IKVM.MSBuild.Tasks/IkvmReferenceItem.cs +++ b/src/IKVM.MSBuild.Tasks/IkvmReferenceItem.cs @@ -62,7 +62,7 @@ public static IkvmReferenceItem[] Import(IEnumerable items) item.References = ResolveReferences(map, item, item.Item.GetMetadata(IkvmReferenceItemMetadata.References)).ToList(); item.ClassLoader = item.Item.GetMetadata(IkvmReferenceItemMetadata.ClassLoader); item.ResolvedReferences = item.Item.GetMetadata(IkvmReferenceItemMetadata.ResolvedReferences)?.Split(IkvmReferenceItemMetadata.PropertySeperatorCharArray, StringSplitOptions.RemoveEmptyEntries).ToList(); - item.Debug = string.Equals(item.Item.GetMetadata(IkvmReferenceItemMetadata.Debug), "true", StringComparison.OrdinalIgnoreCase); + item.Debug = ParseDebug(item.Item.GetMetadata(IkvmReferenceItemMetadata.Debug)); item.KeyFile = item.Item.GetMetadata(IkvmReferenceItemMetadata.KeyFile); item.DelaySign = string.Equals(item.Item.GetMetadata(IkvmReferenceItemMetadata.DelaySign), "true", StringComparison.OrdinalIgnoreCase); item.Aliases = item.Item.GetMetadata(IkvmReferenceItemMetadata.Aliases); @@ -79,6 +79,21 @@ public static IkvmReferenceItem[] Import(IEnumerable items) return map.Values.ToArray(); } + /// + /// Parses the debug metadata value into the enum. + /// + /// + /// + /// + static IkvmReferenceItemDebug ParseDebug(string debug) => debug?.ToLower() switch + { + "none" or "false" or "" or null => IkvmReferenceItemDebug.None, + "full" or "true" => IkvmReferenceItemDebug.Full, + "portable" => IkvmReferenceItemDebug.Portable, + "embedded" => IkvmReferenceItemDebug.Embedded, + _ => IkvmReferenceItemDebug.None + }; + /// /// Attempts to resolve the references given by the reference string for /// against . @@ -229,7 +244,7 @@ public IkvmReferenceItem(ITaskItem item) /// /// Compile in debug mode. /// - public bool Debug { get; set; } + public IkvmReferenceItemDebug Debug { get; set; } /// /// Path to the file to sign the assembly. @@ -279,7 +294,7 @@ public void Save() Item.SetMetadata(IkvmReferenceItemMetadata.StagePath, StagePath); Item.SetMetadata(IkvmReferenceItemMetadata.StageSymbolsPath, StageSymbolsPath); Item.SetMetadata(IkvmReferenceItemMetadata.Aliases, Aliases); - Item.SetMetadata(IkvmReferenceItemMetadata.Debug, Debug ? "true" : "false"); + Item.SetMetadata(IkvmReferenceItemMetadata.Debug, ToString(Debug)); Item.SetMetadata(IkvmReferenceItemMetadata.KeyFile, KeyFile); Item.SetMetadata(IkvmReferenceItemMetadata.DelaySign, DelaySign ? "true" : "false"); Item.SetMetadata(IkvmReferenceItemMetadata.Private, Private ? "true" : "false"); @@ -287,6 +302,21 @@ public void Save() Item.SetMetadata(IkvmReferenceItemMetadata.ResolvedReferences, string.Join(IkvmReferenceItemMetadata.PropertySeperatorString, ResolvedReferences)); } + /// + /// Converts the enum value to a string. + /// + /// + /// + /// + string ToString(IkvmReferenceItemDebug debug) => debug switch + { + IkvmReferenceItemDebug.None => "none", + IkvmReferenceItemDebug.Full => "full", + IkvmReferenceItemDebug.Portable => "portable", + IkvmReferenceItemDebug.Embedded => "embedded", + _ => throw new NotImplementedException(), + }; + } } diff --git a/src/IKVM.MSBuild.Tasks/IkvmReferenceItemDebug.cs b/src/IKVM.MSBuild.Tasks/IkvmReferenceItemDebug.cs new file mode 100644 index 0000000000..17ccf71863 --- /dev/null +++ b/src/IKVM.MSBuild.Tasks/IkvmReferenceItemDebug.cs @@ -0,0 +1,14 @@ +namespace IKVM.MSBuild.Tasks +{ + + enum IkvmReferenceItemDebug + { + + None, + Full, + Portable, + Embedded, + + } + +} \ No newline at end of file diff --git a/src/IKVM.MSBuild.Tasks/IkvmReferenceItemPrepare.cs b/src/IKVM.MSBuild.Tasks/IkvmReferenceItemPrepare.cs index 2fedec3e22..f7e3063781 100644 --- a/src/IKVM.MSBuild.Tasks/IkvmReferenceItemPrepare.cs +++ b/src/IKVM.MSBuild.Tasks/IkvmReferenceItemPrepare.cs @@ -500,7 +500,7 @@ internal async Task CalculateIkvmIdentityAsync(IkvmReferenceItem item, C manifest.WriteLine("AssemblyVersion={0}", item.AssemblyVersion); manifest.WriteLine("AssemblyFileVersion={0}", item.AssemblyFileVersion); manifest.WriteLine("ClassLoader={0}", item.ClassLoader); - manifest.WriteLine("Debug={0}", item.Debug ? "true" : "false"); + manifest.WriteLine("Debug={0}", Enum.GetName(typeof(IkvmReferenceItemDebug), item.Debug)); manifest.WriteLine("KeyFile={0}", string.IsNullOrWhiteSpace(item.KeyFile) == false ? await fileIdentityUtil.GetIdentityForFileAsync(item.KeyFile, Log, cancellationToken) : ""); manifest.WriteLine("DelaySign={0}", item.DelaySign ? "true" : "false"); diff --git a/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj b/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj index 9ac8a1ed1c..5bd05d0dbd 100644 --- a/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj +++ b/src/IKVM.MSBuild.Tests/IKVM.MSBuild.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/IKVM.MSBuild.Tests/Project/global.json b/src/IKVM.MSBuild.Tests/Project/global.json index 912bc7602d..857e6681f3 100644 --- a/src/IKVM.MSBuild.Tests/Project/global.json +++ b/src/IKVM.MSBuild.Tests/Project/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.0", + "version": "7.0.100", "rollForward": "latestFeature" } } diff --git a/src/IKVM.MSBuild.Tests/ProjectTests.cs b/src/IKVM.MSBuild.Tests/ProjectTests.cs index f1cd5cac4b..de88fb3c90 100644 --- a/src/IKVM.MSBuild.Tests/ProjectTests.cs +++ b/src/IKVM.MSBuild.Tests/ProjectTests.cs @@ -54,6 +54,8 @@ void OnAnyEventRaised(object sender, BuildEventArgs args) public static Dictionary Properties { get; set; } + public static string TestRoot { get; set; } + public static string TempRoot { get; set; } public static string WorkRoot { get; set; } @@ -70,6 +72,9 @@ public static void ClassInitialize(TestContext context) // properties to load into test build Properties = File.ReadAllLines("IKVM.MSBuild.Tests.properties").Select(i => i.Split('=', 2)).ToDictionary(i => i[0], i => i[1]); + // root of the project collection itself + TestRoot = Path.Combine(Path.GetDirectoryName(typeof(ProjectTests).Assembly.Location), "Project"); + // temporary directory TempRoot = Path.Combine(Path.GetTempPath(), "IKVM.MSBuild.Tests", Guid.NewGuid().ToString()); if (Directory.Exists(TempRoot)) @@ -102,10 +107,10 @@ public static void ClassInitialize(TestContext context) new XElement("add", new XAttribute("key", "dev"), new XAttribute("value", Path.Combine(Path.GetDirectoryName(typeof(ProjectTests).Assembly.Location), @"nuget")))))) - .Save(Path.Combine(@"Project", "nuget.config")); + .Save(Path.Combine(TestRoot, "nuget.config")); var manager = new AnalyzerManager(); - var analyzer = manager.GetProject(Path.Combine(@"Project", "Exe", "ProjectExe.csproj")); + var analyzer = manager.GetProject(Path.Combine(TestRoot, "Exe", "ProjectExe.csproj")); analyzer.AddBuildLogger(new TargetLogger(context)); analyzer.AddBinaryLogger(Path.Combine(WorkRoot, "msbuild.binlog")); analyzer.SetGlobalProperty("ImportDirectoryBuildProps", "false"); @@ -122,6 +127,7 @@ public static void ClassInitialize(TestContext context) analyzer.SetGlobalProperty("Configuration", "Release"); var options = new EnvironmentOptions(); + options.WorkingDirectory = TestRoot; options.DesignTime = false; options.Restore = true; options.TargetsToBuild.Clear(); @@ -205,7 +211,7 @@ public void CanBuildTestProject(EnvironmentPreference env, string tfm, string ri return; var manager = new AnalyzerManager(); - var analyzer = manager.GetProject(Path.Combine("Project", "Exe", "ProjectExe.csproj")); + var analyzer = manager.GetProject(Path.Combine(TestRoot, "Exe", "ProjectExe.csproj")); analyzer.AddBuildLogger(new TargetLogger(TestContext)); analyzer.AddBinaryLogger(Path.Combine(WorkRoot, $"{tfm}-{rid}-msbuild.binlog")); analyzer.SetGlobalProperty("ImportDirectoryBuildProps", "false"); @@ -222,6 +228,7 @@ public void CanBuildTestProject(EnvironmentPreference env, string tfm, string ri analyzer.SetGlobalProperty("Configuration", "Release"); var options = new EnvironmentOptions(); + options.WorkingDirectory = TestRoot; options.Preference = env; options.DesignTime = false; options.Restore = false; diff --git a/src/IKVM.NET.Sdk.Tests/IKVM.NET.Sdk.Tests.csproj b/src/IKVM.NET.Sdk.Tests/IKVM.NET.Sdk.Tests.csproj index 4b5dec7700..d0f8d5e694 100644 --- a/src/IKVM.NET.Sdk.Tests/IKVM.NET.Sdk.Tests.csproj +++ b/src/IKVM.NET.Sdk.Tests/IKVM.NET.Sdk.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/IKVM.NET.Sdk.Tests/ProjectTests.cs b/src/IKVM.NET.Sdk.Tests/ProjectTests.cs index e9e9e5db54..02af1a9de0 100644 --- a/src/IKVM.NET.Sdk.Tests/ProjectTests.cs +++ b/src/IKVM.NET.Sdk.Tests/ProjectTests.cs @@ -48,6 +48,8 @@ public override void Initialize(IEventSource eventSource) public static Dictionary Properties { get; set; } + public static string TestRoot { get; set; } + public static string TempRoot { get; set; } public static string WorkRoot { get; set; } @@ -64,6 +66,9 @@ public static void Init(TestContext context) // properties to load into test build Properties = File.ReadAllLines("IKVM.NET.Sdk.Tests.properties").Select(i => i.Split('=', 2)).ToDictionary(i => i[0], i => i[1]); + // root of the project collection itself + TestRoot = Path.Combine(Path.GetDirectoryName(typeof(ProjectTests).Assembly.Location), "Project"); + // temporary directory TempRoot = Path.Combine(Path.GetTempPath(), "IKVM.NET.Sdk.Tests", Guid.NewGuid().ToString()); if (Directory.Exists(TempRoot)) @@ -96,10 +101,10 @@ public static void Init(TestContext context) new XElement("add", new XAttribute("key", "dev"), new XAttribute("value", Path.Combine(Path.GetDirectoryName(typeof(ProjectTests).Assembly.Location), @"nuget")))))) - .Save(Path.Combine(@"Project", "nuget.config")); + .Save(Path.Combine(TestRoot, "nuget.config")); var manager = new AnalyzerManager(); - var analyzer = manager.GetProject(Path.Combine(@"Project", "Exe", "ProjectExe.msbuildproj")); + var analyzer = manager.GetProject(Path.Combine(TestRoot, "Exe", "ProjectExe.msbuildproj")); analyzer.AddBuildLogger(new TargetLogger(context)); analyzer.AddBinaryLogger(Path.Combine(WorkRoot, "msbuild.binlog")); analyzer.SetGlobalProperty("ImportDirectoryBuildProps", "false"); @@ -116,6 +121,7 @@ public static void Init(TestContext context) analyzer.SetGlobalProperty("Configuration", "Release"); var options = new EnvironmentOptions(); + options.WorkingDirectory = TestRoot; options.DesignTime = false; options.Restore = true; options.TargetsToBuild.Clear(); @@ -213,6 +219,7 @@ public void CanBuildTestProject(EnvironmentPreference env, string tfm, string ri analyzer.SetGlobalProperty("Configuration", "Release"); var options = new EnvironmentOptions(); + options.WorkingDirectory = TestRoot; options.Preference = env; options.DesignTime = false; options.Restore = false; diff --git a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets index 77f4914d54..713fbd42c7 100644 --- a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets +++ b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets @@ -235,7 +235,7 @@ Items = Items.OrderBy(i => i.ItemSpec).ToArray(); <_IkvmCompilerArgs Remove="@(_IkvmCompilerArgs)" /> <_IkvmCompilerArgs Include="-nologo" /> <_IkvmCompilerArgs Include="-bootstrap" Condition=" '$(Bootstrap)' == 'true' " /> - <_IkvmCompilerArgs Include="-debug" Condition=" '$(DebugSymbols)' == 'true' Or '$(DebugType)' != 'none' " /> + <_IkvmCompilerArgs Include="-debug:$(DebugType)" Condition=" '$(DebugType)' != 'none' " /> <_IkvmCompilerArgs Include="-assembly:$(AssemblyName)" /> <_IkvmCompilerArgs Include="-version:$(AssemblyVersion)" /> <_IkvmCompilerArgs Include="-runtime:$(IkvmRuntimeAssembly)" /> diff --git a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets index 99bf6d6c95..5eb16bc672 100644 --- a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets +++ b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets @@ -57,7 +57,7 @@ <_JavaCompilerDebug Condition=" '$(DebugSymbols)' == 'true' Or '$(DebugType)' != 'none' ">all - + - <_IkvmCompilerDebug Condition=" '$(DebugType)' != 'none' ">true <_IkvmCompilerExclude Condition="Exists('$(_ExcludeFilePath)')">$(_ExcludeFilePath) <_Target>$(OutputType.ToLowerInvariant()) <_Target Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netcoreapp3.1'))">library @@ -128,7 +127,7 @@ Target="$(_Target)" Platform="$(PlatformTarget.ToLowerInvariant())" Main="$(StartupObject)" - Debug="$(_IkvmCompilerDebug)" + Debug="$(DebugType)" KeyFile="$(KeyOriginatorFile)" CompressResources="$(CompressResources)" ClassLoader="$(ClassLoader)" diff --git a/src/IKVM.NET.Sdk/targets/IKVM.NET.Sdk.props b/src/IKVM.NET.Sdk/targets/IKVM.NET.Sdk.props index 641086a101..e007dcb58a 100644 --- a/src/IKVM.NET.Sdk/targets/IKVM.NET.Sdk.props +++ b/src/IKVM.NET.Sdk/targets/IKVM.NET.Sdk.props @@ -26,7 +26,6 @@ true true true - false diff --git a/src/IKVM.Reflection.Tests/ModuleWriterTests.cs b/src/IKVM.Reflection.Tests/ModuleWriterTests.cs index 73fd94e611..64f7f0e7d5 100644 --- a/src/IKVM.Reflection.Tests/ModuleWriterTests.cs +++ b/src/IKVM.Reflection.Tests/ModuleWriterTests.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; +using System.Text; using FluentAssertions; @@ -25,6 +29,7 @@ class VerifyResolver : ILVerify.IResolver { readonly TestAssemblyResolver resolver; + readonly ConcurrentDictionary cache = new ConcurrentDictionary(); /// /// Initializes a new instance. @@ -37,7 +42,7 @@ public VerifyResolver(TestAssemblyResolver resolver) public PEReader ResolveAssembly(System.Reflection.AssemblyName assemblyName) { - return resolver.Resolve(assemblyName.Name) is string s ? new PEReader(File.OpenRead(s)) : null; + return cache.GetOrAdd(assemblyName.Name, _ => resolver.Resolve(_) is string s ? new PEReader(File.OpenRead(s)) : null); } public PEReader ResolveModule(System.Reflection.AssemblyName referencingAssembly, string fileName) @@ -88,7 +93,22 @@ public ModuleWriterTests(ITestOutputHelper output) /// /// /// + /// bool Init(FrameworkSpec framework, out Universe universe, out TestAssemblyResolver resolver, out ILVerify.Verifier verifier, out string tempPath, out MetadataLoadContext tempLoad) + { + return Init(framework, null, out universe, out resolver, out verifier, out tempPath, out tempLoad); + } + + /// + /// Initializes the variables requires to execute tests. + /// + /// + /// + /// + /// + /// + bool Init(FrameworkSpec framework, IEnumerable searchPaths, out Universe universe, out TestAssemblyResolver resolver, out ILVerify.Verifier verifier, out string tempPath, out MetadataLoadContext tempLoad) { universe = null; resolver = null; @@ -110,8 +130,8 @@ bool Init(FrameworkSpec framework, out Universe universe, out TestAssemblyResolv // initialize primary classes universe = new Universe(DotNetSdkUtil.GetCoreLibName(framework.Tfm, framework.TargetFrameworkIdentifier, framework.TargetFrameworkVersion)); - resolver = new TestAssemblyResolver(universe, framework.Tfm, framework.TargetFrameworkIdentifier, framework.TargetFrameworkVersion); - verifier = new ILVerify.Verifier(new VerifyResolver(resolver), new ILVerify.VerifierOptions() { SanityChecks = true }); + resolver = new TestAssemblyResolver(universe, framework.Tfm, framework.TargetFrameworkIdentifier, framework.TargetFrameworkVersion, searchPaths); + verifier = new ILVerify.Verifier(new VerifyResolver(resolver), new ILVerify.VerifierOptions() { IncludeMetadataTokensInErrorMessages = true, SanityChecks = true }); verifier.SetSystemModuleName(new System.Reflection.AssemblyName(universe.CoreLibName)); tempLoad = new MetadataLoadContext(new MetadataAssemblyResolver(resolver)); @@ -464,6 +484,79 @@ public void CanWriteWindowsApplication(FrameworkSpec framework) t.Should().HaveMethod("Main", new[] { tempLoad.CoreAssembly.GetType("System.String").MakeArrayType() }).Which.Should().Return(tempLoad.CoreAssembly.GetType("System.Void")); } + [Theory] + [MemberData(nameof(FrameworkSpec.GetFrameworkTestData), MemberType = typeof(FrameworkSpec))] + public void CanWriteTryCatch(FrameworkSpec framework) + { + if (Init(framework, out var universe, out var resolver, out var verifier, out var tempPath, out var tempLoad) == false) + return; + + var assembly = universe.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Save, tempPath); + var module = assembly.DefineDynamicModule("Test", "Test.dll", false); + var type = module.DefineType("Type"); + + var execMethod = type.DefineMethod("Exec", MethodAttributes.Public, null, Array.Empty()); + var execMethodIL = execMethod.GetILGenerator(); + + var end = execMethodIL.BeginExceptionBlock(); + execMethodIL.Emit(OpCodes.Nop); + execMethodIL.BeginCatchBlock(universe.Import(typeof(Exception))); + execMethodIL.Emit(OpCodes.Nop); + execMethodIL.EndExceptionBlock(); + execMethodIL.Emit(OpCodes.Ret); + + type.CreateType(); + assembly.Save("Test.dll"); + + foreach (var v in verifier.Verify(new PEReader(File.OpenRead(Path.Combine(tempPath, "Test.dll"))))) + if (v.Code != ILVerify.VerifierError.None) + throw new Exception(string.Format(v.Message, v.Args ?? Array.Empty())); + } + + static string ToString(MetadataReader metadata, TypeDefinitionHandle typeHandle) + { + var b = new StringBuilder(); + + if (typeHandle.IsNil) + { + b.Append(""); + } + else + { + var type = metadata.GetTypeDefinition(typeHandle); + if (type.GetDeclaringType().IsNil) + { + var typeNamespace = type.Namespace.IsNil == false ? metadata.GetString(type.Namespace) : null; + var typeName = type.Name.IsNil == false ? metadata.GetString(type.Name) : null; + if (typeNamespace != null) + b.Append(typeNamespace).Append("."); + + b.Append(typeName ?? ""); + } + else + { + b.Append(ToString(metadata, type.GetDeclaringType())).Append("+"); + } + } + + return b.ToString(); + } + + static string ToString(MetadataReader metadata, TypeDefinitionHandle typeHandle, MethodDefinitionHandle methodHandle) + { + var b = new StringBuilder(); + var method = metadata.GetMethodDefinition(methodHandle); + typeHandle = method.GetDeclaringType(); + + b.Append(ToString(metadata, typeHandle)); + b.Append(":"); + + var methodName = method.Name.IsNil == false ? metadata.GetString(method.Name) : null; + b.Append(methodName ?? ""); + + return b.ToString(); + } + } } diff --git a/src/IKVM.Reflection.Tests/TestAssemblyResolver.cs b/src/IKVM.Reflection.Tests/TestAssemblyResolver.cs index 795a718f07..0fbf72999c 100644 --- a/src/IKVM.Reflection.Tests/TestAssemblyResolver.cs +++ b/src/IKVM.Reflection.Tests/TestAssemblyResolver.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.IO; using IKVM.Tests.Util; @@ -16,6 +18,7 @@ class TestAssemblyResolver readonly string tfm; readonly string targetFrameworkIdentifier; readonly string targetFrameworkVersion; + readonly IEnumerable dirs; /// /// Initializes a new instance. @@ -24,13 +27,15 @@ class TestAssemblyResolver /// /// /// + /// /// - public TestAssemblyResolver(Universe universe, string tfm, string targetFrameworkIdentifier, string targetFrameworkVersion) + public TestAssemblyResolver(Universe universe, string tfm, string targetFrameworkIdentifier, string targetFrameworkVersion, IEnumerable dirs = null) { this.universe = universe ?? throw new ArgumentNullException(nameof(universe)); this.tfm = tfm ?? throw new ArgumentNullException(nameof(tfm)); this.targetFrameworkIdentifier = targetFrameworkIdentifier ?? throw new ArgumentNullException(nameof(targetFrameworkIdentifier)); this.targetFrameworkVersion = targetFrameworkVersion ?? throw new ArgumentNullException(nameof(targetFrameworkVersion)); + this.dirs = dirs ?? Array.Empty(); universe.AssemblyResolve += (s, a) => UniverseAssemblyResolve(a.Name); } @@ -42,6 +47,15 @@ public TestAssemblyResolver(Universe universe, string tfm, string targetFramewor /// public string Resolve(string name) { + // check configured directories + foreach (var dir in dirs) + { + var p = Path.Combine(dir, name + ".dll"); + if (File.Exists(p)) + return p; + } + + // check reference assemblies foreach (var d in DotNetSdkUtil.GetPathToReferenceAssemblies(tfm, targetFrameworkIdentifier, targetFrameworkVersion)) { var p = Path.GetExtension(name) == ".dll" ? Path.Combine(d, name) : Path.Combine(d, name + ".dll"); diff --git a/src/IKVM.Reflection/ArrayType.cs b/src/IKVM.Reflection/ArrayType.cs index 4ac0258327..78f02ab4f4 100644 --- a/src/IKVM.Reflection/ArrayType.cs +++ b/src/IKVM.Reflection/ArrayType.cs @@ -48,32 +48,32 @@ internal static Type Make(Type type, CustomModifiers mods) public override Type BaseType { - get { return elementType.Module.universe.System_Array; } + get { return elementType.Module.Universe.System_Array; } } public override Type[] __GetDeclaredInterfaces() { return new Type[] { - Module.universe.Import(typeof(IList<>)).MakeGenericType(elementType), - Module.universe.Import(typeof(ICollection<>)).MakeGenericType(elementType), - Module.universe.Import(typeof(IEnumerable<>)).MakeGenericType(elementType) + Module.Universe.Import(typeof(IList<>)).MakeGenericType(elementType), + Module.Universe.Import(typeof(ICollection<>)).MakeGenericType(elementType), + Module.Universe.Import(typeof(IEnumerable<>)).MakeGenericType(elementType) }; } public override MethodBase[] __GetDeclaredMethods() { - var int32 = new Type[] { Module.universe.System_Int32 }; + var int32 = new Type[] { Module.Universe.System_Int32 }; var list = new List(); - list.Add(new BuiltinArrayMethod(Module, this, "Set", CallingConventions.Standard | CallingConventions.HasThis, Module.universe.System_Void, new Type[] { Module.universe.System_Int32, elementType })); + list.Add(new BuiltinArrayMethod(Module, this, "Set", CallingConventions.Standard | CallingConventions.HasThis, Module.Universe.System_Void, new Type[] { Module.Universe.System_Int32, elementType })); list.Add(new BuiltinArrayMethod(Module, this, "Address", CallingConventions.Standard | CallingConventions.HasThis, elementType.MakeByRefType(), int32)); list.Add(new BuiltinArrayMethod(Module, this, "Get", CallingConventions.Standard | CallingConventions.HasThis, elementType, int32)); - list.Add(new ConstructorInfoImpl(new BuiltinArrayMethod(Module, this, ".ctor", CallingConventions.Standard | CallingConventions.HasThis, Module.universe.System_Void, int32))); + list.Add(new ConstructorInfoImpl(new BuiltinArrayMethod(Module, this, ".ctor", CallingConventions.Standard | CallingConventions.HasThis, Module.Universe.System_Void, int32))); for (var type = elementType; type.__IsVector; type = type.GetElementType()) { Array.Resize(ref int32, int32.Length + 1); int32[int32.Length - 1] = int32[0]; - list.Add(new ConstructorInfoImpl(new BuiltinArrayMethod(Module, this, ".ctor", CallingConventions.Standard | CallingConventions.HasThis, Module.universe.System_Void, int32))); + list.Add(new ConstructorInfoImpl(new BuiltinArrayMethod(Module, this, ".ctor", CallingConventions.Standard | CallingConventions.HasThis, Module.Universe.System_Void, int32))); } return list.ToArray(); diff --git a/src/IKVM.Reflection/Assembly.cs b/src/IKVM.Reflection/Assembly.cs index 910918d01d..b77e7fa09c 100644 --- a/src/IKVM.Reflection/Assembly.cs +++ b/src/IKVM.Reflection/Assembly.cs @@ -30,7 +30,7 @@ namespace IKVM.Reflection public abstract class Assembly : ICustomAttributeProvider { - internal readonly Universe universe; + readonly Universe universe; protected string fullName; // AssemblyBuilder needs access to this field to clear it when the name changes protected List resolvers; @@ -40,9 +40,14 @@ public abstract class Assembly : ICustomAttributeProvider /// internal Assembly(Universe universe) { - this.universe = universe; + this.universe = universe ?? throw new ArgumentNullException(nameof(universe)); } + /// + /// Gets the universe of types that holds this assembly. + /// + public Universe Universe => universe; + public sealed override string ToString() { return FullName; @@ -62,8 +67,11 @@ public event ModuleResolveEventHandler ModuleResolve } public abstract Type[] GetTypes(); + public abstract AssemblyName GetName(); + public abstract string ImageRuntimeVersion { get; } + public abstract Module ManifestModule { get; } public abstract MethodInfo EntryPoint { get; } public abstract string Location { get; } diff --git a/src/IKVM.Reflection/AssemblyHashAlgorithm.cs b/src/IKVM.Reflection/AssemblyHashAlgorithm.cs index 97bce8f7e1..da5fe5bc4b 100644 --- a/src/IKVM.Reflection/AssemblyHashAlgorithm.cs +++ b/src/IKVM.Reflection/AssemblyHashAlgorithm.cs @@ -25,12 +25,15 @@ namespace IKVM.Reflection { public enum AssemblyHashAlgorithm - { + { - None = 0, - MD5 = 0x8003, - SHA1 = 0x8004, + None = 0, + MD5 = 0x8003, + SHA1 = 0x8004, + SHA256 = 0x800c, + SHA384 = 0x800d, + SHA512 = 0x800e, - } + } } diff --git a/src/IKVM.Reflection/AssemblyName.cs b/src/IKVM.Reflection/AssemblyName.cs index 5c8c1a67cd..7fe211a303 100644 --- a/src/IKVM.Reflection/AssemblyName.cs +++ b/src/IKVM.Reflection/AssemblyName.cs @@ -24,6 +24,7 @@ Jeroen Frijters using System; using System.Globalization; using System.IO; +using System.Reflection.Metadata; using System.Security.Cryptography; using System.Text; @@ -428,27 +429,37 @@ public static bool ReferenceMatchesDefinition(AssemblyName reference, AssemblyNa return System.Reflection.AssemblyName.ReferenceMatchesDefinition(new System.Reflection.AssemblyName(reference.FullName), new System.Reflection.AssemblyName(definition.FullName)); } - public static AssemblyName GetAssemblyName(string path) + /// + /// Gets the for a given file. + /// + /// + /// + /// + /// + public static AssemblyName GetAssemblyName(string assemblyFile) { try { - path = Path.GetFullPath(path); - using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - var module = new ModuleReader(null, null, fs, path, false); - if (module.Assembly == null) - throw new BadImageFormatException("Module does not contain a manifest"); + using var st = new FileStream(Path.GetFullPath(assemblyFile), FileMode.Open, FileAccess.Read, FileShare.Read); + using var pe = new System.Reflection.PortableExecutable.PEReader(st); + var mr = pe.GetMetadataReader(); - return module.Assembly.GetName(); - } + if (mr.IsAssembly == false) + throw new BadImageFormatException("Module does not contain a manifest"); + + var nm = mr.GetAssemblyDefinition().Name; + if (nm.IsNil) + throw new BadImageFormatException("Module does not contain a manifest"); + + return new AssemblyName(mr.GetString(nm)); } - catch (IOException x) + catch (IOException e) { - throw new FileNotFoundException(x.Message, x); + throw new FileNotFoundException(e.Message, e); } - catch (UnauthorizedAccessException x) + catch (UnauthorizedAccessException e) { - throw new FileNotFoundException(x.Message, x); + throw new FileNotFoundException(e.Message, e); } } diff --git a/src/IKVM.Reflection/ConstructorInfo.cs b/src/IKVM.Reflection/ConstructorInfo.cs index bb0b5222cb..b7594bee26 100644 --- a/src/IKVM.Reflection/ConstructorInfo.cs +++ b/src/IKVM.Reflection/ConstructorInfo.cs @@ -86,7 +86,12 @@ public sealed override ParameterInfo[] GetParameters() return parameters; } - public sealed override CallingConventions CallingConvention + internal override Type[] GetParameterTypes() + { + return GetMethodInfo().GetParameterTypes(); + } + + public sealed override CallingConventions CallingConvention { get { return GetMethodInfo().CallingConvention; } } diff --git a/src/IKVM.Reflection/CustomAttributeData.cs b/src/IKVM.Reflection/CustomAttributeData.cs index 01ccdd5984..7ac1afeb9d 100644 --- a/src/IKVM.Reflection/CustomAttributeData.cs +++ b/src/IKVM.Reflection/CustomAttributeData.cs @@ -222,7 +222,7 @@ public override string ToString() static void AppendValue(StringBuilder sb, Type type, CustomAttributeTypedArgument arg) { - if (arg.ArgumentType == arg.ArgumentType.Module.universe.System_String) + if (arg.ArgumentType == arg.ArgumentType.Module.Universe.System_String) { sb.Append('"').Append(arg.Value).Append('"'); } @@ -231,9 +231,9 @@ static void AppendValue(StringBuilder sb, Type type, CustomAttributeTypedArgumen var elementType = arg.ArgumentType.GetElementType(); string elementTypeName; if (elementType.IsPrimitive - || elementType == type.Module.universe.System_Object - || elementType == type.Module.universe.System_String - || elementType == type.Module.universe.System_Type) + || elementType == type.Module.Universe.System_Object + || elementType == type.Module.Universe.System_String + || elementType == type.Module.Universe.System_Type) { elementTypeName = elementType.Name; } @@ -276,7 +276,7 @@ internal static void ReadDeclarativeSecurity(Module module, int index, List(); - args.Add(new CustomAttributeNamedArgument(GetProperty(null, module.universe.System_Security_Permissions_PermissionSetAttribute, "XML", module.universe.System_String), new CustomAttributeTypedArgument(module.universe.System_String, xml))); + args.Add(new CustomAttributeNamedArgument(GetProperty(null, module.Universe.System_Security_Permissions_PermissionSetAttribute, "XML", module.Universe.System_String), new CustomAttributeTypedArgument(module.Universe.System_String, xml))); list.Add(new CustomAttributeData(asm.ManifestModule, ctor, new object[] { action }, args)); } } @@ -313,7 +313,7 @@ internal CustomAttributeData(Assembly asm, ConstructorInfo constructor, int secu this.lazyConstructor = constructor; var list = new List(); - list.Add(new CustomAttributeTypedArgument(constructor.Module.universe.System_Security_Permissions_SecurityAction, securityAction)); + list.Add(new CustomAttributeTypedArgument(constructor.Module.Universe.System_Security_Permissions_SecurityAction, securityAction)); this.lazyConstructorArguments = list.AsReadOnly(); this.declSecurityBlob = blob; } @@ -322,30 +322,30 @@ static Type ReadFieldOrPropType(Module context, ByteReader br) { return br.ReadByte() switch { - Signature.ELEMENT_TYPE_BOOLEAN => context.universe.System_Boolean, - Signature.ELEMENT_TYPE_CHAR => context.universe.System_Char, - Signature.ELEMENT_TYPE_I1 => context.universe.System_SByte, - Signature.ELEMENT_TYPE_U1 => context.universe.System_Byte, - Signature.ELEMENT_TYPE_I2 => context.universe.System_Int16, - Signature.ELEMENT_TYPE_U2 => context.universe.System_UInt16, - Signature.ELEMENT_TYPE_I4 => context.universe.System_Int32, - Signature.ELEMENT_TYPE_U4 => context.universe.System_UInt32, - Signature.ELEMENT_TYPE_I8 => context.universe.System_Int64, - Signature.ELEMENT_TYPE_U8 => context.universe.System_UInt64, - Signature.ELEMENT_TYPE_R4 => context.universe.System_Single, - Signature.ELEMENT_TYPE_R8 => context.universe.System_Double, - Signature.ELEMENT_TYPE_STRING => context.universe.System_String, + Signature.ELEMENT_TYPE_BOOLEAN => context.Universe.System_Boolean, + Signature.ELEMENT_TYPE_CHAR => context.Universe.System_Char, + Signature.ELEMENT_TYPE_I1 => context.Universe.System_SByte, + Signature.ELEMENT_TYPE_U1 => context.Universe.System_Byte, + Signature.ELEMENT_TYPE_I2 => context.Universe.System_Int16, + Signature.ELEMENT_TYPE_U2 => context.Universe.System_UInt16, + Signature.ELEMENT_TYPE_I4 => context.Universe.System_Int32, + Signature.ELEMENT_TYPE_U4 => context.Universe.System_UInt32, + Signature.ELEMENT_TYPE_I8 => context.Universe.System_Int64, + Signature.ELEMENT_TYPE_U8 => context.Universe.System_UInt64, + Signature.ELEMENT_TYPE_R4 => context.Universe.System_Single, + Signature.ELEMENT_TYPE_R8 => context.Universe.System_Double, + Signature.ELEMENT_TYPE_STRING => context.Universe.System_String, Signature.ELEMENT_TYPE_SZARRAY => ReadFieldOrPropType(context, br).MakeArrayType(), 0x55 => ReadType(context, br), - 0x50 => context.universe.System_Type, - 0x51 => context.universe.System_Object, + 0x50 => context.Universe.System_Type, + 0x51 => context.Universe.System_Object, _ => throw new BadImageFormatException(), }; } static CustomAttributeTypedArgument ReadFixedArg(Module context, ByteReader br, Type type) { - var u = context.universe; + var u = context.Universe; if (type == u.System_String) { return new CustomAttributeTypedArgument(type, br.ReadString()); @@ -441,7 +441,7 @@ static Type ReadType(Module context, ByteReader br) typeName = typeName.Substring(0, typeName.Length - 1); } - return TypeNameParser.Parse(typeName, true).GetType(context.universe, context, true, typeName, true, false); + return TypeNameParser.Parse(typeName, true).GetType(context.Universe, context, true, typeName, true, false); } static IList ReadConstructorArguments(Module context, ByteReader br, ConstructorInfo constructor) @@ -493,7 +493,7 @@ static FieldInfo GetField(Module context, Type type, string name, Type fieldType type = org; var sig = FieldSignature.Create(fieldType, new CustomModifiers()); - return type.FindField(name, sig) ?? type.Module.universe.GetMissingFieldOrThrow(context, type, name, sig); + return type.FindField(name, sig) ?? type.Module.Universe.GetMissingFieldOrThrow(context, type, name, sig); } static PropertyInfo GetProperty(Module context, Type type, string name, Type propertyType) @@ -508,7 +508,7 @@ static PropertyInfo GetProperty(Module context, Type type, string name, Type pro if (type == null) type = org; - return type.Module.universe.GetMissingPropertyOrThrow(context, type, name, PropertySignature.Create(CallingConventions.Standard | CallingConventions.HasThis, propertyType, null, new PackedCustomModifiers())); + return type.Module.Universe.GetMissingPropertyOrThrow(context, type, name, PropertySignature.Create(CallingConventions.Standard | CallingConventions.HasThis, propertyType, null, new PackedCustomModifiers())); } [Obsolete("Use AttributeType property instead.")] @@ -660,7 +660,7 @@ static object RewrapArray(Type type, CustomAttributeTypedArgument arg) for (int i = 0; i < arr.Length; i++) arr[i] = RewrapArray(elementType, list[i]); - if (type == type.Module.universe.System_Object) + if (type == type.Module.Universe.System_Object) return CustomAttributeBuilder.__MakeTypedArgument(arg.ArgumentType, arr); return arr; @@ -708,9 +708,9 @@ public static IList __GetCustomAttributes(ParameterInfo par { var module = parameter.Module; List list = null; - if (module.universe.ReturnPseudoCustomAttributes) + if (module.Universe.ReturnPseudoCustomAttributes) { - if (attributeType == null || attributeType.IsAssignableFrom(parameter.Module.universe.System_Runtime_InteropServices_MarshalAsAttribute)) + if (attributeType == null || attributeType.IsAssignableFrom(parameter.Module.Universe.System_Runtime_InteropServices_MarshalAsAttribute)) { if (parameter.__TryGetFieldMarshal(out var spec)) { @@ -770,7 +770,7 @@ public static IList __GetCustomAttributes(MemberInfo member static List GetCustomAttributesImpl(List list, MemberInfo member, Type attributeType) { - if (member.Module.universe.ReturnPseudoCustomAttributes) + if (member.Module.Universe.ReturnPseudoCustomAttributes) { var pseudo = member.GetPseudoCustomAttributes(attributeType); if (list == null) @@ -840,7 +840,7 @@ public static IList __GetDeclarativeSecurity(MethodBase met private static bool IsInheritableAttribute(Type attribute) { - var attributeUsageAttribute = attribute.Module.universe.System_AttributeUsageAttribute; + var attributeUsageAttribute = attribute.Module.Universe.System_AttributeUsageAttribute; var attr = __GetCustomAttributes(attribute, attributeUsageAttribute, false); if (attr.Count != 0) foreach (CustomAttributeNamedArgument named in attr[0].NamedArguments) @@ -872,14 +872,14 @@ internal static CustomAttributeData CreateDllImportPseudoCustomAttribute(Module }; var list = new List(); - var type = module.universe.System_Runtime_InteropServices_DllImportAttribute; - var constructor = type.GetPseudoCustomAttributeConstructor(module.universe.System_String); + var type = module.Universe.System_Runtime_InteropServices_DllImportAttribute; + var constructor = type.GetPseudoCustomAttributeConstructor(module.Universe.System_String); AddNamedArgument(list, type, "EntryPoint", entryPoint); - AddNamedArgument(list, type, "CharSet", module.universe.System_Runtime_InteropServices_CharSet, (int)charSet); + AddNamedArgument(list, type, "CharSet", module.Universe.System_Runtime_InteropServices_CharSet, (int)charSet); AddNamedArgument(list, type, "ExactSpelling", (int)flags, (int)ImplMapFlags.NoMangle); AddNamedArgument(list, type, "SetLastError", (int)flags, (int)ImplMapFlags.SupportsLastError); AddNamedArgument(list, type, "PreserveSig", (int)attr, (int)MethodImplAttributes.PreserveSig); - AddNamedArgument(list, type, "CallingConvention", module.universe.System_Runtime_InteropServices_CallingConvention, (int)callingConvention); + AddNamedArgument(list, type, "CallingConvention", module.Universe.System_Runtime_InteropServices_CallingConvention, (int)callingConvention); AddNamedArgument(list, type, "BestFitMapping", (int)flags, (int)ImplMapFlags.BestFitOn); AddNamedArgument(list, type, "ThrowOnUnmappableChar", (int)flags, (int)ImplMapFlags.CharMapErrorOn); return new CustomAttributeData(module, constructor, new object[] { dllName }, list); @@ -887,24 +887,24 @@ internal static CustomAttributeData CreateDllImportPseudoCustomAttribute(Module internal static CustomAttributeData CreateMarshalAsPseudoCustomAttribute(Module module, FieldMarshal fm) { - var typeofMarshalAs = module.universe.System_Runtime_InteropServices_MarshalAsAttribute; - var typeofUnmanagedType = module.universe.System_Runtime_InteropServices_UnmanagedType; - var typeofVarEnum = module.universe.System_Runtime_InteropServices_VarEnum; - var typeofType = module.universe.System_Type; + var typeofMarshalAs = module.Universe.System_Runtime_InteropServices_MarshalAsAttribute; + var typeofUnmanagedType = module.Universe.System_Runtime_InteropServices_UnmanagedType; + var typeofVarEnum = module.Universe.System_Runtime_InteropServices_VarEnum; + var typeofType = module.Universe.System_Type; var named = new List(); AddNamedArgument(named, typeofMarshalAs, "ArraySubType", typeofUnmanagedType, (int)(fm.ArraySubType ?? 0)); - AddNamedArgument(named, typeofMarshalAs, "SizeParamIndex", module.universe.System_Int16, fm.SizeParamIndex ?? 0); - AddNamedArgument(named, typeofMarshalAs, "SizeConst", module.universe.System_Int32, fm.SizeConst ?? 0); - AddNamedArgument(named, typeofMarshalAs, "IidParameterIndex", module.universe.System_Int32, fm.IidParameterIndex ?? 0); + AddNamedArgument(named, typeofMarshalAs, "SizeParamIndex", module.Universe.System_Int16, fm.SizeParamIndex ?? 0); + AddNamedArgument(named, typeofMarshalAs, "SizeConst", module.Universe.System_Int32, fm.SizeConst ?? 0); + AddNamedArgument(named, typeofMarshalAs, "IidParameterIndex", module.Universe.System_Int32, fm.IidParameterIndex ?? 0); AddNamedArgument(named, typeofMarshalAs, "SafeArraySubType", typeofVarEnum, (int)(fm.SafeArraySubType ?? 0)); if (fm.SafeArrayUserDefinedSubType != null) AddNamedArgument(named, typeofMarshalAs, "SafeArrayUserDefinedSubType", typeofType, fm.SafeArrayUserDefinedSubType); if (fm.MarshalType != null) - AddNamedArgument(named, typeofMarshalAs, "MarshalType", module.universe.System_String, fm.MarshalType); + AddNamedArgument(named, typeofMarshalAs, "MarshalType", module.Universe.System_String, fm.MarshalType); if (fm.MarshalTypeRef != null) - AddNamedArgument(named, typeofMarshalAs, "MarshalTypeRef", module.universe.System_Type, fm.MarshalTypeRef); + AddNamedArgument(named, typeofMarshalAs, "MarshalTypeRef", module.Universe.System_Type, fm.MarshalTypeRef); if (fm.MarshalCookie != null) - AddNamedArgument(named, typeofMarshalAs, "MarshalCookie", module.universe.System_String, fm.MarshalCookie); + AddNamedArgument(named, typeofMarshalAs, "MarshalCookie", module.Universe.System_String, fm.MarshalCookie); var constructor = typeofMarshalAs.GetPseudoCustomAttributeConstructor(typeofUnmanagedType); return new CustomAttributeData(module, constructor, new object[] { (int)fm.UnmanagedType }, named); @@ -912,12 +912,12 @@ internal static CustomAttributeData CreateMarshalAsPseudoCustomAttribute(Module static void AddNamedArgument(List list, Type type, string fieldName, string value) { - AddNamedArgument(list, type, fieldName, type.Module.universe.System_String, value); + AddNamedArgument(list, type, fieldName, type.Module.Universe.System_String, value); } static void AddNamedArgument(List list, Type type, string fieldName, int flags, int flagMask) { - AddNamedArgument(list, type, fieldName, type.Module.universe.System_Boolean, (flags & flagMask) != 0); + AddNamedArgument(list, type, fieldName, type.Module.Universe.System_Boolean, (flags & flagMask) != 0); } static void AddNamedArgument(List list, Type attributeType, string fieldName, Type valueType, object value) @@ -930,14 +930,14 @@ static void AddNamedArgument(List list, Type attri internal static CustomAttributeData CreateFieldOffsetPseudoCustomAttribute(Module module, int offset) { - var type = module.universe.System_Runtime_InteropServices_FieldOffsetAttribute; - var constructor = type.GetPseudoCustomAttributeConstructor(module.universe.System_Int32); + var type = module.Universe.System_Runtime_InteropServices_FieldOffsetAttribute; + var constructor = type.GetPseudoCustomAttributeConstructor(module.Universe.System_Int32); return new CustomAttributeData(module, constructor, new object[] { offset }, null); } internal static CustomAttributeData CreatePreserveSigPseudoCustomAttribute(Module module) { - var type = module.universe.System_Runtime_InteropServices_PreserveSigAttribute; + var type = module.Universe.System_Runtime_InteropServices_PreserveSigAttribute; var constructor = type.GetPseudoCustomAttributeConstructor(); return new CustomAttributeData(module, constructor, Array.Empty(), null); } diff --git a/src/IKVM.Reflection/Diagnostics/IMetadataSymbolWriter.cs b/src/IKVM.Reflection/Diagnostics/IMetadataSymbolWriter.cs new file mode 100644 index 0000000000..bf733f1928 --- /dev/null +++ b/src/IKVM.Reflection/Diagnostics/IMetadataSymbolWriter.cs @@ -0,0 +1,30 @@ +using System.Diagnostics.SymbolStore; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace IKVM.Reflection.Diagnostics +{ + + /// + /// Extensions to that provides additional capability for IKVM. + /// + public interface IMetadataSymbolWriter : ISymbolWriter + { + + /// + /// Extended OpenMethod that accepts a local variable signature. + /// + /// + /// + void OpenMethod(SymbolToken method, StandaloneSignatureHandle localSignatureHandle); + + /// + /// Writes the debug information to the given metadata. + /// + /// + /// + void WriteTo(MetadataBuilder metadata, out int userEntryPoint); + + } + +} diff --git a/src/IKVM.Reflection/Diagnostics/PortablePdbSymbolWriter.cs b/src/IKVM.Reflection/Diagnostics/PortablePdbSymbolWriter.cs new file mode 100644 index 0000000000..e04fbd69d8 --- /dev/null +++ b/src/IKVM.Reflection/Diagnostics/PortablePdbSymbolWriter.cs @@ -0,0 +1,751 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.SymbolStore; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using IKVM.Reflection.Emit; + +namespace IKVM.Reflection.Diagnostics +{ + + /// + /// Symbol writer base implementation that outputs symbols for module builders. + /// + public class PortablePdbSymbolWriter : IMetadataSymbolWriter + { + + protected sealed class Document : ISymbolDocumentWriter + { + + readonly string url; + readonly Guid language; + readonly Guid languageVendor; + readonly Guid documentType; + + Guid algorithmId; + byte[] checksum; + byte[] source; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + internal Document(string url, Guid language, Guid languageVendor, Guid documentType) + { + this.url = url; + this.language = language; + this.languageVendor = languageVendor; + this.documentType = documentType; + } + + /// + /// Sets the checksum. + /// + /// + /// + public void SetCheckSum(Guid algorithmId, byte[] checksum) + { + this.algorithmId = algorithmId; + this.checksum = checksum; + } + + /// + /// Sets the source. + /// + /// + public void SetSource(byte[] source) + { + this.source = source; + } + + /// + /// Gets the URL of the document. + /// + + public string Url => url; + + /// + /// Gets the language of the document. + /// + public Guid Language => language; + + /// + /// Gets the vendor of the document. + /// + public Guid LanguageVendor => languageVendor; + + /// + /// Gets the document type. + /// + public Guid DocumentType => documentType; + + /// + /// Gets the checksum algorithm ID. + /// + public Guid AlgorithmId => algorithmId; + + /// + /// Gets the checksum. + /// + public byte[] Checksum => checksum; + + /// + /// Gets the source. + /// + public byte[] Source => source; + + } + + /// + /// Track local variable information. + /// + protected readonly struct LocalVar + { + + readonly System.Reflection.FieldAttributes attributes; + readonly byte[] signature; + readonly SymAddressKind addrKind; + readonly int addr1; + readonly int addr2; + readonly int addr3; + readonly int startOffset; + readonly int endOffset; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal LocalVar(System.Reflection.FieldAttributes attributes, byte[] signature, SymAddressKind addrKind, int addr1, int addr2, int addr3, int startOffset, int endOffset) + { + this.attributes = attributes; + this.signature = signature; + this.addrKind = addrKind; + this.addr1 = addr1; + this.addr2 = addr2; + this.addr3 = addr3; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + /// + /// Gets the attributes of the variable. + /// + public System.Reflection.FieldAttributes Attributes => attributes; + + /// + /// Gets the encoded signature of the local variable. + /// + public byte[] Signature => signature; + + /// + /// Gets the address kind + /// + public SymAddressKind AddressKind => addrKind; + + /// + /// Gets the first address. + /// + public int Address1 => addr1; + + /// + /// Gets the second address. + /// + public int Address2 => addr2; + + /// + /// Gets the third address. + /// + public int Address3 => addr3; + + /// + /// Gets the start offset in IL of the variable. + /// + public int StartOffset => startOffset; + + /// + /// Gets the end offset in IL of the variable. + /// + public int EndOffset => endOffset; + + } + + /// + /// Tracks method scope information. + /// + protected sealed class Scope + { + + internal int startOffset; + internal int endOffset; + internal List scopes; + internal Dictionary locals; + internal List namespaces; + + /// + /// Initializes a new instance. + /// + /// + internal Scope(int startOffset) + { + this.startOffset = startOffset; + } + + /// + /// Gets the start offset. + /// + public int StartOffset => startOffset; + + /// + /// Gets the end offset. + /// + public int EndOffset => endOffset; + + /// + /// Gets the nested scopes. + /// + public IReadOnlyList Scopes => (IReadOnlyList)scopes ?? Array.Empty(); + + /// + /// Gets the local variables. + /// + public IReadOnlyDictionary Locals => (IReadOnlyDictionary)locals ?? ImmutableDictionary.Empty; + + /// + /// Gets the namespaces. + /// + public IReadOnlyList Namespaces => (IReadOnlyList)namespaces ?? Array.Empty(); + + } + + /// + /// Tracks method information. + /// + protected sealed class Method + { + + readonly int token; + readonly StandaloneSignatureHandle localSignatureHandle; + internal List sequencePoints; + internal List scopes; + internal Stack scopeStack; + + /// + /// Initializes a new instance. + /// + /// + /// + internal Method(int token, StandaloneSignatureHandle localSignatureHandle) + { + this.token = token; + this.localSignatureHandle = localSignatureHandle; + } + + /// + /// Gets the token of the method. + /// + public int Token => token; + + /// + /// Gets the local variable signature. + /// + public StandaloneSignatureHandle LocalSignatureHandle => localSignatureHandle; + + /// + /// Gets the sequence points. + /// + public IReadOnlyList SequencePoints => (IReadOnlyList)sequencePoints ?? Array.Empty(); + + /// + /// Gets the scopes. + /// + public IReadOnlyList Scopes => (IReadOnlyList)scopes ?? Array.Empty(); + + } + + /// + /// Describes a sequence point on a method. + /// + protected readonly struct SequencePoint + { + + readonly Document document; + readonly int[] offsets; + readonly int[] lines; + readonly int[] columns; + readonly int[] endLines; + readonly int[] endColumns; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + /// + internal SequencePoint(Document document, int[] offsets, int[] lines, int[] columns, int[] endLines, int[] endColumns) + { + this.document = document; + this.offsets = offsets; + this.lines = lines; + this.columns = columns; + this.endLines = endLines; + this.endColumns = endColumns; + } + + /// + /// Gets the document. + /// + public Document Document => document; + + /// + /// Gets the offsets. + /// + public int[] Offsets => offsets; + + /// + /// Gets the lines + /// + public int[] Lines => lines; + + /// + /// Gets the columns. + /// + public int[] Columns => columns; + + /// + /// Gets the line endings. + /// + public int[] EndLines => endLines; + + /// + /// Gets the column endings. + /// + public int[] EndColumns => endColumns; + + } + + readonly ModuleBuilder module; + Dictionary documents; + List methods; + Method currentMethod; + int userEntryPoint; + + /// + /// Initializes a new instance. + /// + /// + public PortablePdbSymbolWriter(ModuleBuilder module) + { + this.module = module ?? throw new ArgumentNullException(nameof(module)); + } + + /// + public virtual void Initialize(IntPtr emitter, string filename, bool fFullBuild) + { + throw new NotImplementedException(); + } + + /// + public virtual ISymbolDocumentWriter DefineDocument(string url, Guid language, Guid languageVendor, Guid documentType) + { + documents ??= new(); + if (documents.TryGetValue(url, out var doc) == false) + documents.Add(url, doc = new Document(url, language, languageVendor, documentType)); + + return doc; + } + + /// + public virtual void OpenNamespace(string name) + { + throw new NotImplementedException(); + } + + /// + public virtual void OpenMethod(SymbolToken method) + { + Debug.Assert(ModuleBuilder.IsPseudoToken(method.GetToken()) == false); + currentMethod = new Method(method.GetToken(), default); + } + + /// + /// Opens a method to place symbol information into. + /// + /// + /// + public virtual void OpenMethod(SymbolToken method, StandaloneSignatureHandle localSignatureHandle) + { + Debug.Assert(ModuleBuilder.IsPseudoToken(method.GetToken()) == false); + currentMethod = new Method(method.GetToken(), localSignatureHandle); + } + + /// + public virtual void DefineSequencePoints(ISymbolDocumentWriter document, int[] offsets, int[] lines, int[] columns, int[] endLines, int[] endColumns) + { + if (currentMethod == null) + throw new InvalidOperationException("No open method."); + + if (document is not Document doc) + throw new InvalidOperationException("Document not obtained from symbol writer."); + + var sequencePoint = new SequencePoint(doc, offsets, lines, columns, endLines, endColumns); + currentMethod.sequencePoints ??= new(); + currentMethod.sequencePoints.Add(sequencePoint); + } + + /// + public virtual int OpenScope(int startOffset) + { + if (currentMethod == null) + throw new InvalidOperationException("No open method."); + + var scope = new Scope(startOffset); + + if (currentMethod.scopeStack == null || currentMethod.scopeStack.Count == 0) + { + currentMethod.scopes ??= new(); + currentMethod.scopes.Add(scope); + } + else + { + var thisScope = currentMethod.scopeStack != null ? currentMethod.scopeStack.Peek() : null; + thisScope.scopes ??= new(); + thisScope.scopes.Add(scope); + } + + currentMethod.scopeStack ??= new(); + currentMethod.scopeStack.Push(scope); + + return 0; + } + + /// + public virtual void UsingNamespace(string fullName) + { + if (currentMethod == null) + throw new InvalidOperationException("No open method."); + + var scope = currentMethod.scopeStack != null ? currentMethod.scopeStack.Peek() : null; + if (scope == null) + throw new InvalidOperationException("No open scope."); + + scope.namespaces ??= new(); + scope.namespaces.Add(fullName); + } + + /// + public virtual void DefineLocalVariable(string name, System.Reflection.FieldAttributes attributes, byte[] signature, SymAddressKind addrKind, int addr1, int addr2, int addr3, int startOffset, int endOffset) + { + if (currentMethod == null) + throw new InvalidOperationException("No open method."); + + var scope = currentMethod.scopeStack != null ? currentMethod.scopeStack.Peek() : null; + if (scope == null) + throw new InvalidOperationException("No open scope."); + + scope.locals ??= new(); + scope.locals[name] = new LocalVar(attributes, signature, addrKind, addr1, addr2, addr3, startOffset, endOffset); + } + + /// + public virtual void CloseScope(int endOffset) + { + if (currentMethod == null) + throw new InvalidOperationException("No open method."); + + var scope = currentMethod.scopeStack != null ? currentMethod.scopeStack.Pop() : null; + if (scope == null) + throw new InvalidOperationException("No open scope."); + + scope.endOffset = endOffset; + } + + /// + public virtual void CloseMethod() + { + if (currentMethod == null) + throw new InvalidOperationException("No open method."); + + methods ??= new(); + methods.Add(currentMethod); + currentMethod = null; + } + + /// + public virtual void CloseNamespace() + { + throw new NotImplementedException(); + } + + /// + public virtual void Close() + { + + } + + /// + public virtual void WriteTo(MetadataBuilder metadata, out int userEntryPoint) + { + metadata.GetOrAddString(""); + metadata.GetOrAddUserString(""); + + var documentCache = new Dictionary(); + foreach (var method in methods) + WriteMethod(metadata, method, documentCache); + + userEntryPoint = this.userEntryPoint; + } + + /// + /// Writes the given method. + /// + /// + /// + /// + void WriteMethod(MetadataBuilder metadata, Method method, Dictionary documentCache) + { + // find first document and set as initial, as it will be directly on method debug information + var initialDocument = GetSingleDocument(method.SequencePoints); + var initialDocumentHandle = initialDocument != null ? GetOrWriteDocument(metadata, initialDocument, documentCache) : default; + var currentDocumentHandle = initialDocumentHandle; + + // write sequence points and scopes + WriteSequencePoints(metadata, method, documentCache, out var sequencePointsHandle, ref currentDocumentHandle); + WriteScopes(metadata, method); + + // final debug information, containing initial document + var methodDebugHandle = metadata.AddMethodDebugInformation(initialDocumentHandle, sequencePointsHandle); + Debug.Assert(MetadataTokens.GetRowNumber(methodDebugHandle) == MetadataTokens.GetRowNumber(MetadataTokens.EntityHandle(method.Token))); + } + + /// + /// Returns the single unique in a set of sequence points, or null if there is no single unique document. + /// + /// + /// + Document GetSingleDocument(IEnumerable sequencePoints) + { + var s = sequencePoints.Select(i => i.Document).Distinct().Take(2).ToList(); + return s.Count == 1 ? s[0] : null; + } + + /// + /// Writes the sequence points for the method. + /// + /// + /// + /// + /// + /// + /// + void WriteSequencePoints(MetadataBuilder metadata, Method method, Dictionary documentCache, out BlobHandle sequencePointHandle, ref DocumentHandle previousDocument) + { + // no sequence points? + sequencePointHandle = default; + if (method.SequencePoints.Count == 0) + return; + + var buf = new BlobBuilder(); + var enc = new SequencePointEncoder(buf); + + // obtain local signature from method builder directly + if (method.LocalSignatureHandle.IsNil) + throw new InvalidOperationException("MethodBuilder missing local signature."); + + // define the local signature, default seems to work fine + enc.LocalSignature(default); + + // add the sequence points recorded on the method + foreach (var (document, offset, startLine, endLine, startColumn, endColumn) in ExpandSequencePoints(method.SequencePoints).OrderBy(i => i.Offset)) + { + var doc = GetOrWriteDocument(metadata, document, documentCache); + enc.SequencePoint(doc, offset, startLine, startColumn, endLine, endColumn, ref previousDocument); + } + + // add sequence points blob + sequencePointHandle = metadata.GetOrAddBlob(buf); + } + + /// + /// Expands the set of sequence points with multiple offsets into single sequence point records for each offset. + /// + /// + /// + IEnumerable<(Document Document, int Offset, int StartLine, int EndLine, int StartColumn, int EndColumn)> ExpandSequencePoints(IReadOnlyList sequencePoints) + { + for (int i = 0; i < sequencePoints.Count; i++) + for (int j = 0; j < sequencePoints[i].Offsets.Length; j++) + yield return (sequencePoints[i].Document, sequencePoints[i].Offsets[j], sequencePoints[i].Lines[j], sequencePoints[i].EndLines[j], sequencePoints[i].Columns[j], sequencePoints[i].EndColumns[j]); + } + + /// + /// Gets or writes the handle to a document in the metadata. + /// + /// + /// + /// + DocumentHandle GetOrWriteDocument(MetadataBuilder metadata, Document document, Dictionary documentCache) + { + if (document == null) + return default; + + if (documentCache.TryGetValue(document, out var handle) == false) + documentCache.Add(document, handle = WriteDocument(metadata, document)); + + return handle; + } + + /// + /// Writes the document to the metadata. + /// + /// + /// + /// + DocumentHandle WriteDocument(MetadataBuilder metadata, Document document) + { + return metadata.AddDocument( + metadata.GetOrAddDocumentName(document.Url ?? ""), + document.AlgorithmId != Guid.Empty ? metadata.GetOrAddGuid(document.AlgorithmId) : default, + document.Checksum != null ? metadata.GetOrAddBlob(document.Checksum) : default, + document.Language != Guid.Empty ? metadata.GetOrAddGuid(document.Language) : default); + } + + /// + /// Writes the scopes of the method. + /// + /// + /// + void WriteScopes(MetadataBuilder metadata, Method method) + { + foreach (var scope in method.Scopes.OrderBy(i => i.StartOffset).ThenByDescending(i => i.EndOffset - i.StartOffset)) + WriteScope(metadata, method, scope); + } + + /// + /// Writes the given scope. + /// + /// + /// + /// + void WriteScope(MetadataBuilder metadata, Method method, Scope scope) + { + WriteScope(metadata, method, scope, default); + } + + /// + /// Writes the given scope. + /// + /// + /// + /// + /// + void WriteScope(MetadataBuilder metadata, Method method, Scope scope, ImportScopeHandle parentImportScope) + { + // insert import scope for this scope + var importScope = default(ImportScopeHandle); + if (scope.Namespaces.Count > 0) + { + var buf = new BlobBuilder(); + var enc = new ImportsEncoder(buf); + + // add namespace imports + foreach (var ns in scope.Namespaces) + enc.ImportNamespace(metadata.GetOrAddBlobUTF8(ns)); + + // write nested scope imports + importScope = metadata.AddImportScope(parentImportScope, metadata.GetOrAddBlob(buf)); + } + + // insert variables for this scope, keeping first entry + var variableList = default(LocalVariableHandle); + foreach (var kvp in scope.Locals.OrderBy(i => i.Value.Address1)) + { + var h = metadata.AddLocalVariable(LocalVariableAttributes.None, kvp.Value.Address1, metadata.GetOrAddString(kvp.Key)); + if (variableList.IsNil) + variableList = h; + } + + // add scope + metadata.AddLocalScope( + (MethodDefinitionHandle)MetadataTokens.EntityHandle(method.Token), + importScope, + variableList, + default, + scope.StartOffset, + scope.EndOffset - scope.StartOffset); + + // repeat for children scopes + foreach (var nestedScope in scope.Scopes.OrderBy(i => i.StartOffset).ThenByDescending(i => i.EndOffset - i.StartOffset)) + WriteScope(metadata, method, nestedScope, importScope); + } + + #region Not Implemented + + /// + public void DefineField(SymbolToken parent, string name, System.Reflection.FieldAttributes attributes, byte[] signature, SymAddressKind addrKind, int addr1, int addr2, int addr3) + { + throw new NotImplementedException(); + } + + /// + public void DefineGlobalVariable(string name, System.Reflection.FieldAttributes attributes, byte[] signature, SymAddressKind addrKind, int addr1, int addr2, int addr3) + { + throw new NotImplementedException(); + } + + /// + public void DefineParameter(string name, System.Reflection.ParameterAttributes attributes, int sequence, SymAddressKind addrKind, int addr1, int addr2, int addr3) + { + throw new NotImplementedException(); + } + + /// + public void SetMethodSourceRange(ISymbolDocumentWriter startDoc, int startLine, int startColumn, ISymbolDocumentWriter endDoc, int endLine, int endColumn) + { + throw new NotImplementedException(); + } + + /// + public void SetScopeRange(int scopeID, int startOffset, int endOffset) + { + throw new NotImplementedException(); + } + + /// + public void SetSymAttribute(SymbolToken parent, string name, byte[] data) + { + throw new NotImplementedException(); + } + + /// + public void SetUnderlyingWriter(IntPtr underlyingWriter) + { + throw new NotImplementedException(); + } + + /// + public void SetUserEntryPoint(SymbolToken entryMethod) + { + this.userEntryPoint = entryMethod.GetToken(); + } + + #endregion + + } + +} diff --git a/src/IKVM.Reflection/Emit/ArrayMethod.cs b/src/IKVM.Reflection/Emit/ArrayMethod.cs index e55868c403..5a22dca83f 100644 --- a/src/IKVM.Reflection/Emit/ArrayMethod.cs +++ b/src/IKVM.Reflection/Emit/ArrayMethod.cs @@ -52,7 +52,7 @@ internal ArrayMethod(Module module, Type arrayClass, string methodName, CallingC this.arrayClass = arrayClass; this.methodName = methodName; this.callingConvention = callingConvention; - this.returnType = returnType ?? module.universe.System_Void; + this.returnType = returnType ?? module.Universe.System_Void; this.parameterTypes = Util.Copy(parameterTypes); } @@ -76,6 +76,11 @@ public override ParameterInfo[] GetParameters() throw new NotSupportedException(); } + internal override Type[] GetParameterTypes() + { + return parameterTypes; + } + internal override int ImportTo(ModuleBuilder module) { return module.ImportMethodOrField(arrayClass, methodName, MethodSignature); diff --git a/src/IKVM.Reflection/Emit/AssemblyBuilder.cs b/src/IKVM.Reflection/Emit/AssemblyBuilder.cs index 1f2c713b5e..4269a4dc2f 100644 --- a/src/IKVM.Reflection/Emit/AssemblyBuilder.cs +++ b/src/IKVM.Reflection/Emit/AssemblyBuilder.cs @@ -161,7 +161,7 @@ void SetVersionHelper(Version version) void Rename(AssemblyName oldName) { this.fullName = null; - universe.RenameAssembly(this, oldName); + Universe.RenameAssembly(this, oldName); } public void __SetAssemblyVersion(Version version) @@ -247,27 +247,38 @@ public override string Location get { throw new NotSupportedException(); } } + /// + /// Defines a persistable dynamic module with the given name that will be saved to the specified file. No symbol information is emitted. + /// + /// + /// + /// public ModuleBuilder DefineDynamicModule(string name, string fileName) { return DefineDynamicModule(name, fileName, false); } + /// + /// Defines a persistable dynamic module, specifying the module name, the name of the file to which the module will be saved, and whether symbol information should be emitted using the default symbol writer. + /// + /// + /// + /// + /// public ModuleBuilder DefineDynamicModule(string name, string fileName, bool emitSymbolInfo) { - ModuleBuilder module = new ModuleBuilder(this, name, fileName, emitSymbolInfo); + var module = new ModuleBuilder(this, name, fileName); + module.SetSymWriter(emitSymbolInfo ? Universe.CreateSymbolWriter(module) : null); modules.Add(module); return module; } public ModuleBuilder GetDynamicModule(string name) { - foreach (ModuleBuilder module in modules) - { + foreach (var module in modules) if (module.Name == name) - { return module; - } - } + return null; } @@ -307,34 +318,72 @@ public void SetEntryPoint(MethodInfo entryMethod, PEFileKinds fileKind) this.fileKind = fileKind; } - public void __Save(Stream stream, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine) + /// + /// Saves the single module assembly to the specified stream. + /// + /// + /// + /// + /// + /// + public void __Save(Stream peStream, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine) { - if (!stream.CanRead || !stream.CanWrite || !stream.CanSeek || stream.Position != 0) - throw new ArgumentException("Stream must support read/write/seek and current position must be zero.", "stream"); if (modules.Count != 1) throw new NotSupportedException("Saving to a stream is only supported for single module assemblies."); - SaveImpl(modules[0].fileName, stream, portableExecutableKind, imageFileMachine); + __Save(peStream, null, portableExecutableKind, imageFileMachine); } + /// + /// Saves the single module assembly and it's symbols to the specified streams. + /// + /// + /// + /// + /// + /// + /// + public void __Save(Stream peStream, Stream pdbStream, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine) + { + if (peStream.CanWrite == false) + throw new ArgumentException("Stream must support write.", nameof(peStream)); + if (pdbStream != null && pdbStream.CanWrite == false) + throw new ArgumentException("Stream must support write.", nameof(pdbStream)); + if (modules.Count != 1) + throw new NotSupportedException("Saving to a stream is only supported for single module assemblies."); + + SaveImpl(modules[0].fileName, peStream, pdbStream, portableExecutableKind, imageFileMachine); + } + + /// + /// Saves this dynamic assembly to disk. + /// + /// public void Save(string assemblyFileName) { Save(assemblyFileName, PortableExecutableKinds.ILOnly, ImageFileMachine.I386); } + /// + /// Saves this dynamic assembly to disk, specifying the nature of code in the assembly's executables and the target platform. + /// + /// + /// + /// public void Save(string assemblyFileName, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine) { - SaveImpl(assemblyFileName, null, portableExecutableKind, imageFileMachine); + SaveImpl(assemblyFileName, null, null, portableExecutableKind, imageFileMachine); } /// /// Implements the logic to save all of the modules within the assembly. /// /// - /// + /// + /// /// /// - void SaveImpl(string assemblyFileName, Stream streamOrNull, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine) + void SaveImpl(string assemblyFileName, Stream peStream, Stream pdbStream, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine) { ModuleBuilder manifestModule = null; @@ -387,7 +436,7 @@ void SaveImpl(string assemblyFileName, Stream streamOrNull, PortableExecutableKi foreach (var cab in customAttributes) { // .NET doesn't support copying blob custom attributes into the version info - if (cab.HasBlob == false || universe.DecodeVersionInfoAttributeBlobs) + if (cab.HasBlob == false || Universe.DecodeVersionInfoAttributeBlobs) versionInfo.SetAttribute(this, cab); } @@ -435,24 +484,24 @@ void SaveImpl(string assemblyFileName, Stream streamOrNull, PortableExecutableKi } // write each non-manifest module - foreach (var moduleBuilder in modules) + foreach (var module in modules) { - moduleBuilder.FillAssemblyRefTable(); + module.FillAssemblyRefTable(); - if (moduleBuilder != manifestModule) + if (module != manifestModule) { var fileToken = default(AssemblyFileHandle); - if (entryPoint != null && entryPoint.Module == moduleBuilder) + if (entryPoint != null && entryPoint.Module == module) { throw new NotSupportedException("Multi-module assemblies cannot have an entry point in a module other than the manifest module."); } else { - ModuleWriter.WriteModule(null, null, moduleBuilder, fileKind, portableExecutableKind, imageFileMachine, moduleBuilder.nativeResources, default); - fileToken = AddFile(manifestModule, moduleBuilder.fileName, 0 /*ContainsMetaData*/); + ModuleWriter.WriteModule(null, null, module, fileKind, portableExecutableKind, imageFileMachine, module.nativeResources, default); + fileToken = AddFile(manifestModule, module.fileName, 0 /*ContainsMetaData*/); } - moduleBuilder.ExportTypes(fileToken, manifestModule); + module.ExportTypes(fileToken, manifestModule); } } @@ -464,7 +513,7 @@ void SaveImpl(string assemblyFileName, Stream streamOrNull, PortableExecutableKi } // finally, write the manifest module - ModuleWriter.WriteModule(keyPair, publicKey, manifestModule, fileKind, portableExecutableKind, imageFileMachine, nativeResources, entryPoint, streamOrNull); + ModuleWriter.WriteModule(keyPair, publicKey, manifestModule, fileKind, portableExecutableKind, imageFileMachine, nativeResources, entryPoint, null, peStream, null, pdbStream); } AssemblyFileHandle AddFile(ModuleBuilder manifestModule, string fileName, int flags) diff --git a/src/IKVM.Reflection/Emit/ConstructorBuilder.cs b/src/IKVM.Reflection/Emit/ConstructorBuilder.cs index 4f364abc2f..91d984b8a8 100644 --- a/src/IKVM.Reflection/Emit/ConstructorBuilder.cs +++ b/src/IKVM.Reflection/Emit/ConstructorBuilder.cs @@ -103,11 +103,6 @@ public ILGenerator GetILGenerator(int streamSize) return methodBuilder.GetILGenerator(streamSize); } - public void __ReleaseILGenerator() - { - methodBuilder.__ReleaseILGenerator(); - } - public Type ReturnType { get { return methodBuilder.ReturnType; } @@ -129,6 +124,14 @@ public bool InitLocals set { methodBuilder.InitLocals = value; } } + /// + /// Creates the body of the constructor by using a specified byte array of Microsoft intermediate language (MSIL) instructions. + /// + /// + /// + /// + /// + /// public void SetMethodBody(byte[] il, int maxStack, byte[] localSignature, IEnumerable exceptionHandlers, IEnumerable tokenFixups) { methodBuilder.SetMethodBody(il, maxStack, localSignature, exceptionHandlers, tokenFixups); diff --git a/src/IKVM.Reflection/Emit/CustomAttributeBuilder.cs b/src/IKVM.Reflection/Emit/CustomAttributeBuilder.cs index 8f18c1425f..1dc4fdd25a 100644 --- a/src/IKVM.Reflection/Emit/CustomAttributeBuilder.cs +++ b/src/IKVM.Reflection/Emit/CustomAttributeBuilder.cs @@ -232,7 +232,7 @@ void WriteInt32(int value) void WriteFixedArg(Type type, object value) { - var u = assembly.universe; + var u = assembly.Universe; if (type == u.System_String) { WriteString((string)value); @@ -370,7 +370,7 @@ void WriteTypeName(Type type) void GetTypeName(StringBuilder sb, Type type, bool isTypeParam) { bool v1 = !assembly.ManifestModule.__IsMissing && assembly.ManifestModule.MDStreamVersion < 0x20000; - bool includeAssemblyName = type.Assembly != assembly && (!v1 || type.Assembly != type.Module.universe.CoreLib); + bool includeAssemblyName = type.Assembly != assembly && (!v1 || type.Assembly != type.Module.Universe.CoreLib); if (isTypeParam && includeAssemblyName) { sb.Append('['); @@ -435,7 +435,7 @@ void WritePackedLen(int len) void WriteFieldOrPropType(Type type) { - var u = type.Module.universe; + var u = type.Module.Universe; if (type == u.System_Type) { WriteByte(0x50); @@ -591,7 +591,7 @@ internal bool IsLegacyDeclSecurity get { return ReferenceEquals(con, LegacyPermissionSet) - || (con.DeclaringType == con.Module.universe.System_Security_Permissions_PermissionSetAttribute + || (con.DeclaringType == con.Module.Universe.System_Security_Permissions_PermissionSetAttribute && blob == null && (namedFields == null || namedFields.Length == 0) && namedProperties != null @@ -661,7 +661,7 @@ static CustomAttributeTypedArgument RewrapValue(Type type, object value) if (value is Array) { var array = (Array)value; - var arrayType = type.Module.universe.Import(array.GetType()); + var arrayType = type.Module.Universe.Import(array.GetType()); return RewrapArray(arrayType, array); } else if (value is CustomAttributeTypedArgument) diff --git a/src/IKVM.Reflection/Emit/ExceptionHandler.cs b/src/IKVM.Reflection/Emit/ExceptionHandler.cs index 9b4232e471..c5196a5ca8 100644 --- a/src/IKVM.Reflection/Emit/ExceptionHandler.cs +++ b/src/IKVM.Reflection/Emit/ExceptionHandler.cs @@ -1,122 +1,201 @@ -/* - Copyright (C) 2012 Jeroen Frijters - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jeroen Frijters - jeroen@frijters.net - -*/ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Copied from .NET Core RuntimeMethodBuilder as of 1/24/2024. + using System; +using System.Diagnostics; +using System.Runtime.InteropServices; namespace IKVM.Reflection.Emit { + + /// + /// Describes exception handler in a method body. + /// + [StructLayout(LayoutKind.Sequential)] public readonly struct ExceptionHandler : IEquatable { + // Keep in sync with unmanged structure. + internal readonly int m_exceptionClass; + internal readonly int m_tryStartOffset; + internal readonly int m_tryEndOffset; + internal readonly int m_filterOffset; + internal readonly int m_handlerStartOffset; + internal readonly int m_handlerEndOffset; + internal readonly ExceptionHandlingClauseOptions m_kind; - readonly int tryOffset; - readonly int tryLength; - readonly int filterOffset; - readonly int handlerOffset; - readonly int handlerLength; - readonly ExceptionHandlingClauseOptions kind; - readonly int exceptionTypeToken; - - /// - /// Initializes a new instance. - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ExceptionHandler(int tryOffset, int tryLength, int filterOffset, int handlerOffset, int handlerLength, ExceptionHandlingClauseOptions kind, int exceptionTypeToken) + public int ExceptionTypeToken { - if (tryOffset < 0 || tryLength < 0 || filterOffset < 0 || handlerOffset < 0 || handlerLength < 0) - throw new ArgumentOutOfRangeException(); - - this.tryOffset = tryOffset; - this.tryLength = tryLength; - this.filterOffset = filterOffset; - this.handlerOffset = handlerOffset; - this.handlerLength = handlerLength; - this.kind = kind; - this.exceptionTypeToken = exceptionTypeToken; + get { return m_exceptionClass; } } public int TryOffset { - get { return tryOffset; } + get { return m_tryStartOffset; } } public int TryLength { - get { return tryLength; } + get { return m_tryEndOffset - m_tryStartOffset; } } public int FilterOffset { - get { return filterOffset; } + get { return m_filterOffset; } } public int HandlerOffset { - get { return handlerOffset; } + get { return m_handlerStartOffset; } } public int HandlerLength { - get { return handlerLength; } + get { return m_handlerEndOffset - m_handlerStartOffset; } } public ExceptionHandlingClauseOptions Kind { - get { return kind; } + get { return m_kind; } } - public int ExceptionTypeToken + #region Constructors + + /// + /// Creates a description of an exception handler. + /// + /// The offset of the first instruction protected by this handler. + /// The number of bytes protected by this handler. + /// The filter code begins at the specified offset and ends at the first instruction of the handler block. Specify 0 if not applicable (this is not a filter handler). + /// The offset of the first instruction of this handler. + /// The number of bytes of the handler. + /// The kind of handler, the handler might be a catch handler, filter handler, fault handler, or finally handler. + /// The token of the exception type handled by this handler. Specify 0 if not applicable (this is finally handler). + /// + /// Some of the instruction offset is negative, + /// the end offset of specified range is less than its start offset, + /// or has an invalid value. + /// + public ExceptionHandler(int tryOffset, int tryLength, int filterOffset, int handlerOffset, int handlerLength, + ExceptionHandlingClauseOptions kind, int exceptionTypeToken) { - get { return exceptionTypeToken; } + if (tryOffset < 0) + { + throw new ArgumentOutOfRangeException("tryOffset", string.Format("Non-negative number required.")); + } + + if (tryLength < 0) + { + throw new ArgumentOutOfRangeException("tryLength", string.Format("Non-negative number required.")); + } + + if (filterOffset < 0) + { + throw new ArgumentOutOfRangeException("filterOffset", string.Format("Non-negative number required.")); + } + + if (handlerOffset < 0) + { + throw new ArgumentOutOfRangeException("handlerOffset", string.Format("Non-negative number required.")); + } + + if (handlerLength < 0) + { + throw new ArgumentOutOfRangeException("handlerLength", string.Format("Non-negative number required.")); + } + + if ((long)tryOffset + tryLength > Int32.MaxValue) + { + throw new ArgumentOutOfRangeException("tryLength", string.Format("Valid values are between {0} and {1}, inclusive.", 0, Int32.MaxValue - tryOffset)); + } + + if ((long)handlerOffset + handlerLength > Int32.MaxValue) + { + throw new ArgumentOutOfRangeException("handlerLength", string.Format("Valid values are between {0} and {1}, inclusive.", 0, Int32.MaxValue - handlerOffset)); + } + + // Other tokens migth also be invalid. We only check nil tokens as the implementation (SectEH_Emit in corhlpr.cpp) requires it, + // and we can't check for valid tokens until the module is baked. + if (kind == ExceptionHandlingClauseOptions.Clause && (exceptionTypeToken & 0x00FFFFFF) == 0) + { + throw new ArgumentException(string.Format("Token {0:x} is not a valid Type token.", exceptionTypeToken), "exceptionTypeToken"); + } + + if (!IsValidKind(kind)) + { + throw new ArgumentOutOfRangeException("kind", string.Format("Enum value was out of legal range.")); + } + + m_tryStartOffset = tryOffset; + m_tryEndOffset = tryOffset + tryLength; + m_filterOffset = filterOffset; + m_handlerStartOffset = handlerOffset; + m_handlerEndOffset = handlerOffset + handlerLength; + m_kind = kind; + m_exceptionClass = exceptionTypeToken; } - public bool Equals(ExceptionHandler other) + internal ExceptionHandler(int tryStartOffset, int tryEndOffset, int filterOffset, int handlerStartOffset, int handlerEndOffset, + int kind, int exceptionTypeToken) { - return tryOffset == other.tryOffset - && tryLength == other.tryLength - && filterOffset == other.filterOffset - && handlerOffset == other.handlerOffset - && handlerLength == other.handlerLength - && kind == other.kind - && exceptionTypeToken == other.exceptionTypeToken; + Debug.Assert(tryStartOffset >= 0); + Debug.Assert(tryEndOffset >= 0); + Debug.Assert(filterOffset >= 0); + Debug.Assert(handlerStartOffset >= 0); + Debug.Assert(handlerEndOffset >= 0); + Debug.Assert(IsValidKind((ExceptionHandlingClauseOptions)kind)); + Debug.Assert(kind != (int)ExceptionHandlingClauseOptions.Clause || (exceptionTypeToken & 0x00FFFFFF) != 0); + + m_tryStartOffset = tryStartOffset; + m_tryEndOffset = tryEndOffset; + m_filterOffset = filterOffset; + m_handlerStartOffset = handlerStartOffset; + m_handlerEndOffset = handlerEndOffset; + m_kind = (ExceptionHandlingClauseOptions)kind; + m_exceptionClass = exceptionTypeToken; } - public override bool Equals(object obj) + private static bool IsValidKind(ExceptionHandlingClauseOptions kind) { - ExceptionHandler? other = obj as ExceptionHandler?; - return other != null && Equals(other.Value); + switch (kind) + { + case ExceptionHandlingClauseOptions.Clause: + case ExceptionHandlingClauseOptions.Filter: + case ExceptionHandlingClauseOptions.Finally: + case ExceptionHandlingClauseOptions.Fault: + return true; + + default: + return false; + } } + #endregion + + #region Equality + public override int GetHashCode() { - return tryOffset ^ tryLength * 33 ^ filterOffset * 333 ^ handlerOffset * 3333 ^ handlerLength * 33333; + return m_exceptionClass ^ m_tryStartOffset ^ m_tryEndOffset ^ m_filterOffset ^ m_handlerStartOffset ^ m_handlerEndOffset ^ (int)m_kind; + } + + public override bool Equals(Object obj) + { + return obj is ExceptionHandler && Equals((ExceptionHandler)obj); + } + + public bool Equals(ExceptionHandler other) + { + return + other.m_exceptionClass == m_exceptionClass && + other.m_tryStartOffset == m_tryStartOffset && + other.m_tryEndOffset == m_tryEndOffset && + other.m_filterOffset == m_filterOffset && + other.m_handlerStartOffset == m_handlerStartOffset && + other.m_handlerEndOffset == m_handlerEndOffset && + other.m_kind == m_kind; } public static bool operator ==(ExceptionHandler left, ExceptionHandler right) @@ -129,6 +208,8 @@ public override int GetHashCode() return !left.Equals(right); } + #endregion + } } diff --git a/src/IKVM.Reflection/Emit/FieldBuilder.cs b/src/IKVM.Reflection/Emit/FieldBuilder.cs index cc43f688bb..290af15d9b 100644 --- a/src/IKVM.Reflection/Emit/FieldBuilder.cs +++ b/src/IKVM.Reflection/Emit/FieldBuilder.cs @@ -22,9 +22,7 @@ Jeroen Frijters */ using System; -using System.Diagnostics; using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; using IKVM.Reflection.Metadata; using IKVM.Reflection.Writer; @@ -35,13 +33,14 @@ namespace IKVM.Reflection.Emit public sealed class FieldBuilder : FieldInfo { - readonly TypeBuilder typeBuilder; + readonly TypeBuilder type; readonly string name; readonly int pseudoToken; - FieldAttributes attribs; - readonly StringHandle nameIndex; - readonly BlobHandle signature; - readonly FieldSignature fieldSig; + FieldAttributes attributes; + readonly FieldSignature signature; + int offset = -1; + + BlobHandle signatureBlobHandle; /// /// Initializes a new instance. @@ -50,84 +49,43 @@ public sealed class FieldBuilder : FieldInfo /// /// /// - /// - internal FieldBuilder(TypeBuilder type, string name, Type fieldType, CustomModifiers customModifiers, FieldAttributes attribs) + /// + internal FieldBuilder(TypeBuilder type, string name, Type fieldType, CustomModifiers customModifiers, FieldAttributes attributes) { - this.typeBuilder = type; + this.type = type; this.name = name; + this.attributes = attributes; this.pseudoToken = type.ModuleBuilder.AllocPseudoToken(); - this.nameIndex = type.ModuleBuilder.GetOrAddString(name); - this.fieldSig = FieldSignature.Create(fieldType, customModifiers); - var sig = new ByteBuffer(5); - fieldSig.Write(typeBuilder.ModuleBuilder, sig); - this.signature = typeBuilder.ModuleBuilder.GetOrAddBlob(sig.ToArray()); - this.attribs = attribs; - this.typeBuilder.ModuleBuilder.FieldTable.AddVirtualRecord(); + this.signature = FieldSignature.Create(fieldType, customModifiers); + this.type.ModuleBuilder.FieldTable.AddVirtualRecord(); + + // create signature blob + var buf = new ByteBuffer(5); + signature.Write(type.ModuleBuilder, buf); + signatureBlobHandle = type.ModuleBuilder.GetOrAddBlob(buf.ToArray()); } public void SetConstant(object defaultValue) { - attribs |= FieldAttributes.HasDefault; - typeBuilder.ModuleBuilder.AddConstant(pseudoToken, defaultValue); + attributes |= FieldAttributes.HasDefault; + type.ModuleBuilder.AddConstant(pseudoToken, defaultValue); } public override object GetRawConstantValue() { - if (!typeBuilder.IsCreated()) + if (!type.IsCreated()) { // the .NET FieldBuilder doesn't support this method // (since we dont' have a different FieldInfo object after baking, we will support it once we're baked) throw new NotSupportedException(); } - return typeBuilder.Module.ConstantTable.GetRawConstantValue(typeBuilder.Module, GetCurrentToken()); - } - - public void __SetDataAndRVA(byte[] data) - { - SetDataAndRvaImpl(data, typeBuilder.ModuleBuilder.initializedData, 0); - } - - public void __SetReadOnlyDataAndRVA(byte[] data) - { - SetDataAndRvaImpl(data, typeBuilder.ModuleBuilder.methodBodies, unchecked((int)0x80000000)); - } - - void SetDataAndRvaImpl(byte[] data, ByteBuffer bb, int readonlyMarker) - { - attribs |= FieldAttributes.HasFieldRVA; - FieldRVATable.Record rec = new FieldRVATable.Record(); - bb.Align(8); - rec.RVA = bb.Position + readonlyMarker; - rec.Field = pseudoToken; - typeBuilder.ModuleBuilder.FieldRVATable.AddRecord(rec); - bb.Write(data); - } - - public override void __GetDataFromRVA(byte[] data, int offset, int length) - { - throw new NotImplementedException(); - } - - public override int __FieldRVA - { - get { throw new NotImplementedException(); } + return type.Module.ConstantTable.GetRawConstantValue(type.Module, GetCurrentToken()); } public override bool __TryGetFieldOffset(out int offset) { - int pseudoTokenOrIndex = pseudoToken; - if (typeBuilder.ModuleBuilder.IsSaved) - pseudoTokenOrIndex = typeBuilder.ModuleBuilder.ResolvePseudoToken(pseudoToken) & 0xFFFFFF; - - foreach (int i in this.Module.FieldLayoutTable.Filter(pseudoTokenOrIndex)) - { - offset = this.Module.FieldLayoutTable.records[i].Offset; - return true; - } - - offset = 0; - return false; + return (offset = this.offset) != -1; } public void SetCustomAttribute(ConstructorInfo con, byte[] binaryAttribute) @@ -140,40 +98,37 @@ public void SetCustomAttribute(CustomAttributeBuilder customBuilder) switch (customBuilder.KnownCA) { case KnownCA.FieldOffsetAttribute: - SetOffset((int)customBuilder.DecodeBlob(this.Module.Assembly).GetConstructorArgument(0)); + SetOffset((int)customBuilder.DecodeBlob(Module.Assembly).GetConstructorArgument(0)); break; case KnownCA.MarshalAsAttribute: - FieldMarshal.SetMarshalAsAttribute(typeBuilder.ModuleBuilder, pseudoToken, customBuilder); - attribs |= FieldAttributes.HasFieldMarshal; + FieldMarshal.SetMarshalAsAttribute(type.ModuleBuilder, pseudoToken, customBuilder); + attributes |= FieldAttributes.HasFieldMarshal; break; case KnownCA.NonSerializedAttribute: - attribs |= FieldAttributes.NotSerialized; + attributes |= FieldAttributes.NotSerialized; break; case KnownCA.SpecialNameAttribute: - attribs |= FieldAttributes.SpecialName; + attributes |= FieldAttributes.SpecialName; break; default: - typeBuilder.ModuleBuilder.SetCustomAttribute(pseudoToken, customBuilder); + type.ModuleBuilder.SetCustomAttribute(pseudoToken, customBuilder); break; } } public void SetOffset(int iOffset) { - FieldLayoutTable.Record rec = new FieldLayoutTable.Record(); - rec.Offset = iOffset; - rec.Field = pseudoToken; - typeBuilder.ModuleBuilder.FieldLayoutTable.AddRecord(rec); + offset = iOffset; } public override FieldAttributes Attributes { - get { return attribs; } + get { return attributes; } } public override Type DeclaringType { - get { return typeBuilder.IsModulePseudoType ? null : typeBuilder; } + get { return type.IsModulePseudoType ? null : type; } } public override string Name @@ -188,7 +143,7 @@ public override int MetadataToken public override Module Module { - get { return typeBuilder.Module; } + get { return type.Module; } } public FieldToken GetToken() @@ -196,44 +151,48 @@ public FieldToken GetToken() return new FieldToken(pseudoToken); } - internal void WriteFieldRecords(MetadataBuilder metadata) + internal void WriteFieldRecords() { - metadata.AddFieldDefinition( - (System.Reflection.FieldAttributes)attribs, - nameIndex, - signature); + type.ModuleBuilder.Metadata.AddFieldDefinition( + (System.Reflection.FieldAttributes)attributes, + type.ModuleBuilder.GetOrAddString(name), + signatureBlobHandle); } internal void FixupToken(int token) { - typeBuilder.ModuleBuilder.RegisterTokenFixup(pseudoToken, token); + if (offset > -1) + { + var rec = new FieldLayoutTable.Record(); + rec.Offset = offset; + rec.Field = pseudoToken; + type.ModuleBuilder.FieldLayoutTable.AddRecord(rec); + } + + type.ModuleBuilder.RegisterTokenFixup(pseudoToken, token); } internal override FieldSignature FieldSignature { - get { return fieldSig; } + get { return signature; } } internal override int ImportTo(ModuleBuilder other) { - return other.ImportMethodOrField(typeBuilder, name, fieldSig); + return other.ImportMethodOrField(type, name, signature); } internal override int GetCurrentToken() { - if (typeBuilder.ModuleBuilder.IsSaved) - { - return typeBuilder.ModuleBuilder.ResolvePseudoToken(pseudoToken); - } + if (type.ModuleBuilder.IsSaved) + return type.ModuleBuilder.ResolvePseudoToken(pseudoToken); else - { return pseudoToken; - } } internal override bool IsBaked { - get { return typeBuilder.IsBaked; } + get { return type.IsBaked; } } } diff --git a/src/IKVM.Reflection/Emit/ILGenerator.cs b/src/IKVM.Reflection/Emit/ILGenerator.cs index a5048fb196..146241b4bb 100644 --- a/src/IKVM.Reflection/Emit/ILGenerator.cs +++ b/src/IKVM.Reflection/Emit/ILGenerator.cs @@ -1,1067 +1,1899 @@ -/* - Copyright (C) 2008-2012 Jeroen Frijters - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jeroen Frijters - jeroen@frijters.net - -*/ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// This file was forked from the .NET RuntimeILGenerator implementation as of 1/24/2024, and updated to emit debug +// symbols as it used to on .NET Framework. + +#nullable enable + using System; -using System.Collections.Generic; +using System.Buffers.Binary; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Diagnostics.SymbolStore; -using System.Reflection.Metadata.Ecma335; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using IKVM.Reflection.Writer; - namespace IKVM.Reflection.Emit { - - public sealed class ILGenerator + public class ILGenerator { - struct LabelFixup + #region Const Members + private const int DefaultSize = 16; + private const int DefaultFixupArraySize = 8; + private const int DefaultLabelArraySize = 4; + private const int DefaultExceptionArraySize = 2; + #endregion + + #region Internal Statics + internal static T[] EnlargeArray(T[] incoming) { - internal int label; - internal int offset; + return EnlargeArray(incoming, incoming.Length * 2); } - internal sealed class ExceptionBlock : IComparer + internal static T[] EnlargeArray(T[] incoming, int requiredSize) { + Debug.Assert(incoming != null); - internal readonly int ordinal; - internal Label labelEnd; - internal int tryOffset; - internal int tryLength; - internal int handlerOffset; - internal int handlerLength; - internal int filterOffsetOrExceptionTypeToken; - internal ExceptionHandlingClauseOptions kind; + T[] temp = new T[requiredSize]; + Array.Copy(incoming, temp, incoming.Length); + return temp; + } - /// - /// Initializes a new instance. - /// - /// - internal ExceptionBlock(int ordinal) - { - this.ordinal = ordinal; - } + #endregion - /// - /// Initializes a new instance. - /// - /// - internal ExceptionBlock(ExceptionHandler h) - { - this.ordinal = -1; - this.tryOffset = h.TryOffset; - this.tryLength = h.TryLength; - this.handlerOffset = h.HandlerOffset; - this.handlerLength = h.HandlerLength; - this.kind = h.Kind; - this.filterOffsetOrExceptionTypeToken = kind == ExceptionHandlingClauseOptions.Filter ? h.FilterOffset : h.ExceptionTypeToken; - } + #region Internal Data Members + private int m_length; + private byte[] m_ILStream; - int IComparer.Compare(ExceptionBlock x, ExceptionBlock y) - { - // Mono's sort insists on doing unnecessary comparisons - if (x == y) - return 0; - else if (x.tryOffset == y.tryOffset && x.tryLength == y.tryLength) - return x.ordinal < y.ordinal ? -1 : 1; - else if (x.tryOffset >= y.tryOffset && x.handlerOffset + x.handlerLength <= y.handlerOffset + y.handlerLength) - return -1; - else if (y.tryOffset >= x.tryOffset && y.handlerOffset + y.handlerLength <= x.handlerOffset + x.handlerLength) - return 1; - else - return x.ordinal < y.ordinal ? -1 : 1; - } - } + private __LabelInfo[]? m_labelList; + private int m_labelCount; - struct SequencePoint - { + private __FixupData[]? m_fixupData; - internal ISymbolDocumentWriter document; - internal int offset; - internal int startLine; - internal int startColumn; - internal int endLine; - internal int endColumn; + private int m_fixupCount; - } + private int[]? m_RelocFixupList; + private int m_RelocFixupCount; - sealed class Scope - { + private int m_exceptionCount; + private int m_currExcStackCount; + private __ExceptionInfo[]? m_exceptions; // This is the list of all of the exceptions in this ILStream. + private __ExceptionInfo[]? m_currExcStack; // This is the stack of exceptions which we're currently in. - internal readonly Scope parent; - internal readonly List children = new List(); - internal readonly List locals = new List(); - internal readonly List namespaces = new List(); - internal int startOffset; - internal int endOffset; + internal ScopeTree m_ScopeTree; // this variable tracks all debugging scope information + internal LineNumberInfo m_LineNumberInfo; // this variable tracks all line number information - /// - /// Initializes a new instance. - /// - /// - internal Scope(Scope parent) - { - this.parent = parent; - } + internal MethodBuilder m_methodBuilder; + internal int m_localCount; + internal SignatureHelper m_localSignature; - } + private int m_curDepth; // Current stack depth, with -1 meaning unknown. + private int m_targetDepth; // Stack depth at a target of the previous instruction (when it is branching). + private int m_maxDepth; // Running max of the stack depth. - readonly ModuleBuilder moduleBuilder; - readonly ByteBuffer code; - readonly SignatureHelper locals; - int localsCount; - readonly List tokenFixups = new List(); - readonly List labels = new List(); - readonly List labelStackHeight = new List(); - readonly List labelFixups = new List(); - readonly List sequencePoints = new List(); - readonly List exceptions = new List(); - readonly Stack exceptionStack = new Stack(); - ushort maxStack; - bool fatHeader; - int stackHeight; - Scope scope; - byte exceptionBlockAssistanceMode = EBAM_COMPAT; - const byte EBAM_COMPAT = 0; - const byte EBAM_DISABLE = 1; - const byte EBAM_CLEVER = 2; + // Adjustment to add to m_maxDepth for incorrect/invalid IL. For example, when branch instructions + // with different stack depths target the same label. + private long m_depthAdjustment; - /// - /// Initializes a new instance. - /// - /// - /// - internal ILGenerator(ModuleBuilder moduleBuilder, int initialCapacity) - { - this.code = new ByteBuffer(initialCapacity); - this.moduleBuilder = moduleBuilder; - this.locals = SignatureHelper.GetLocalVarSigHelper(moduleBuilder); + internal int CurrExcStackCount => m_currExcStackCount; -#if NETFRAMEWORK - if (moduleBuilder.symbolWriter != null) - scope = new Scope(null); -#endif - } + internal __ExceptionInfo[]? CurrExcStack => m_currExcStack; + + #endregion - // non-standard API - public void __DisableExceptionBlockAssistance() + #region Constructor + // package private constructor. This code path is used when client create + // ILGenerator through MethodBuilder. + internal ILGenerator(MethodBuilder methodBuilder) : this(methodBuilder, 64) { - exceptionBlockAssistanceMode = EBAM_DISABLE; } - // non-standard API - public void __CleverExceptionBlockAssistance() + internal ILGenerator(MethodBuilder methodBuilder, int size) { - exceptionBlockAssistanceMode = EBAM_CLEVER; + Debug.Assert(methodBuilder != null); + Debug.Assert(methodBuilder is MethodBuilder); + + m_ILStream = new byte[Math.Max(size, DefaultSize)]; + + // initialize the scope tree + m_ScopeTree = new ScopeTree(); + m_LineNumberInfo = new LineNumberInfo(); + m_methodBuilder = methodBuilder; + + // initialize local signature + m_localSignature = SignatureHelper.GetLocalVarSigHelper(methodBuilder.ModuleBuilder); } - // non-standard API - public int __MaxStackSize + #endregion + + #region Internal Members + internal virtual void RecordTokenFixup() { - get { return maxStack; } - set + if (m_RelocFixupList == null) { - maxStack = (ushort)value; - fatHeader = true; + m_RelocFixupList = new int[DefaultFixupArraySize]; + } + else if (m_RelocFixupList.Length <= m_RelocFixupCount) + { + m_RelocFixupList = EnlargeArray(m_RelocFixupList); } - } - - // non-standard API - // returns -1 if the current position is currently unreachable - public int __StackHeight - { - get { return stackHeight; } - } - // new in .NET 4.0 - public int ILOffset - { - get { return code.Position; } + m_RelocFixupList[m_RelocFixupCount++] = m_length; } - public void BeginCatchBlock(Type exceptionType) + internal void InternalEmit(OpCode opcode) { - if (exceptionType == null) + short opcodeValue = opcode.Value; + if (opcode.Size != 1) { - // this must be a catch block after a filter - var block = exceptionStack.Peek(); - if (block.kind != ExceptionHandlingClauseOptions.Filter || block.handlerOffset != 0) - throw new ArgumentNullException("exceptionType"); - - if (exceptionBlockAssistanceMode == EBAM_COMPAT || (exceptionBlockAssistanceMode == EBAM_CLEVER && stackHeight != -1)) - Emit(OpCodes.Endfilter); - - stackHeight = 0; - UpdateStack(1); - block.handlerOffset = code.Position; + BinaryPrimitives.WriteInt16BigEndian(m_ILStream.AsSpan(m_length), opcodeValue); + m_length += 2; } else { - var block = BeginCatchOrFilterBlock(); - block.kind = ExceptionHandlingClauseOptions.Clause; - block.filterOffsetOrExceptionTypeToken = moduleBuilder.GetTypeTokenForMemberRef(exceptionType); - block.handlerOffset = code.Position; + m_ILStream[m_length++] = (byte)opcodeValue; } + + UpdateStackSize(opcode, opcode.StackChange()); } - ExceptionBlock BeginCatchOrFilterBlock() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void UpdateStackSize(OpCode opcode, int stackchange) { - var block = exceptionStack.Peek(); - if (exceptionBlockAssistanceMode == EBAM_COMPAT || (exceptionBlockAssistanceMode == EBAM_CLEVER && stackHeight != -1)) - Emit(OpCodes.Leave, block.labelEnd); + // Updates internal variables for keeping track of the stack size + // requirements for the function. stackchange specifies the amount + // by which the stacksize needs to be updated. - stackHeight = 0; - UpdateStack(1); - - if (block.tryLength == 0) + if (m_curDepth < 0) { - block.tryLength = code.Position - block.tryOffset; + // Current depth is "unknown". We get here when: + // * this is unreachable code. + // * the client uses explicit numeric offsets rather than Labels. + m_curDepth = 0; } - else + + m_curDepth += stackchange; + if (m_curDepth < 0) { - block.handlerLength = code.Position - block.handlerOffset; - exceptionStack.Pop(); - var newBlock = new ExceptionBlock(exceptions.Count); - newBlock.labelEnd = block.labelEnd; - newBlock.tryOffset = block.tryOffset; - newBlock.tryLength = block.tryLength; - block = newBlock; - exceptions.Add(block); - exceptionStack.Push(block); + // Stack underflow. Assume our previous depth computation was flawed. + m_depthAdjustment -= m_curDepth; + m_curDepth = 0; } + else if (m_maxDepth < m_curDepth) + m_maxDepth = m_curDepth; + Debug.Assert(m_depthAdjustment >= 0); + Debug.Assert(m_curDepth >= 0); - return block; - } + // Record the stack depth at a "target" of this instruction. + m_targetDepth = m_curDepth; - public Label BeginExceptionBlock() - { - var block = new ExceptionBlock(exceptions.Count); - block.labelEnd = DefineLabel(); - block.tryOffset = code.Position; - exceptionStack.Push(block); - exceptions.Add(block); - stackHeight = 0; - return block.labelEnd; + // If the current instruction can't fall through, set the depth to unknown. + if (opcode.EndsUncondJmpBlk()) + m_curDepth = -1; } - public void BeginExceptFilterBlock() + private int GetMethodToken(MethodBase method, Type[]? optionalParameterTypes, bool useMethodDef) { - var block = BeginCatchOrFilterBlock(); - block.kind = ExceptionHandlingClauseOptions.Filter; - block.filterOffsetOrExceptionTypeToken = code.Position; + var mi = method is ConstructorInfo ctor ? ctor.GetMethodInfo() : (MethodInfo)method; + if (optionalParameterTypes == null || optionalParameterTypes.Length == 0) + return m_methodBuilder.ModuleBuilder.GetMethodTokenForIL(mi).Token; + else + return m_methodBuilder.ModuleBuilder.__GetMethodToken(mi, optionalParameterTypes, null).Token; } - public void BeginFaultBlock() + internal SignatureHelper GetMemberRefSignature( + CallingConventions call, + Type? returnType, + Type[]? parameterTypes, + Type[]? optionalParameterTypes) { - BeginFinallyFaultBlock(ExceptionHandlingClauseOptions.Fault); - } + var sig = SignatureHelper.GetMethodSigHelper(m_methodBuilder.Module, call, returnType); + sig.AddArguments(parameterTypes, null, null); + if (optionalParameterTypes != null && optionalParameterTypes.Length != 0) + { + sig.AddSentinel(); + sig.AddArguments(optionalParameterTypes, null, null); + } - public void BeginFinallyBlock() - { - BeginFinallyFaultBlock(ExceptionHandlingClauseOptions.Finally); + return sig; } - void BeginFinallyFaultBlock(ExceptionHandlingClauseOptions kind) + internal byte[]? BakeByteArray() { - var block = exceptionStack.Peek(); - if (exceptionBlockAssistanceMode == EBAM_COMPAT || (exceptionBlockAssistanceMode == EBAM_CLEVER && stackHeight != -1)) - Emit(OpCodes.Leave, block.labelEnd); + // BakeByteArray is an internal function designed to be called by MethodBuilder to do + // all of the fixups and return a new byte array representing the byte stream with labels resolved, etc. - if (block.handlerOffset == 0) + if (m_currExcStackCount != 0) { - block.tryLength = code.Position - block.tryOffset; + throw new ArgumentException("The IL Generator cannot be used while there are unclosed exceptions."); } - else + + if (m_length == 0) + return null; + + // Allocate space for the new array. + byte[] newBytes = new byte[m_length]; + + // Copy the data from the old array + Array.Copy(m_ILStream, newBytes, m_length); + + // Do the fixups. + // This involves iterating over all of the labels and + // replacing them with their proper values. + for (int i = 0; i < m_fixupCount; i++) { - block.handlerLength = code.Position - block.handlerOffset; - Label labelEnd; - if (exceptionBlockAssistanceMode != EBAM_COMPAT) + __FixupData fixupData = m_fixupData![i]; + int updateAddr = GetLabelPos(fixupData.m_fixupLabel) - (fixupData.m_fixupPos + fixupData.m_fixupInstSize); + + // Handle single byte instructions + // Throw an exception if they're trying to store a jump in a single byte instruction that doesn't fit. + if (fixupData.m_fixupInstSize == 1) { - labelEnd = block.labelEnd; + // Verify that our one-byte arg will fit into a Signed Byte. + if (updateAddr < sbyte.MinValue || updateAddr > sbyte.MaxValue) + { + throw new NotSupportedException(string.Format("Illegal one-byte branch at position: {0}. Requested branch was: {1}.", fixupData.m_fixupPos, updateAddr)); + } + + // Place the one-byte arg + newBytes[fixupData.m_fixupPos] = (byte)updateAddr; } else { - MarkLabel(block.labelEnd); - labelEnd = DefineLabel(); - Emit(OpCodes.Leave, labelEnd); + // Place the four-byte arg + BinaryPrimitives.WriteInt32LittleEndian(newBytes.AsSpan(fixupData.m_fixupPos), updateAddr); } - exceptionStack.Pop(); - - var newBlock = new ExceptionBlock(exceptions.Count); - newBlock.labelEnd = labelEnd; - newBlock.tryOffset = block.tryOffset; - newBlock.tryLength = code.Position - block.tryOffset; - block = newBlock; - exceptions.Add(block); - exceptionStack.Push(block); } - - block.handlerOffset = code.Position; - block.kind = kind; - stackHeight = 0; + return newBytes; } - public void EndExceptionBlock() + internal __ExceptionInfo[]? GetExceptions() { - var block = exceptionStack.Pop(); - if (exceptionBlockAssistanceMode == EBAM_COMPAT || (exceptionBlockAssistanceMode == EBAM_CLEVER && stackHeight != -1)) + if (m_currExcStackCount != 0) { - if (block.kind != ExceptionHandlingClauseOptions.Finally && block.kind != ExceptionHandlingClauseOptions.Fault) - Emit(OpCodes.Leave, block.labelEnd); - else - Emit(OpCodes.Endfinally); + throw new NotSupportedException("The IL Generator cannot be used while there are unclosed exceptions."); } - MarkLabel(block.labelEnd); - block.handlerLength = code.Position - block.handlerOffset; - } + if (m_exceptionCount == 0) + { + return null; + } - public void BeginScope() - { - var newScope = new Scope(scope); - scope?.children.Add(newScope); - scope = newScope; - scope.startOffset = code.Position; + var temp = new __ExceptionInfo[m_exceptionCount]; + Array.Copy(m_exceptions!, temp, m_exceptionCount); + SortExceptions(temp); + return temp; } - public void UsingNamespace(string usingNamespace) + internal void EnsureCapacity(int size) { - scope?.namespaces.Add(usingNamespace); + // Guarantees an array capable of holding at least size elements. + if (m_length + size >= m_ILStream.Length) + { + IncreaseCapacity(size); + } } - public LocalBuilder DeclareLocal(Type localType) + private void IncreaseCapacity(int size) { - return DeclareLocal(localType, false); + byte[] temp = new byte[Math.Max(m_ILStream.Length * 2, m_length + size)]; + Array.Copy(m_ILStream, temp, m_ILStream.Length); + m_ILStream = temp; } - public LocalBuilder DeclareLocal(Type localType, bool pinned) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PutInteger4(int value) { - var local = new LocalBuilder(localType, localsCount++, pinned); - locals.AddArgument(localType, pinned); - scope?.locals.Add(local); - return local; + BinaryPrimitives.WriteInt32LittleEndian(m_ILStream.AsSpan(m_length), value); + m_length += 4; } - public LocalBuilder __DeclareLocal(Type localType, bool pinned, CustomModifiers customModifiers) + private int GetLabelPos(Label lbl) { - var local = new LocalBuilder(localType, localsCount++, pinned, customModifiers); - locals.__AddArgument(localType, pinned, customModifiers); - scope?.locals.Add(local); - return local; - } + // Gets the position in the stream of a particular label. + // Verifies that the label exists and that it has been given a value. - public Label DefineLabel() - { - var label = new Label(labels.Count); - labels.Add(-1); - labelStackHeight.Add(-1); - return label; + int index = lbl.Id; + + if (index < 0 || index >= m_labelCount || m_labelList is null) + throw new ArgumentException("Bad label in ILGenerator."); + + int pos = m_labelList[index].m_pos; + if (pos < 0) + throw new ArgumentException("Bad label content in ILGenerator."); + + return pos; } - public void Emit(OpCode opc) + private void AddFixup(Label lbl, int pos, int instSize) { - if (opc == OpCodes.Ret && stackHeight > 1) - throw new BadImageFormatException("Unbalanced stack height."); + // Notes the label, position, and instruction size of a new fixup. Expands + // all of the fixup arrays as appropriate. - if (opc.Value < 0) - code.Write((byte)(opc.Value >> 8)); + if (m_fixupData == null) + { + m_fixupData = new __FixupData[DefaultFixupArraySize]; + } + else if (m_fixupData.Length <= m_fixupCount) + { + m_fixupData = EnlargeArray(m_fixupData); + } - code.Write((byte)opc.Value); - switch (opc.FlowControl) + m_fixupData[m_fixupCount++] = new __FixupData + { + m_fixupPos = pos, + m_fixupLabel = lbl, + m_fixupInstSize = instSize + }; + + int labelIndex = lbl.Id; + if (labelIndex < 0 || labelIndex >= m_labelCount || m_labelList is null) + throw new ArgumentException("Bad label in ILGenerator."); + + int depth = m_labelList[labelIndex].m_depth; + int targetDepth = m_targetDepth; + Debug.Assert(depth >= -1); + Debug.Assert(targetDepth >= -1); + if (depth < targetDepth) { - case FlowControl.Branch: - case FlowControl.Break: - case FlowControl.Return: - case FlowControl.Throw: - stackHeight = -1; - break; - default: - UpdateStack(opc.StackDiff); - break; + // Either unknown depth for this label or this branch location has a larger depth than previously recorded. + // In the latter case, the IL is (likely) invalid, but we just compensate for it. + if (depth >= 0) + m_depthAdjustment += targetDepth - depth; + m_labelList[labelIndex].m_depth = targetDepth; } } - void UpdateStack(int stackdiff) + internal int GetMaxStackSize() { - if (stackHeight == -1) - stackHeight = 0; // we're about to emit code that is either unreachable or reachable only via a backward branch - - Debug.Assert(stackHeight >= 0 && stackHeight <= ushort.MaxValue); - stackHeight += stackdiff; - Debug.Assert(stackHeight >= 0 && stackHeight <= ushort.MaxValue); - maxStack = Math.Max(maxStack, (ushort)stackHeight); + // Limit the computed max stack to 2^16 - 1, since the value is mod`ed by 2^16 by other code. + Debug.Assert(m_depthAdjustment >= 0); + return (int)Math.Min(ushort.MaxValue, m_maxDepth + m_depthAdjustment); } - public void Emit(OpCode opc, byte arg) + private static void SortExceptions(__ExceptionInfo[] exceptions) { - Emit(opc); - code.Write(arg); - } + // In order to call exceptions properly we have to sort them in ascending order by their end position. + // Just a cheap insertion sort. We don't expect many exceptions (<10), where InsertionSort beats QuickSort. + // If we have more exceptions than this in real life, we should consider moving to a QuickSort. - public void Emit(OpCode opc, double arg) - { - Emit(opc); - code.Write(arg); + for (int i = 0; i < exceptions.Length; i++) + { + int least = i; + for (int j = i + 1; j < exceptions.Length; j++) + { + if (exceptions[least].IsInner(exceptions[j])) + { + least = j; + } + } + __ExceptionInfo temp = exceptions[i]; + exceptions[i] = exceptions[least]; + exceptions[least] = temp; + } } - public void Emit(OpCode opc, FieldInfo field) + internal int[]? GetTokenFixups() { - Emit(opc); - WriteToken(moduleBuilder.GetFieldToken(field).Token); + if (m_RelocFixupCount == 0) + { + Debug.Assert(m_RelocFixupList == null); + return null; + } + + int[] narrowTokens = new int[m_RelocFixupCount]; + Array.Copy(m_RelocFixupList!, narrowTokens, m_RelocFixupCount); + return narrowTokens; } + #endregion - public void Emit(OpCode opc, short arg) + #region Public Members + + #region Emit + public void Emit(OpCode opcode) { - Emit(opc); - code.Write(arg); + EnsureCapacity(3); + InternalEmit(opcode); } - public void Emit(OpCode opc, int arg) + public void Emit(OpCode opcode, byte arg) { - Emit(opc); - code.Write(arg); + EnsureCapacity(4); + InternalEmit(opcode); + m_ILStream[m_length++] = arg; } - public void Emit(OpCode opc, long arg) + public void Emit(OpCode opcode, short arg) { - Emit(opc); - code.Write(arg); + // Puts opcode onto the stream of instructions followed by arg + EnsureCapacity(5); + InternalEmit(opcode); + BinaryPrimitives.WriteInt16LittleEndian(m_ILStream.AsSpan(m_length), arg); + m_length += 2; } - public void Emit(OpCode opc, Label label) + public void Emit(OpCode opcode, int arg) { - // We need special stackHeight handling for unconditional branches, - // because the branch and next flows have differing stack heights. - // Note that this assumes that unconditional branches do not push/pop. - int flowStackHeight = this.stackHeight; - Emit(opc); - if (opc == OpCodes.Leave || opc == OpCodes.Leave_S) - flowStackHeight = 0; - else if (opc.FlowControl != FlowControl.Branch) - flowStackHeight = this.stackHeight; + // Special-case several opcodes that have shorter variants for common values. + if (opcode.Equals(OpCodes.Ldc_I4)) + { + if (arg >= -1 && arg <= 8) + { + opcode = arg switch + { + -1 => OpCodes.Ldc_I4_M1, + 0 => OpCodes.Ldc_I4_0, + 1 => OpCodes.Ldc_I4_1, + 2 => OpCodes.Ldc_I4_2, + 3 => OpCodes.Ldc_I4_3, + 4 => OpCodes.Ldc_I4_4, + 5 => OpCodes.Ldc_I4_5, + 6 => OpCodes.Ldc_I4_6, + 7 => OpCodes.Ldc_I4_7, + _ => OpCodes.Ldc_I4_8, + }; + Emit(opcode); + return; + } - // if the label has already been marked, we can emit the branch offset directly - if (labels[label.Index] != -1) + if (arg >= -128 && arg <= 127) + { + Emit(OpCodes.Ldc_I4_S, (sbyte)arg); + return; + } + } + else if (opcode.Equals(OpCodes.Ldarg)) { - if (labelStackHeight[label.Index] != flowStackHeight && (labelStackHeight[label.Index] != 0 || flowStackHeight != -1)) + if ((uint)arg <= 3) { - // the "backward branch constraint" prohibits this, so we don't need to support it - throw new NotSupportedException("'Backward branch constraints' violated"); + Emit(arg switch + { + 0 => OpCodes.Ldarg_0, + 1 => OpCodes.Ldarg_1, + 2 => OpCodes.Ldarg_2, + _ => OpCodes.Ldarg_3, + }); + return; } - if (opc.OperandType == OperandType.ShortInlineBrTarget) + + if ((uint)arg <= byte.MaxValue) { - WriteByteBranchOffset(labels[label.Index] - (code.Position + 1)); + Emit(OpCodes.Ldarg_S, (byte)arg); + return; } - else + + if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode { - code.Write(labels[label.Index] - (code.Position + 4)); + Emit(OpCodes.Ldarg, (short)arg); + return; } } - else + else if (opcode.Equals(OpCodes.Ldarga)) { - Debug.Assert(labelStackHeight[label.Index] == -1 || labelStackHeight[label.Index] == flowStackHeight || (flowStackHeight == -1 && labelStackHeight[label.Index] == 0)); - labelStackHeight[label.Index] = flowStackHeight; - var fix = new LabelFixup(); - fix.label = label.Index; - fix.offset = code.Position; - labelFixups.Add(fix); - if (opc.OperandType == OperandType.ShortInlineBrTarget) - code.Write((byte)1); - else - code.Write(4); - } - } - - void WriteByteBranchOffset(int offset) - { - if (offset < -128 || offset > 127) - throw new NotSupportedException("Branch offset of " + offset + " does not fit in one-byte branch target at position " + code.Position); - - code.Write((byte)offset); - } - - public void Emit(OpCode opc, Label[] labels) - { - Emit(opc); + if ((uint)arg <= byte.MaxValue) + { + Emit(OpCodes.Ldarga_S, (byte)arg); + return; + } - var fix = new LabelFixup(); - fix.label = -1; - fix.offset = code.Position; - labelFixups.Add(fix); - code.Write(labels.Length); - foreach (var label in labels) + if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode + { + Emit(OpCodes.Ldarga, (short)arg); + return; + } + } + else if (opcode.Equals(OpCodes.Starg)) { - code.Write(label.Index); - if (this.labels[label.Index] != -1) + if ((uint)arg <= byte.MaxValue) { - // the "backward branch constraint" prohibits this, so we don't need to support it - if (labelStackHeight[label.Index] != stackHeight) - throw new NotSupportedException(); + Emit(OpCodes.Starg_S, (byte)arg); + return; } - else + + if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode { - Debug.Assert(labelStackHeight[label.Index] == -1 || labelStackHeight[label.Index] == stackHeight); - labelStackHeight[label.Index] = stackHeight; + Emit(OpCodes.Starg, (short)arg); + return; } } + + // For everything else, put the opcode followed by the arg onto the stream of instructions. + EnsureCapacity(7); + InternalEmit(opcode); + PutInteger4(arg); } - public void Emit(OpCode opc, LocalBuilder local) + public void Emit(OpCode opcode, MethodInfo meth) { - if ((opc == OpCodes.Ldloc || opc == OpCodes.Ldloca || opc == OpCodes.Stloc) && local.LocalIndex < 256) + if (meth is null) + throw new ArgumentNullException(nameof(meth)); + + if (opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj)) { - if (opc == OpCodes.Ldloc) - { - switch (local.LocalIndex) - { - case 0: - Emit(OpCodes.Ldloc_0); - break; - case 1: - Emit(OpCodes.Ldloc_1); - break; - case 2: - Emit(OpCodes.Ldloc_2); - break; - case 3: - Emit(OpCodes.Ldloc_3); - break; - default: - Emit(OpCodes.Ldloc_S); - code.Write((byte)local.LocalIndex); - break; - } - } - else if (opc == OpCodes.Ldloca) - { - Emit(OpCodes.Ldloca_S); - code.Write((byte)local.LocalIndex); - } - else if (opc == OpCodes.Stloc) - { - switch (local.LocalIndex) - { - case 0: - Emit(OpCodes.Stloc_0); - break; - case 1: - Emit(OpCodes.Stloc_1); - break; - case 2: - Emit(OpCodes.Stloc_2); - break; - case 3: - Emit(OpCodes.Stloc_3); - break; - default: - Emit(OpCodes.Stloc_S); - code.Write((byte)local.LocalIndex); - break; - } - } + EmitCall(opcode, meth, null); } else { - Emit(opc); - switch (opc.OperandType) - { - case OperandType.InlineVar: - code.Write((ushort)local.LocalIndex); - break; - case OperandType.ShortInlineVar: - code.Write((byte)local.LocalIndex); - break; - } + // Reflection doesn't distinguish between these two concepts: + // 1. A generic method definition: Foo`1 + // 2. A generic method definition instantiated over its own generic arguments: Foo`1 + // In RefEmit, we always want 1 for Ld* opcodes and 2 for Call* and Newobj. + bool useMethodDef = opcode.Equals(OpCodes.Ldtoken) || opcode.Equals(OpCodes.Ldftn) || opcode.Equals(OpCodes.Ldvirtftn); + int tk = GetMethodToken(meth, null, useMethodDef); + + EnsureCapacity(7); + InternalEmit(opcode); + + UpdateStackSize(opcode, 0); + RecordTokenFixup(); + PutInteger4(tk); } } - void WriteToken(int token) + public void EmitCalli(OpCode opcode, CallingConventions callingConvention, + Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) { - if (ModuleBuilder.IsPseudoToken(token)) - tokenFixups.Add(code.Position); + int stackchange = 0; + if (optionalParameterTypes != null) + { + if ((callingConvention & CallingConventions.VarArgs) == 0) + { + // Client should not supply optional parameter in default calling convention + throw new InvalidOperationException("Calling convention must be VarArgs."); + } + } + + ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.ModuleBuilder; + SignatureHelper sig = GetMemberRefSignature(callingConvention, + returnType, + parameterTypes, + optionalParameterTypes); + + EnsureCapacity(7); + Emit(OpCodes.Calli); + + // If there is a non-void return type, push one. + if (returnType != modBuilder.Universe.System_Void) + stackchange++; + // Pop off arguments if any. + if (parameterTypes != null) + stackchange -= parameterTypes.Length; + // Pop off vararg arguments. + if (optionalParameterTypes != null) + stackchange -= optionalParameterTypes.Length; + // Pop the this parameter if the method has a this parameter. + if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis) + stackchange--; + // Pop the native function pointer. + stackchange--; + UpdateStackSize(OpCodes.Calli, stackchange); - code.Write(token); + RecordTokenFixup(); + PutInteger4(modBuilder.GetSignatureToken(sig).Token); } - void UpdateStack(OpCode opc, bool hasthis, Type returnType, int parameterCount) + public void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes) { - if (opc == OpCodes.Jmp) + int stackchange = 0; + int cParams = 0; + + ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module; + + if (parameterTypes != null) { - stackHeight = -1; + cParams = parameterTypes.Length; } - else if (opc.FlowControl == FlowControl.Call) + + SignatureHelper sig = SignatureHelper.GetMethodSigHelper( + modBuilder, + unmanagedCallConv, + returnType); + + if (parameterTypes != null) { - int stackdiff = 0; - if ((hasthis && opc != OpCodes.Newobj) || opc == OpCodes.Calli) + for (int i = 0; i < cParams; i++) { - // pop this - stackdiff--; + sig.AddArgument(parameterTypes[i]); } - // pop parameters - stackdiff -= parameterCount; - if (returnType != moduleBuilder.universe.System_Void) - { - // push return value - stackdiff++; - } - UpdateStack(stackdiff); } - } - public void Emit(OpCode opc, MethodInfo method) - { - UpdateStack(opc, method.HasThis, method.ReturnType, method.ParameterCount); - Emit(opc); - WriteToken(moduleBuilder.GetMethodTokenForIL(method).Token); - } + // If there is a non-void return type, push one. + if (returnType != modBuilder.Universe.System_Void) + stackchange++; - public void Emit(OpCode opc, ConstructorInfo constructor) - { - Emit(opc, constructor.GetMethodInfo()); - } + // Pop off arguments if any. + if (parameterTypes != null) + stackchange -= cParams; - public void Emit(OpCode opc, sbyte arg) - { - Emit(opc); - code.Write(arg); - } + // Pop the native function pointer. + stackchange--; + UpdateStackSize(OpCodes.Calli, stackchange); - public void Emit(OpCode opc, float arg) - { - Emit(opc); - code.Write(arg); + EnsureCapacity(7); + Emit(OpCodes.Calli); + RecordTokenFixup(); + PutInteger4(modBuilder.GetSignatureToken(sig).Token); } - public void Emit(OpCode opc, string str) + public void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) { - Emit(opc); - code.Write(moduleBuilder.GetStringConstant(str).Token); - } + if (methodInfo is null) + throw new ArgumentNullException(nameof(methodInfo)); - public void Emit(OpCode opc, Type type) - { - Emit(opc); - if (opc == OpCodes.Ldtoken) - code.Write(moduleBuilder.GetTypeToken(type).Token); - else - code.Write(moduleBuilder.GetTypeTokenForMemberRef(type)); + if (!(opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj))) + throw new ArgumentException("The specified opcode cannot be passed to EmitCall.", nameof(opcode)); + + int stackchange = 0; + int tk = GetMethodToken(methodInfo, optionalParameterTypes, false); + + EnsureCapacity(7); + InternalEmit(opcode); + + // Push the return value if there is one. + if (methodInfo.ReturnType != m_methodBuilder.ModuleBuilder.Universe.System_Void) + stackchange++; + // Pop the parameters. + Type[] parameters = methodInfo.GetParameterTypes(); + if (parameters != null) + stackchange -= parameters.Length; + + // Pop the this parameter if the method is non-static and the + // instruction is not newobj. + if (/*!(methodInfo is SymbolMethod) && */!methodInfo.IsStatic && !opcode.Equals(OpCodes.Newobj)) + stackchange--; + // Pop the optional parameters off the stack. + if (optionalParameterTypes != null) + stackchange -= optionalParameterTypes.Length; + UpdateStackSize(opcode, stackchange); + + RecordTokenFixup(); + PutInteger4(tk); } public void Emit(OpCode opcode, SignatureHelper signature) { - Emit(opcode); - UpdateStack(opcode, signature.HasThis, signature.ReturnType, signature.ParameterCount); - code.Write(moduleBuilder.GetSignatureToken(signature).Token); - } + if (signature is null) + throw new ArgumentNullException(nameof(signature)); - public void EmitCall(OpCode opc, MethodInfo method, Type[] optionalParameterTypes) - { - __EmitCall(opc, method, optionalParameterTypes, null); + int stackchange = 0; + ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module; + int sig = modBuilder.GetSignatureToken(signature).Token; + + int tempVal = sig; + + EnsureCapacity(7); + InternalEmit(opcode); + + // The only IL instruction that has VarPop behaviour, that takes a + // Signature token as a parameter is calli. Pop the parameters and + // the native function pointer. To be conservative, do not pop the + // this pointer since this information is not easily derived from + // SignatureHelper. + if (opcode.StackBehaviourPop == StackBehaviour.Varpop) + { + Debug.Assert(opcode.Equals(OpCodes.Calli), + "Unexpected opcode encountered for StackBehaviour VarPop."); + // Pop the arguments.. + stackchange -= signature.ArgumentCount; + // Pop native function pointer off the stack. + stackchange--; + UpdateStackSize(opcode, stackchange); + } + + RecordTokenFixup(); + PutInteger4(tempVal); } - public void __EmitCall(OpCode opc, MethodInfo method, Type[] optionalParameterTypes, CustomModifiers[] customModifiers) + public void Emit(OpCode opcode, ConstructorInfo con) { - if (optionalParameterTypes == null || optionalParameterTypes.Length == 0) + if (con is null) + throw new ArgumentNullException(nameof(con)); + + int stackchange = 0; + + // Constructors cannot be generic so the value of UseMethodDef doesn't matter. + int tk = GetMethodToken(con, null, true); + + EnsureCapacity(7); + InternalEmit(opcode); + + // Make a conservative estimate by assuming a return type and no + // this parameter. + if (opcode.StackBehaviourPush == StackBehaviour.Varpush) { - Emit(opc, method); + // Instruction must be one of call or callvirt. + Debug.Assert(opcode.Equals(OpCodes.Call) || + opcode.Equals(OpCodes.Callvirt), + "Unexpected opcode encountered for StackBehaviour of VarPush."); + stackchange++; } - else + if (opcode.StackBehaviourPop == StackBehaviour.Varpop) { - Emit(opc); - UpdateStack(opc, method.HasThis, method.ReturnType, method.ParameterCount + optionalParameterTypes.Length); - code.Write(moduleBuilder.__GetMethodToken(method, optionalParameterTypes, customModifiers).Token); + // Instruction must be one of call, callvirt or newobj. + Debug.Assert(opcode.Equals(OpCodes.Call) || + opcode.Equals(OpCodes.Callvirt) || + opcode.Equals(OpCodes.Newobj), + "Unexpected opcode encountered for StackBehaviour of VarPop."); + + Type[] parameters = con.GetParameterTypes(); + if (parameters != null) + stackchange -= parameters.Length; } + UpdateStackSize(opcode, stackchange); + + RecordTokenFixup(); + PutInteger4(tk); } - public void __EmitCall(OpCode opc, ConstructorInfo constructor, Type[] optionalParameterTypes) + public void Emit(OpCode opcode, Type cls) { - EmitCall(opc, constructor.GetMethodInfo(), optionalParameterTypes); + // Puts opcode onto the stream and then the metadata token represented + // by cls. The location of cls is recorded so that the token can be + // patched if necessary when persisting the module to a PE. + + ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module; + bool getGenericDefinition = (opcode == OpCodes.Ldtoken && cls != null && cls.IsGenericTypeDefinition); + int tempVal = getGenericDefinition ? modBuilder.GetTypeToken(cls).Token : modBuilder.GetTypeTokenForMemberRef(cls); + + EnsureCapacity(7); + InternalEmit(opcode); + RecordTokenFixup(); + PutInteger4(tempVal); } - public void __EmitCall(OpCode opc, ConstructorInfo constructor, Type[] optionalParameterTypes, CustomModifiers[] customModifiers) + public void Emit(OpCode opcode, long arg) { - __EmitCall(opc, constructor.GetMethodInfo(), optionalParameterTypes, customModifiers); + EnsureCapacity(11); + InternalEmit(opcode); + BinaryPrimitives.WriteInt64LittleEndian(m_ILStream.AsSpan(m_length), arg); + m_length += 8; } - public void EmitCalli(OpCode opc, CallingConvention callingConvention, Type returnType, Type[] parameterTypes) + public void Emit(OpCode opcode, float arg) { - var sig = SignatureHelper.GetMethodSigHelper(moduleBuilder, callingConvention, returnType); - sig.AddArguments(parameterTypes, null, null); - Emit(opc, sig); + EnsureCapacity(7); + InternalEmit(opcode); + BinaryPrimitives.WriteInt32LittleEndian(m_ILStream.AsSpan(m_length), SingleToInt32Bits(arg)); + m_length += 4; } - public void EmitCalli(OpCode opc, CallingConventions callingConvention, Type returnType, Type[] parameterTypes, Type[] optionalParameterTypes) + /// + /// Converts the specified single-precision floating point number to a 32-bit signed integer. + /// + /// The number to convert. + /// A 32-bit signed integer whose bits are identical to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static unsafe int SingleToInt32Bits(float value) { - var sig = SignatureHelper.GetMethodSigHelper(moduleBuilder, callingConvention, returnType); - sig.AddArguments(parameterTypes, null, null); - if (optionalParameterTypes != null && optionalParameterTypes.Length != 0) - { - sig.AddSentinel(); - sig.AddArguments(optionalParameterTypes, null, null); - } - Emit(opc, sig); +#if NETFRAMEWORK + return *((int*)&value); +#else + return BitConverter.SingleToInt32Bits(value); +#endif + } + + public void Emit(OpCode opcode, double arg) + { + EnsureCapacity(11); + InternalEmit(opcode); + BinaryPrimitives.WriteInt64LittleEndian(m_ILStream.AsSpan(m_length), BitConverter.DoubleToInt64Bits(arg)); + m_length += 8; } - public void __EmitCalli(OpCode opc, __StandAloneMethodSig sig) + public void Emit(OpCode opcode, Label label) { - Emit(opc); - if (sig.IsUnmanaged) + // Puts opcode onto the stream and leaves space to include label + // when fixups are done. Labels are created using ILGenerator.DefineLabel and + // their location within the stream is fixed by using ILGenerator.MarkLabel. + // If a single-byte instruction (designated by the _S suffix in OpCodes.cs) is used, + // the label can represent a jump of at most 127 bytes along the stream. + // + // opcode must represent a branch instruction (although we don't explicitly + // verify this). Since branches are relative instructions, label will be replaced with the + // correct offset to branch during the fixup process. + + EnsureCapacity(7); + + InternalEmit(opcode); + if (OpCodes.TakesSingleByteArgument(opcode)) { - UpdateStack(opc, false, sig.ReturnType, sig.ParameterCount); + AddFixup(label, m_length++, 1); } else { - CallingConventions callingConvention = sig.CallingConvention; - UpdateStack(opc, (callingConvention & CallingConventions.HasThis | CallingConventions.ExplicitThis) == CallingConventions.HasThis, sig.ReturnType, sig.ParameterCount); + AddFixup(label, m_length, 4); + m_length += 4; } - var bb = new ByteBuffer(16); - Signature.WriteStandAloneMethodSig(moduleBuilder, bb, sig); - code.Write(MetadataTokens.GetToken(MetadataTokens.StandaloneSignatureHandle(moduleBuilder.StandAloneSigTable.FindOrAddRecord(moduleBuilder.GetOrAddBlob(bb.ToArray()))))); } - public void EmitWriteLine(string text) + public void Emit(OpCode opcode, Label[] labels) { - var u = moduleBuilder.universe; - Emit(OpCodes.Ldstr, text); - Emit(OpCodes.Call, u.System_Console.GetMethod("WriteLine", new Type[] { u.System_String })); - } + if (labels is null) + throw new ArgumentNullException(nameof(labels)); - public void EmitWriteLine(FieldInfo field) - { - var u = moduleBuilder.universe; - Emit(OpCodes.Call, u.System_Console.GetMethod("get_Out")); - if (field.IsStatic) - { - Emit(OpCodes.Ldsfld, field); - } - else + // Emitting a switch table + + int i; + int remaining; // number of bytes remaining for this switch instruction to be subtracted + // for computing the offset + + int count = labels.Length; + + EnsureCapacity(count * 4 + 7); + InternalEmit(opcode); + PutInteger4(count); + for (remaining = count * 4, i = 0; remaining > 0; remaining -= 4, i++) { - Emit(OpCodes.Ldarg_0); - Emit(OpCodes.Ldfld, field); + AddFixup(labels[i], m_length, remaining); + m_length += 4; } - Emit(OpCodes.Callvirt, u.System_IO_TextWriter.GetMethod("WriteLine", new Type[] { field.FieldType })); } - public void EmitWriteLine(LocalBuilder local) + public void Emit(OpCode opcode, FieldInfo field) { - var u = moduleBuilder.universe; - Emit(OpCodes.Call, u.System_Console.GetMethod("get_Out")); - Emit(OpCodes.Ldloc, local); - Emit(OpCodes.Callvirt, u.System_IO_TextWriter.GetMethod("WriteLine", new Type[] { local.LocalType })); + ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module; + int tempVal = modBuilder.GetFieldToken(field).Token; + EnsureCapacity(7); + InternalEmit(opcode); + RecordTokenFixup(); + PutInteger4(tempVal); } - public void EndScope() + public void Emit(OpCode opcode, string str) { - scope.endOffset = code.Position; - scope = scope.parent; + // Puts the opcode onto the IL stream followed by the metadata token + // represented by str. The location of str is recorded for future + // fixups if the module is persisted to a PE. + + ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module; + int tempVal = modBuilder.GetStringConstant(str).Token; + EnsureCapacity(7); + InternalEmit(opcode); + PutInteger4(tempVal); } - public void MarkLabel(Label loc) + public void Emit(OpCode opcode, LocalBuilder local) { - Debug.Assert(stackHeight == -1 || labelStackHeight[loc.Index] == -1 || stackHeight == labelStackHeight[loc.Index]); - labels[loc.Index] = code.Position; - if (labelStackHeight[loc.Index] == -1) + if (local is null) + throw new ArgumentNullException(nameof(local)); + + // Puts the opcode onto the IL stream followed by the information for local variable local. + int tempVal = local.LocalIndex; + if (local.Method != m_methodBuilder) { - if (stackHeight == -1) + throw new ArgumentException("Local passed in does not belong to this ILGenerator.", nameof(local)); + } + // If the instruction is a ldloc, ldloca a stloc, morph it to the optimal form. + if (opcode.Equals(OpCodes.Ldloc)) + { + switch (tempVal) { - // We're at a location that can only be reached by a backward branch, - // so according to the "backward branch constraint" that must mean the stack is empty, - // but note that this may be an unused label followed by another label that is used and - // that does have a non-zero stack height, so we don't yet set stackHeight here. - labelStackHeight[loc.Index] = 0; + case 0: + opcode = OpCodes.Ldloc_0; + break; + case 1: + opcode = OpCodes.Ldloc_1; + break; + case 2: + opcode = OpCodes.Ldloc_2; + break; + case 3: + opcode = OpCodes.Ldloc_3; + break; + default: + if (tempVal <= 255) + opcode = OpCodes.Ldloc_S; + break; } - else + } + else if (opcode.Equals(OpCodes.Stloc)) + { + switch (tempVal) { - labelStackHeight[loc.Index] = stackHeight; + case 0: + opcode = OpCodes.Stloc_0; + break; + case 1: + opcode = OpCodes.Stloc_1; + break; + case 2: + opcode = OpCodes.Stloc_2; + break; + case 3: + opcode = OpCodes.Stloc_3; + break; + default: + if (tempVal <= 255) + opcode = OpCodes.Stloc_S; + break; } } - else + else if (opcode.Equals(OpCodes.Ldloca)) { - Debug.Assert(stackHeight == -1 || stackHeight == labelStackHeight[loc.Index]); - stackHeight = labelStackHeight[loc.Index]; + if (tempVal <= 255) + opcode = OpCodes.Ldloca_S; } - } - public void MarkSequencePoint(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) - { - var sp = new SequencePoint(); - sp.document = document; - sp.offset = code.Position; - sp.startLine = startLine; - sp.startColumn = startColumn; - sp.endLine = endLine; - sp.endColumn = endColumn; - sequencePoints.Add(sp); - } + EnsureCapacity(7); + InternalEmit(opcode); - public void ThrowException(Type excType) - { - Emit(OpCodes.Newobj, excType.GetConstructor(Type.EmptyTypes)); - Emit(OpCodes.Throw); - } + if (opcode.OperandType == OperandType.InlineNone) + return; - internal int WriteBody(bool initLocals) - { -#if NETFRAMEWORK - if (moduleBuilder.symbolWriter != null) + if (!OpCodes.TakesSingleByteArgument(opcode)) { - Debug.Assert(scope != null && scope.parent == null); - scope.endOffset = code.Position; + BinaryPrimitives.WriteInt16LittleEndian(m_ILStream.AsSpan(m_length), (short)tempVal); + m_length += 2; } -#endif - - ResolveBranches(); - - var bb = moduleBuilder.methodBodies; - - int localVarSigTok = 0; - - int rva; - if (localsCount == 0 && exceptions.Count == 0 && maxStack <= 8 && code.Length < 64 && !fatHeader) + else { - rva = WriteTinyHeaderAndCode(bb); + // Handle stloc_1, ldloc_1 + if (tempVal > byte.MaxValue) + { + throw new InvalidOperationException("Opcodes using a short-form index cannot address a local position over 255."); + } + m_ILStream[m_length++] = (byte)tempVal; } - else + } + #endregion + + #region Exceptions + public Label BeginExceptionBlock() + { + // Begin an Exception block. Creating an Exception block records some information, + // but does not actually emit any IL onto the stream. Exceptions should be created and + // marked in the following form: + // + // Emit Some IL + // BeginExceptionBlock + // Emit the IL which should appear within the "try" block + // BeginCatchBlock + // Emit the IL which should appear within the "catch" block + // Optional: BeginCatchBlock (this can be repeated an arbitrary number of times + // EndExceptionBlock + + // Delay init + m_exceptions ??= new __ExceptionInfo[DefaultExceptionArraySize]; + m_currExcStack ??= new __ExceptionInfo[DefaultExceptionArraySize]; + + if (m_exceptionCount >= m_exceptions.Length) { - if (localsCount != 0) - localVarSigTok = moduleBuilder.GetSignatureToken(locals).Token; + m_exceptions = EnlargeArray(m_exceptions); + } - rva = WriteFatHeaderAndCode(bb, localVarSigTok, initLocals); + if (m_currExcStackCount >= m_currExcStack.Length) + { + m_currExcStack = EnlargeArray(m_currExcStack); } -#if NETFRAMEWORK + Label endLabel = DefineLabel(0); + __ExceptionInfo exceptionInfo = new __ExceptionInfo(m_length, endLabel); - if (moduleBuilder.symbolWriter != null) + // add the exception to the tracking list + m_exceptions[m_exceptionCount++] = exceptionInfo; + + // Make this exception the current active exception + m_currExcStack[m_currExcStackCount++] = exceptionInfo; + + // Stack depth for "try" starts at zero. + m_curDepth = 0; + + return endLabel; + } + + public void EndExceptionBlock() + { + if (m_currExcStackCount == 0) { - if (sequencePoints.Count != 0) - { - var document = sequencePoints[0].document; - int[] offsets = new int[sequencePoints.Count]; - int[] lines = new int[sequencePoints.Count]; - int[] columns = new int[sequencePoints.Count]; - int[] endLines = new int[sequencePoints.Count]; - int[] endColumns = new int[sequencePoints.Count]; - for (int i = 0; i < sequencePoints.Count; i++) - { - if (sequencePoints[i].document != document) - throw new NotImplementedException(); - - offsets[i] = sequencePoints[i].offset; - lines[i] = sequencePoints[i].startLine; - columns[i] = sequencePoints[i].startColumn; - endLines[i] = sequencePoints[i].endLine; - endColumns[i] = sequencePoints[i].endColumn; - } + throw new NotSupportedException("Not currently in an exception block."); + } - moduleBuilder.symbolWriter.DefineSequencePoints(document, offsets, lines, columns, endLines, endColumns); - } + // Pop the current exception block + __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1]; + m_currExcStack[--m_currExcStackCount] = null!; + + Label endLabel = current.GetEndLabel(); + int state = current.GetCurrentState(); - WriteScope(scope, localVarSigTok); + if (state == __ExceptionInfo.State_Filter || + state == __ExceptionInfo.State_Try) + { + throw new InvalidOperationException("Incorrect code generation for exception block."); } -#endif + if (state == __ExceptionInfo.State_Catch) + { + Emit(OpCodes.Leave, endLabel); + } + else if (state == __ExceptionInfo.State_Finally || state == __ExceptionInfo.State_Fault) + { + Emit(OpCodes.Endfinally); + } + + // Check if we've already set this label. + // The only reason why we might have set this is if we have a finally block. + + Label label = m_labelList![endLabel.GetLabelValue()].m_pos != -1 + ? current.m_finallyEndLabel + : endLabel; + + MarkLabel(label); + + current.Done(m_length); + } + + public void BeginExceptFilterBlock() + { + // Begins an exception filter block. Emits a branch instruction to the end of the current exception block. + + if (m_currExcStackCount == 0) + throw new NotSupportedException("Not currently in an exception block."); + + __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1]; + + Emit(OpCodes.Leave, current.GetEndLabel()); - return rva; + current.MarkFilterAddr(m_length); + + // Stack depth for "filter" starts at one. + m_curDepth = 1; } - void ResolveBranches() + public void BeginCatchBlock(Type? exceptionType) { - foreach (var fixup in labelFixups) + Debug.Assert(ModuleBuilder.IsPseudoToken(m_methodBuilder.ModuleBuilder.GetTypeTokenForMemberRef(exceptionType)) == false); + + // Begins a catch block. Emits a branch instruction to the end of the current exception block. + + if (m_currExcStackCount == 0) { - // is it a switch? - if (fixup.label == -1) - { - code.Position = fixup.offset; - int count = code.GetInt32AtCurrentPosition(); - int offset = fixup.offset + 4 + 4 * count; - code.Position += 4; - for (int i = 0; i < count; i++) - { - int index = code.GetInt32AtCurrentPosition(); - code.Write(labels[index] - offset); - } - } - else + throw new NotSupportedException("Not currently in an exception block."); + } + __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1]; + + if (current.GetCurrentState() == __ExceptionInfo.State_Filter) + { + if (exceptionType != null) { - code.Position = fixup.offset; - byte size = code.GetByteAtCurrentPosition(); - int branchOffset = labels[fixup.label] - (code.Position + size); - if (size == 1) - WriteByteBranchOffset(branchOffset); - else - code.Write(branchOffset); + throw new ArgumentException("Should not specify exception type for catch clause for filter block."); } + + Emit(OpCodes.Endfilter); + } + else + { + // execute this branch if previous clause is Catch or Fault + if (exceptionType is null) + throw new ArgumentNullException(nameof(exceptionType)); + + Emit(OpCodes.Leave, current.GetEndLabel()); } + + current.MarkCatchAddr(m_length, exceptionType); + + // Stack depth for "catch" starts at one. + m_curDepth = 1; } - internal static void WriteTinyHeader(ByteBuffer bb, int length) + public void BeginFaultBlock() { - const byte CorILMethod_TinyFormat = 0x2; - bb.Write((byte)(CorILMethod_TinyFormat | (length << 2))); + if (m_currExcStackCount == 0) + { + throw new NotSupportedException("Not currently in an exception block."); + } + __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1]; + + // emit the leave for the clause before this one. + Emit(OpCodes.Leave, current.GetEndLabel()); + + current.MarkFaultAddr(m_length); + + // Stack depth for "fault" starts at zero. + m_curDepth = 0; } - int WriteTinyHeaderAndCode(ByteBuffer bb) + public void BeginFinallyBlock() { - int rva = bb.Position; - WriteTinyHeader(bb, code.Length); - AddTokenFixups(bb.Position, moduleBuilder.tokenFixupOffsets, tokenFixups); - bb.Write(code); - return rva; + if (m_currExcStackCount == 0) + { + throw new NotSupportedException("Not currently in an exception block."); + } + __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1]; + int state = current.GetCurrentState(); + Label endLabel = current.GetEndLabel(); + int catchEndAddr = 0; + if (state != __ExceptionInfo.State_Try) + { + // generate leave for any preceding catch clause + Emit(OpCodes.Leave, endLabel); + catchEndAddr = m_length; + } + + MarkLabel(endLabel); + + Label finallyEndLabel = DefineLabel(0); + current.SetFinallyEndLabel(finallyEndLabel); + + // generate leave for try clause + Emit(OpCodes.Leave, finallyEndLabel); + if (catchEndAddr == 0) + catchEndAddr = m_length; + current.MarkFinallyAddr(m_length, catchEndAddr); + + // Stack depth for "finally" starts at zero. + m_curDepth = 0; } - internal static void WriteFatHeader(ByteBuffer bb, bool initLocals, bool exceptions, ushort maxStack, int codeLength, int localVarSigTok) + #endregion + + #region Labels + public Label DefineLabel() { - const byte CorILMethod_FatFormat = 0x03; - const byte CorILMethod_MoreSects = 0x08; - const byte CorILMethod_InitLocals = 0x10; + // We don't know the stack depth at the label yet, so set it to -1. + return DefineLabel(-1); + } - var flagsAndSize = (short)(CorILMethod_FatFormat | (3 << 12)); - if (initLocals) - flagsAndSize |= CorILMethod_InitLocals; + private Label DefineLabel(int depth) + { + // Declares a new Label. This is just a token and does not yet represent any particular location + // within the stream. In order to set the position of the label within the stream, you must call + // Mark Label. + Debug.Assert(depth >= -1); - if (exceptions) - flagsAndSize |= CorILMethod_MoreSects; + // Delay init the label array in case we dont use it + m_labelList ??= new __LabelInfo[DefaultLabelArraySize]; - bb.Write(flagsAndSize); - bb.Write(maxStack); - bb.Write(codeLength); - bb.Write(localVarSigTok); + if (m_labelCount >= m_labelList.Length) + { + m_labelList = EnlargeArray(m_labelList); + } + m_labelList[m_labelCount].m_pos = -1; + m_labelList[m_labelCount].m_depth = depth; + return new Label(m_labelCount++); } - int WriteFatHeaderAndCode(ByteBuffer bb, int localVarSigTok, bool initLocals) + public void MarkLabel(Label loc) { - // fat headers require 4-byte alignment - bb.Align(4); - int rva = bb.Position; - WriteFatHeader(bb, initLocals, exceptions.Count > 0, maxStack, code.Length, localVarSigTok); - AddTokenFixups(bb.Position, moduleBuilder.tokenFixupOffsets, tokenFixups); - bb.Write(code); - if (exceptions.Count > 0) + // Defines a label by setting the position where that label is found within the stream. + // Does not allow a label to be defined more than once. + + int labelIndex = loc.Id; + + // This should only happen if a label from another generator is used with this one. + if (m_labelList is null || labelIndex < 0 || labelIndex >= m_labelList.Length) + { + throw new ArgumentException("Invalid Label."); + } + + if (m_labelList[labelIndex].m_pos != -1) { - exceptions.Sort(exceptions[0]); - WriteExceptionHandlers(bb, exceptions); + throw new ArgumentException("Label defined multiple times."); } - return rva; + m_labelList[labelIndex].m_pos = m_length; + + int depth = m_labelList[labelIndex].m_depth; + if (depth < 0) + { + // Unknown depth for this label, indicating that it hasn't been used yet. + // If m_curDepth is unknown, we're in the Backward branch constraint case. See ECMA-335 III.1.7.5. + // The m_depthAdjustment field will compensate for violations of this constraint, as we + // discover them. That is, here we assume a depth of zero. If a (later) branch to this label + // has a positive stack depth, we'll record that as the new depth and add the delta into + // m_depthAdjustment. + if (m_curDepth < 0) + m_curDepth = 0; + m_labelList[labelIndex].m_depth = m_curDepth; + } + else if (depth < m_curDepth) + { + // A branch location with smaller stack targets this label. In this case, the IL is + // invalid, but we just compensate for it. + m_depthAdjustment += m_curDepth - depth; + m_labelList[labelIndex].m_depth = m_curDepth; + } + else if (depth > m_curDepth) + { + // Either the current depth is unknown, or a branch location with larger stack targets + // this label, so the IL is invalid. In either case, just adjust the current depth. + m_curDepth = depth; + } } - internal static void WriteExceptionHandlers(ByteBuffer bb, List exceptions) + #endregion + + #region Debug API + + public LocalBuilder DeclareLocal(Type localType) + { + return DeclareLocal(localType, false); + } + + public LocalBuilder DeclareLocal(Type localType, bool pinned) { - bb.Align(4); + // Declare a local of type "local". The current active lexical scope + // will be the scope that local will live. - bool fat = false; - if (exceptions.Count * 12 + 4 > 255) + if (m_methodBuilder is not MethodBuilder methodBuilder) + throw new NotSupportedException(); + + if (methodBuilder.IsTypeCreated()) { - fat = true; + // cannot change method after its containing type has been created + throw new InvalidOperationException("Unable to change after type has been created."); + } + + if (localType is null) + throw new ArgumentNullException(nameof(localType)); + + if (methodBuilder.IsBaked) + { + throw new InvalidOperationException("Type definition of the method is complete."); + } + + // add the localType to local signature + m_localSignature.AddArgument(localType, pinned); + + return new LocalBuilder(m_methodBuilder, localType, m_localCount++, pinned); + } + + public void UsingNamespace(string usingNamespace) + { + // Specifying the namespace to be used in evaluating locals and watches + // for the current active lexical scope. + + if (string.IsNullOrEmpty(usingNamespace)) + throw new ArgumentException(nameof(usingNamespace)); + + if (m_methodBuilder is not MethodBuilder methodBuilder) + throw new NotSupportedException(); + + int index = ((ILGenerator)methodBuilder.GetILGenerator()).m_ScopeTree.GetCurrentActiveScopeIndex(); + if (index == -1) + { + methodBuilder.m_localSymInfo ??= new(); + methodBuilder.m_localSymInfo!.AddUsingNamespace(usingNamespace); } else { - foreach (var block in exceptions) - { - if (block.tryOffset > 65535 || block.tryLength > 255 || block.handlerOffset > 65535 || block.handlerLength > 255) - { - fat = true; - break; - } - } + m_ScopeTree.AddUsingNamespaceToCurrentScope(usingNamespace); } + } - const byte CorILMethod_Sect_EHTable = 0x1; - const byte CorILMethod_Sect_FatFormat = 0x40; + public void BeginScope() + { + m_ScopeTree.AddScopeInfo(ScopeAction.Open, m_length); + } + + public void EndScope() + { + m_ScopeTree.AddScopeInfo(ScopeAction.Close, m_length); + } + + public virtual void MarkSequencePoint( + ISymbolDocumentWriter document, + int startLine, // line number is 1 based + int startColumn, // column is 0 based + int endLine, // line number is 1 based + int endColumn) // column is 0 based + { + if (startLine == 0 || startLine < 0 || endLine == 0 || endLine < 0) + { + throw new ArgumentOutOfRangeException("startLine"); + } + Contract.EndContractBlock(); + m_LineNumberInfo.AddLineNumberInfo(document, m_length, startLine, startColumn, endLine, endColumn); + } + + public int ILOffset => m_length; + + public void Emit(OpCode opcode, sbyte arg) => Emit(opcode, (byte)arg); + + #endregion + + #endregion + } + + internal struct __LabelInfo + { + internal int m_pos; // Position in the il stream, with -1 meaning unknown. + internal int m_depth; // Stack depth, with -1 meaning unknown. + } - if (fat) + internal struct __FixupData + { + internal Label m_fixupLabel; + internal int m_fixupPos; + + internal int m_fixupInstSize; + } + + internal sealed class __ExceptionInfo + { + internal const int None = 0x0000; // COR_ILEXCEPTION_CLAUSE_NONE + internal const int Filter = 0x0001; // COR_ILEXCEPTION_CLAUSE_FILTER + internal const int Finally = 0x0002; // COR_ILEXCEPTION_CLAUSE_FINALLY + internal const int Fault = 0x0004; // COR_ILEXCEPTION_CLAUSE_FAULT + internal const int PreserveStack = 0x0004; // COR_ILEXCEPTION_CLAUSE_PRESERVESTACK + + internal const int State_Try = 0; + internal const int State_Filter = 1; + internal const int State_Catch = 2; + internal const int State_Finally = 3; + internal const int State_Fault = 4; + internal const int State_Done = 5; + + internal int m_startAddr; + internal int[] m_filterAddr; + internal int[] m_catchAddr; + internal int[] m_catchEndAddr; + internal int[] m_type; + internal Type[] m_catchClass; + internal Label m_endLabel; + internal Label m_finallyEndLabel; + internal int m_endAddr; + internal int m_endFinally; + internal int m_currentCatch; + + private int m_currentState; + + internal __ExceptionInfo(int startAddr, Label endLabel) + { + m_startAddr = startAddr; + m_endAddr = -1; + m_filterAddr = new int[4]; + m_catchAddr = new int[4]; + m_catchEndAddr = new int[4]; + m_catchClass = new Type[4]; + m_currentCatch = 0; + m_endLabel = endLabel; + m_type = new int[4]; + m_endFinally = -1; + m_currentState = State_Try; + } + + private void MarkHelper( + int catchorfilterAddr, // the starting address of a clause + int catchEndAddr, // the end address of a previous catch clause. Only use when finally is following a catch + Type? catchClass, // catch exception type + int type) // kind of clause + { + int currentCatch = m_currentCatch; + if (currentCatch >= m_catchAddr.Length) { - bb.Write((byte)(CorILMethod_Sect_EHTable | CorILMethod_Sect_FatFormat)); - int dataSize = exceptions.Count * 24 + 4; - bb.Write((byte)dataSize); - bb.Write((short)(dataSize >> 8)); - foreach (ExceptionBlock block in exceptions) + m_filterAddr = ILGenerator.EnlargeArray(m_filterAddr); + m_catchAddr = ILGenerator.EnlargeArray(m_catchAddr); + m_catchEndAddr = ILGenerator.EnlargeArray(m_catchEndAddr); + m_catchClass = ILGenerator.EnlargeArray(m_catchClass); + m_type = ILGenerator.EnlargeArray(m_type); + } + if (type == Filter) + { + m_type[currentCatch] = type; + m_filterAddr[currentCatch] = catchorfilterAddr; + m_catchAddr[currentCatch] = -1; + if (currentCatch > 0) { - bb.Write((int)block.kind); - bb.Write(block.tryOffset); - bb.Write(block.tryLength); - bb.Write(block.handlerOffset); - bb.Write(block.handlerLength); - bb.Write(block.filterOffsetOrExceptionTypeToken); + Debug.Assert(m_catchEndAddr[currentCatch - 1] == -1, "m_catchEndAddr[m_currentCatch-1] == -1"); + m_catchEndAddr[currentCatch - 1] = catchorfilterAddr; } } else { - bb.Write(CorILMethod_Sect_EHTable); - bb.Write((byte)(exceptions.Count * 12 + 4)); - bb.Write((short)0); - foreach (ExceptionBlock block in exceptions) + // catch or Fault clause + m_catchClass[currentCatch] = catchClass!; + if (m_type[currentCatch] != Filter) { - bb.Write((short)block.kind); - bb.Write((short)block.tryOffset); - bb.Write((byte)block.tryLength); - bb.Write((short)block.handlerOffset); - bb.Write((byte)block.handlerLength); - bb.Write(block.filterOffsetOrExceptionTypeToken); + m_type[currentCatch] = type; + } + m_catchAddr[currentCatch] = catchorfilterAddr; + if (currentCatch > 0) + { + if (m_type[currentCatch] != Filter) + { + Debug.Assert(m_catchEndAddr[currentCatch - 1] == -1, "m_catchEndAddr[m_currentCatch-1] == -1"); + m_catchEndAddr[currentCatch - 1] = catchEndAddr; + } } + m_catchEndAddr[currentCatch] = -1; + m_currentCatch++; + } + + if (m_endAddr == -1) + { + m_endAddr = catchorfilterAddr; } } - internal static void AddTokenFixups(int codeOffset, List tokenFixupOffsets, IEnumerable tokenFixups) + internal void MarkFilterAddr(int filterAddr) { - foreach (int fixup in tokenFixups) - tokenFixupOffsets.Add(fixup + codeOffset); + m_currentState = State_Filter; + MarkHelper(filterAddr, filterAddr, null, Filter); } - void WriteScope(Scope scope, int localVarSigTok) + internal void MarkFaultAddr(int faultAddr) { -#if NETFRAMEWORK - moduleBuilder.symbolWriter.OpenScope(scope.startOffset); + m_currentState = State_Fault; + MarkHelper(faultAddr, faultAddr, null, Fault); + } - foreach (var local in scope.locals) + internal void MarkCatchAddr(int catchAddr, Type? catchException) + { + m_currentState = State_Catch; + MarkHelper(catchAddr, catchAddr, catchException, None); + } + + internal void MarkFinallyAddr(int finallyAddr, int endCatchAddr) + { + if (m_endFinally != -1) { - if (local.name != null) + throw new ArgumentException("Exception blocks may have at most one finally clause."); + } + + m_currentState = State_Finally; + m_endFinally = finallyAddr; + MarkHelper(finallyAddr, endCatchAddr, null, Finally); + } + + internal void Done(int endAddr) + { + Debug.Assert(m_currentCatch > 0, "m_currentCatch > 0"); + Debug.Assert(m_catchAddr[m_currentCatch - 1] > 0, "m_catchAddr[m_currentCatch-1] > 0"); + Debug.Assert(m_catchEndAddr[m_currentCatch - 1] == -1, "m_catchEndAddr[m_currentCatch-1] == -1"); + m_catchEndAddr[m_currentCatch - 1] = endAddr; + m_currentState = State_Done; + } + + internal int GetStartAddress() + { + return m_startAddr; + } + + internal int GetEndAddress() + { + return m_endAddr; + } + + internal int GetFinallyEndAddress() + { + return m_endFinally; + } + + internal Label GetEndLabel() + { + return m_endLabel; + } + + internal int[] GetFilterAddresses() + { + return m_filterAddr; + } + + internal int[] GetCatchAddresses() + { + return m_catchAddr; + } + + internal int[] GetCatchEndAddresses() + { + return m_catchEndAddr; + } + + internal Type[] GetCatchClass() + { + return m_catchClass; + } + + internal int GetNumberOfCatches() + { + return m_currentCatch; + } + + internal int[] GetExceptionTypes() + { + return m_type; + } + + internal void SetFinallyEndLabel(Label lbl) + { + m_finallyEndLabel = lbl; + } + + internal Label GetFinallyEndLabel() + { + return m_finallyEndLabel; + } + + // Specifies whether exc is an inner exception for "this". The way + // its determined is by comparing the end address for the last catch + // clause for both exceptions. If they're the same, the start address + // for the exception is compared. + // WARNING: This is not a generic function to determine the innerness + // of an exception. This is somewhat of a mis-nomer. This gives a + // random result for cases where the two exceptions being compared do + // not having a nesting relation. + internal bool IsInner(__ExceptionInfo exc) + { + Debug.Assert(exc != null); + Debug.Assert(m_currentCatch > 0, "m_currentCatch > 0"); + Debug.Assert(exc.m_currentCatch > 0, "exc.m_currentCatch > 0"); + + int exclast = exc.m_currentCatch - 1; + int last = m_currentCatch - 1; + + if (exc.m_catchEndAddr[exclast] < m_catchEndAddr[last]) + return true; + + if (exc.m_catchEndAddr[exclast] != m_catchEndAddr[last]) + return false; + Debug.Assert(exc.GetEndAddress() != GetEndAddress(), + "exc.GetEndAddress() != GetEndAddress()"); + + return exc.GetEndAddress() > GetEndAddress(); + } + + // 0 indicates in a try block + // 1 indicates in a filter block + // 2 indicates in a catch block + // 3 indicates in a finally block + // 4 indicates Done + internal int GetCurrentState() + { + return m_currentState; + } + } + + /// + /// Scope Tree is a class that track the scope structure within a method body + /// It keeps track two parallel array. m_ScopeAction keeps track the action. It can be + /// OpenScope or CloseScope. m_iOffset records the offset where the action + /// takes place. + /// + internal enum ScopeAction : sbyte + { + Open = -0x1, + Close = 0x1 + } + + internal sealed class ScopeTree + { + internal ScopeTree() + { + // initialize data variables + m_iOpenScopeCount = 0; + m_iCount = 0; + } + + /// + /// Find the current active lexical scope. For example, if we have + /// "Open Open Open Close", + /// we will return 1 as the second BeginScope is currently active. + /// + internal int GetCurrentActiveScopeIndex() + { + if (m_iCount == 0) + { + return -1; + } + + int i = m_iCount - 1; + + for (int cClose = 0; cClose > 0 || m_ScopeActions[i] == ScopeAction.Close; i--) + { + cClose += (sbyte)m_ScopeActions[i]; + } + + return i; + } + + internal void AddLocalSymInfoToCurrentScope( + string strName, + byte[] signature, + int slot, + int startOffset, + int endOffset) + { + int i = GetCurrentActiveScopeIndex(); + m_localSymInfos[i] ??= new LocalSymInfo(); + m_localSymInfos[i]!.AddLocalSymInfo(strName, signature, slot, startOffset, endOffset); + } + + internal void AddUsingNamespaceToCurrentScope(string strNamespace) + { + int i = GetCurrentActiveScopeIndex(); + m_localSymInfos[i] ??= new LocalSymInfo(); + m_localSymInfos[i]!.AddUsingNamespace(strNamespace); + } + + internal void AddScopeInfo(ScopeAction sa, int iOffset) + { + if (sa == ScopeAction.Close && m_iOpenScopeCount <= 0) + { + throw new ArgumentException("Non-matching symbol scope."); + } + + // make sure that arrays are large enough to hold addition info + EnsureCapacity(); + + m_ScopeActions[m_iCount] = sa; + m_iOffsets[m_iCount] = iOffset; + m_localSymInfos[m_iCount] = null; + checked { m_iCount++; } + + m_iOpenScopeCount += -(sbyte)sa; + } + + /// + /// Helper to ensure arrays are large enough + /// + internal void EnsureCapacity() + { + if (m_iCount == 0) + { + // First time. Allocate the arrays. + m_iOffsets = new int[InitialSize]; + m_ScopeActions = new ScopeAction[InitialSize]; + m_localSymInfos = new LocalSymInfo[InitialSize]; + } + else if (m_iCount == m_iOffsets.Length) + { + // the arrays are full. Enlarge the arrays + // It would probably be simpler to just use Lists here. + int newSize = checked(m_iCount * 2); + int[] temp = new int[newSize]; + Array.Copy(m_iOffsets, temp, m_iCount); + m_iOffsets = temp; + + ScopeAction[] tempSA = new ScopeAction[newSize]; + Array.Copy(m_ScopeActions, tempSA, m_iCount); + m_ScopeActions = tempSA; + + LocalSymInfo[] tempLSI = new LocalSymInfo[newSize]; + Array.Copy(m_localSymInfos, tempLSI, m_iCount); + m_localSymInfos = tempLSI; + } + } + internal void EmitScopeTree(ISymbolWriter symWriter) + { + int i; + for (i = 0; i < m_iCount; i++) + { + if (m_ScopeActions != null && m_ScopeActions[i] == ScopeAction.Open) { - int startOffset = local.startOffset; - int endOffset = local.endOffset; - if (startOffset == 0 && endOffset == 0) - { - startOffset = scope.startOffset; - endOffset = scope.endOffset; - } + symWriter.OpenScope(m_iOffsets[i]); + } + else + { + symWriter.CloseScope(m_iOffsets[i]); + } + if (m_localSymInfos != null && m_localSymInfos[i] != null) + { + m_localSymInfos[i]!.EmitLocalSymInfo(symWriter); + } + } + } + + internal int[] m_iOffsets = null!; // array of offsets + internal ScopeAction[] m_ScopeActions = null!; // array of scope actions + internal int m_iCount; // how many entries in the arrays are occupied + internal int m_iOpenScopeCount; // keep track how many scopes are open + internal const int InitialSize = 16; + internal LocalSymInfo?[] m_localSymInfos = null!; // keep track debugging local information + } + + + /*************************** + * + * This class tracks the line number info + * + ***************************/ + internal sealed class LineNumberInfo + { + internal LineNumberInfo() + { + // initialize data variables + m_DocumentCount = 0; + m_iLastFound = 0; + } + + internal void AddLineNumberInfo( + ISymbolDocumentWriter document, + int iOffset, + int iStartLine, + int iStartColumn, + int iEndLine, + int iEndColumn) + { + int i; + + // make sure that arrays are large enough to hold addition info + i = FindDocument(document); + + Contract.Assert(i < m_DocumentCount, "Bad document look up!"); + m_Documents[i].AddLineNumberInfo(document, iOffset, iStartLine, iStartColumn, iEndLine, iEndColumn); + } + + // Find a REDocument representing document. If we cannot find one, we will add a new entry into + // the REDocument array. + private int FindDocument(ISymbolDocumentWriter document) + { + int i; + + // This is an optimization. The chance that the previous line is coming from the same + // document is very high. + if (m_iLastFound < m_DocumentCount && m_Documents[m_iLastFound].m_document == document) + return m_iLastFound; - moduleBuilder.symbolWriter.DefineLocalVariable2(local.name, 0, localVarSigTok, SymAddressKind.ILOffset, local.LocalIndex, 0, 0, startOffset, endOffset); + for (i = 0; i < m_DocumentCount; i++) + { + if (m_Documents[i].m_document == document) + { + m_iLastFound = i; + return m_iLastFound; } } - foreach (string ns in scope.namespaces) - moduleBuilder.symbolWriter.UsingNamespace(ns); + // cannot find an existing document so add one to the array + EnsureCapacity(); + m_iLastFound = m_DocumentCount; + m_Documents[m_iLastFound] = new REDocument(document); + checked { m_DocumentCount++; } + return m_iLastFound; + } - foreach (var child in scope.children) - WriteScope(child, localVarSigTok); + /************************** + * + * Helper to ensure arrays are large enough + * + **************************/ + private void EnsureCapacity() + { + if (m_DocumentCount == 0) + { + // First time. Allocate the arrays. + m_Documents = new REDocument[InitialSize]; + } + else if (m_DocumentCount == m_Documents.Length) + { + // the arrays are full. Enlarge the arrays + REDocument[] temp = new REDocument[m_DocumentCount * 2]; + Array.Copy(m_Documents, temp, m_DocumentCount); + m_Documents = temp; + } + } - moduleBuilder.symbolWriter.CloseScope(scope.endOffset); -#else - throw new NotSupportedException(); -#endif + internal void EmitLineNumberInfo(ISymbolWriter symWriter) + { + for (int i = 0; i < m_DocumentCount; i++) + m_Documents[i].EmitLineNumberInfo(symWriter); } + private int m_DocumentCount; // how many documents that we have right now + private REDocument[] m_Documents; // array of documents + private const int InitialSize = 16; + private int m_iLastFound; } + + /*************************** + * + * This class tracks the line number info + * + ***************************/ + internal sealed class REDocument + { + internal REDocument(ISymbolDocumentWriter document) + { + // initialize data variables + m_iLineNumberCount = 0; + m_document = document; + } + + internal void AddLineNumberInfo( + ISymbolDocumentWriter document, + int iOffset, + int iStartLine, + int iStartColumn, + int iEndLine, + int iEndColumn) + { + Contract.Assert(document == m_document, "Bad document look up!"); + + // make sure that arrays are large enough to hold addition info + EnsureCapacity(); + + m_iOffsets[m_iLineNumberCount] = iOffset; + m_iLines[m_iLineNumberCount] = iStartLine; + m_iColumns[m_iLineNumberCount] = iStartColumn; + m_iEndLines[m_iLineNumberCount] = iEndLine; + m_iEndColumns[m_iLineNumberCount] = iEndColumn; + checked { m_iLineNumberCount++; } + } + + /************************** + * + * Helper to ensure arrays are large enough + * + **************************/ + private void EnsureCapacity() + { + if (m_iLineNumberCount == 0) + { + // First time. Allocate the arrays. + m_iOffsets = new int[InitialSize]; + m_iLines = new int[InitialSize]; + m_iColumns = new int[InitialSize]; + m_iEndLines = new int[InitialSize]; + m_iEndColumns = new int[InitialSize]; + } + else if (m_iLineNumberCount == m_iOffsets.Length) + { + // the arrays are full. Enlarge the arrays + // It would probably be simpler to just use Lists here + int newSize = checked(m_iLineNumberCount * 2); + int[] temp = new int[newSize]; + Array.Copy(m_iOffsets, temp, m_iLineNumberCount); + m_iOffsets = temp; + + temp = new int[newSize]; + Array.Copy(m_iLines, temp, m_iLineNumberCount); + m_iLines = temp; + + temp = new int[newSize]; + Array.Copy(m_iColumns, temp, m_iLineNumberCount); + m_iColumns = temp; + + temp = new int[newSize]; + Array.Copy(m_iEndLines, temp, m_iLineNumberCount); + m_iEndLines = temp; + + temp = new int[newSize]; + Array.Copy(m_iEndColumns, temp, m_iLineNumberCount); + m_iEndColumns = temp; + } + } + + internal void EmitLineNumberInfo(ISymbolWriter symWriter) + { + int[] iOffsetsTemp; + int[] iLinesTemp; + int[] iColumnsTemp; + int[] iEndLinesTemp; + int[] iEndColumnsTemp; + + if (m_iLineNumberCount == 0) + return; + // reduce the array size to be exact + iOffsetsTemp = new int[m_iLineNumberCount]; + Array.Copy(m_iOffsets, iOffsetsTemp, m_iLineNumberCount); + + iLinesTemp = new int[m_iLineNumberCount]; + Array.Copy(m_iLines, iLinesTemp, m_iLineNumberCount); + + iColumnsTemp = new int[m_iLineNumberCount]; + Array.Copy(m_iColumns, iColumnsTemp, m_iLineNumberCount); + + iEndLinesTemp = new int[m_iLineNumberCount]; + Array.Copy(m_iEndLines, iEndLinesTemp, m_iLineNumberCount); + + iEndColumnsTemp = new int[m_iLineNumberCount]; + Array.Copy(m_iEndColumns, iEndColumnsTemp, m_iLineNumberCount); + + symWriter.DefineSequencePoints(m_document, iOffsetsTemp, iLinesTemp, iColumnsTemp, iEndLinesTemp, iEndColumnsTemp); + } + + private int[] m_iOffsets; // array of offsets + private int[] m_iLines; // array of offsets + private int[] m_iColumns; // array of offsets + private int[] m_iEndLines; // array of offsets + private int[] m_iEndColumns; // array of offsets + internal ISymbolDocumentWriter m_document; // The ISymbolDocumentWriter that this REDocument is tracking. + private int m_iLineNumberCount; // how many entries in the arrays are occupied + private const int InitialSize = 16; + } // end of REDocument + } + +#nullable restore diff --git a/src/IKVM.Reflection/Emit/ImportsEncoder.cs b/src/IKVM.Reflection/Emit/ImportsEncoder.cs new file mode 100644 index 0000000000..6aaa6ad00a --- /dev/null +++ b/src/IKVM.Reflection/Emit/ImportsEncoder.cs @@ -0,0 +1,93 @@ +using System; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace IKVM.Reflection.Emit +{ + + struct ImportsEncoder + { + + readonly BlobBuilder writer; + int count; + + /// + /// Initializes a new instance. + /// + /// + /// + public ImportsEncoder(BlobBuilder writer) + { + this.writer = writer ?? throw new ArgumentNullException(nameof(writer)); + } + + /// + /// Gets the number of imports written. + /// + public int Count => count; + + public void AliasAssemblyReference(BlobHandle alias, AssemblyReferenceHandle assembly) + { + // ::= AliasAssemblyReference + writer.WriteByte((byte)ImportDefinitionKind.AliasAssemblyReference); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(alias)); + writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(assembly)); + } + + public void AliasType(BlobHandle alias, EntityHandle targetType) + { + // ::= AliasType + writer.WriteByte((byte)ImportDefinitionKind.AliasType); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(alias)); + writer.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(targetType)); + } + + public void ImportType(EntityHandle targetType) + { + // ::= AliasType + writer.WriteByte((byte)ImportDefinitionKind.ImportType); + writer.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(targetType)); + } + + public void AliasAssemblyNamespace(BlobHandle alias, AssemblyReferenceHandle targetAssembly, BlobHandle namespaceName) + { + // ::= AliasAssemblyNamespace + writer.WriteByte((byte)ImportDefinitionKind.AliasAssemblyNamespace); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(alias)); + writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(targetAssembly)); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(namespaceName)); + } + + public void ImportAssemblyNamespace(AssemblyReferenceHandle targetAssembly, BlobHandle namespaceName) + { + // ::= ImportAssemblyNamespace + writer.WriteByte((byte)ImportDefinitionKind.ImportAssemblyNamespace); + writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(targetAssembly)); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(namespaceName)); + } + + public void AliasNamespace(BlobHandle alias, BlobHandle namespaceName) + { + // ::= AliasNamespace + writer.WriteByte((byte)ImportDefinitionKind.AliasNamespace); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(alias)); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(namespaceName)); + } + + public void ImportNamespace(BlobHandle namespaceName) + { + // ::= ImportNamespace + writer.WriteByte((byte)ImportDefinitionKind.ImportNamespace); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(namespaceName)); + } + + public void ImportReferenceAlias(BlobHandle alias) + { + // ::= ImportReferenceAlias + writer.WriteByte((byte)ImportDefinitionKind.ImportAssemblyReferenceAlias); + writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(alias)); + } + + } + +} diff --git a/src/IKVM.Reflection/Emit/Label.cs b/src/IKVM.Reflection/Emit/Label.cs index 731f984b0b..212689817b 100644 --- a/src/IKVM.Reflection/Emit/Label.cs +++ b/src/IKVM.Reflection/Emit/Label.cs @@ -1,73 +1,45 @@ -/* - Copyright (C) 2008-2012 Jeroen Frijters +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. +#nullable enable - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: +using System; +using System.Diagnostics.CodeAnalysis; - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jeroen Frijters - jeroen@frijters.net - -*/ namespace IKVM.Reflection.Emit { - - public readonly struct Label + /// + /// Represents a label in the instruction stream. Used in conjunction with the class. + /// + /// + /// The Label class is an opaque representation of a label used by the + /// class. The token is used to mark where labels occur in the IL + /// stream. Labels are created by using and their position is set + /// by using . + /// + public readonly struct Label : IEquatable