diff --git a/src/IKVM.CoreLib.Tests/Collections/WeakHashTableTests.cs b/src/IKVM.CoreLib.Tests/Collections/WeakHashTableTests.cs new file mode 100644 index 0000000000..8b18a491d3 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Collections/WeakHashTableTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; + +using FluentAssertions; + +using IKVM.CoreLib.Collections; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Collections +{ + + [TestClass] + public class WeakHashTableTests + { + + class ArrayComparer : EqualityComparer + { + + public override bool Equals(object[]? x, object[]? y) + { + return Enumerable.SequenceEqual(x ?? [], y ?? []); + } + + public override int GetHashCode([DisallowNull] object[] obj) + { + var hc = new HashCode(); + foreach (var i in obj) + hc.Add(i); + + return hc.ToHashCode(); + } + + } + + [TestMethod] + public void CanGetOrCreateValueAtPath() + { + var a = new object(); + var b = new object(); + var c = new object(); + var r = new object(); + + var t = new WeakHashTable(new ArrayComparer()); + var v1 = t.GetOrCreateValue([a, b, c], k => r); + v1.Should().BeSameAs(r); + var v2 = t.GetOrCreateValue([a, b, c], k => new object()); + v2.Should().BeSameAs(r); + } + + [TestMethod] + public void CachedValueDoesNotExpireWhenReferenced() + { + var t = new WeakHashTable(new ArrayComparer()); + + var a = new object(); + var b = new object(); + var c = new object(); + var r = new object(); + + void Test(WeakHashTable t) + { + var v1 = t.GetOrCreateValue([a, b, c], k => r); + v1.Should().BeSameAs(r); + t.Should().HaveCount(1); + } + + Test(t); + + GC.Collect(); + GC.Collect(); + GC.Collect(); + Thread.Sleep(10); + + t.Should().HaveCount(1); + GC.KeepAlive(r); + } + + [TestMethod] + public void CachedValueExpiresWhenUnreferenced() + { + var t = new WeakHashTable(new ArrayComparer()); + + var a = new object(); + var b = new object(); + var c = new object(); + + void Test(WeakHashTable t) + { + var r = new object(); + + var v1 = t.GetOrCreateValue([a, b, c], k => r); + v1.Should().BeSameAs(r); + t.Should().HaveCount(1); + } + + Test(t); + + GC.Collect(); + GC.Collect(); + GC.Collect(); + Thread.Sleep(10); + + t.Should().HaveCount(0); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Runtime/DependentHandleTests.cs b/src/IKVM.CoreLib.Tests/Runtime/DependentHandleTests.cs new file mode 100644 index 0000000000..d1ac2b436f --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Runtime/DependentHandleTests.cs @@ -0,0 +1,63 @@ +using FluentAssertions; + +using IKVM.CoreLib.Runtime; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Runtime +{ + + [TestClass] + public class DependentHandleTests + { + + [TestMethod] + public void CanCreate() + { + var a = new object(); + var b = new object(); + var dh = new DependentHandle(a, b); + dh.IsAllocated.Should().BeTrue(); + } + + [TestMethod] + public void CanGetTarget() + { + var a = new object(); + var b = new object(); + var dh = new DependentHandle(a, b); + dh.Target.Should().BeSameAs(a); + } + + [TestMethod] + public void CanGetDependent() + { + var a = new object(); + var b = new object(); + var dh = new DependentHandle(a, b); + dh.Dependent.Should().BeSameAs(b); + } + + [TestMethod] + public void CanGetTargetAndDependent() + { + var a = new object(); + var b = new object(); + var dh = new DependentHandle(a, b); + var td = dh.TargetAndDependent; + td.Target.Should().BeSameAs(a); + td.Dependent.Should().BeSameAs(b); + } + + [TestMethod] + public void CanDispose() + { + var a = new object(); + var b = new object(); + var dh = new DependentHandle(a, b); + dh.Dispose(); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/AssemblySymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/AssemblySymbolTests.cs new file mode 100644 index 0000000000..75f223a39b --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/AssemblySymbolTests.cs @@ -0,0 +1,25 @@ +using FluentAssertions; + +using IKVM.CoreLib.Symbols; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols +{ + + public abstract class AssemblySymbolTests + where TInit : SymbolTestInit, new() + where TSymbols : SymbolContext + { + + protected TInit Init { get; } = new TInit(); + + [TestMethod] + public void SystemObjectShouldNotBeNull() + { + Init.Symbols.ResolveCoreType("System.Object").Should().NotBeNull(); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Emit/AssemblySymbolBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Emit/AssemblySymbolBuilderTests.cs new file mode 100644 index 0000000000..f94313b831 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Emit/AssemblySymbolBuilderTests.cs @@ -0,0 +1,39 @@ +using System; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Emit +{ + + public abstract class AssemblySymbolBuilderTests + where TInit : SymbolTestInit, new() + where TSymbols: SymbolContext + { + + protected TInit Init { get; } = new TInit(); + + [TestMethod] + public void ThrowsOnFreeze() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + a.Freeze(); + a.Invoking(_ => _.DefineModule("Test.dll", "Test.dll")).Should().ThrowExactly(); + } + + [TestMethod] + public void CanDefineModule() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + a.FullName.Should().Be("Test, Version=0.0.0.0, PublicKeyToken=null"); + var m = a.DefineModule("Test.dll", "Test.dll"); + m.Name.Should().Be("Test.dll"); + m.ScopeName.Should().Be("Test.dll"); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Emit/ModuleSymbolBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Emit/ModuleSymbolBuilderTests.cs new file mode 100644 index 0000000000..e49d52063b --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Emit/ModuleSymbolBuilderTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Emit +{ + + public abstract class ModuleSymbolBuilderTests + where TInit : SymbolTestInit, new() + where TSymbols: SymbolContext + { + + protected TInit Init { get; } = new TInit(); + + [TestMethod] + public void ThrowsOnFreeze() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = a.DefineModule("Test.dll", "Test.dll"); + m.Freeze(); + m.Invoking(_ => _.DefineType("Namespace.TestType", TypeAttributes.Public)).Should().ThrowExactly(); + } + + [TestMethod] + public void CanDefineGlobalMethod() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = a.DefineModule("Test.dll", "Test.dll"); + var f = m.DefineGlobalMethod("TestMethod", MethodAttributes.Public | MethodAttributes.Static, null, []); + f.Name.Should().Be("TestMethod"); + f.Module.Should().Be(m); + f.Assembly.Should().Be(a); + f.Attributes.Should().HaveFlag(MethodAttributes.Public); + f.Attributes.Should().HaveFlag(MethodAttributes.Static); + var il = f.GetILGenerator(); + il.Emit(OpCodes.Ret); + } + + [TestMethod] + public void CanDefineType() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = a.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType", TypeAttributes.Public); + t.Assembly.Should().Be(a); + t.Module.Should().Be(m); + t.Name.Should().Be("TestType"); + t.FullName.Should().Be("Namespace.TestType"); + t.Attributes.Should().HaveFlag(TypeAttributes.Public); + t.Attributes.Should().HaveFlag(TypeAttributes.Class); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Emit/TypeSymbolBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Emit/TypeSymbolBuilderTests.cs new file mode 100644 index 0000000000..1ea71977bb --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Emit/TypeSymbolBuilderTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Emit +{ + + public abstract class TypeSymbolBuilderTests + where TInit : SymbolTestInit, new() + where TSymbols: SymbolContext + { + + protected TInit Init { get; } = new TInit(); + + [TestMethod] + public void ThrowsOnFreeze() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = a.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Test"); + t.Freeze(); + t.Invoking(_ => _.SetParent(null)).Should().ThrowExactly(); + } + + [TestMethod] + public void CanDefineMethod() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = a.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Test"); + var f = t.DefineMethod("TestMethod", MethodAttributes.Public | MethodAttributes.Static, null, []); + f.Name.Should().Be("TestMethod"); + f.Module.Should().Be(m); + f.Assembly.Should().Be(a); + f.Attributes.Should().HaveFlag(MethodAttributes.Public); + f.Attributes.Should().HaveFlag(MethodAttributes.Static); + var il = f.GetILGenerator(); + il.Emit(OpCodes.Ret); + } + + [TestMethod] + public void CanDefineNestedType() + { + var a = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = a.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType", TypeAttributes.Public); + var n = t.DefineNestedType("NestedType"); + n.Assembly.Should().Be(a); + n.Module.Should().Be(m); + n.Name.Should().Be("NestedType"); + n.FullName.Should().Be("Namespace.TestType+NestedType"); + n.Attributes.Should().HaveFlag(TypeAttributes.Public); + n.Attributes.Should().HaveFlag(TypeAttributes.Class); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionAssemblyBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionAssemblyBuilderTests.cs new file mode 100644 index 0000000000..9b076b4887 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionAssemblyBuilderTests.cs @@ -0,0 +1,75 @@ +using FluentAssertions; + +using IKVM.CoreLib.Symbols; +using IKVM.CoreLib.Symbols.IkvmReflection; +using IKVM.CoreLib.Tests.Symbols.Emit; +using IKVM.Reflection.Emit; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection.Emit +{ + + [TestClass] + public class IkvmReflectionAssemblyBuilderTests : AssemblySymbolBuilderTests + { + + [TestMethod] + public void CanResolveDefined() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + b.FullName.Should().Be("Test, Version=0.0.0.0, PublicKeyToken=null"); + var a = Init.Symbols.ResolveAssembly(b, IkvmReflectionSymbolState.Defined); + a.Should().BeOfType(); + a.FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Name.Should().Be("Test"); + a.GetName().FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Version.Should().Be(new System.Version("0.0.0.0")); + a.GetName().CultureName.Should().Be(""); + a.GetName().GetPublicKeyToken().Should().BeEmpty(); + } + + [TestMethod] + public void CanResolveEmitted() + { + var s = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var a = Init.Symbols.ResolveAssembly(s, IkvmReflectionSymbolState.Emitted); + a.Should().BeOfType(); + a.FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Name.Should().Be("Test"); + a.GetName().FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Version.Should().Be(new System.Version("0.0.0.0")); + a.GetName().CultureName.Should().Be(""); + a.GetName().GetPublicKeyToken().Should().BeEmpty(); + } + + [TestMethod] + public void CanResolveFinished() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + b.FullName.Should().Be("Test, Version=0.0.0.0, PublicKeyToken=null"); + var a = Init.Symbols.ResolveAssembly(b, IkvmReflectionSymbolState.Emitted); + a.Should().BeOfType(); + a.FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Name.Should().Be("Test"); + a.GetName().FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Version.Should().Be(new System.Version("0.0.0.0")); + a.GetName().CultureName.Should().Be(""); + a.GetName().GetPublicKeyToken().Should().BeEmpty(); + } + + [TestMethod] + public void ShouldDeclareModuleOnFinish() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + b.FullName.Should().Be("Test, Version=0.0.0.0, PublicKeyToken=null"); + b.DefineModule("Test.dll", "Test.dll"); + var a = Init.Symbols.ResolveAssembly(b, IkvmReflectionSymbolState.Emitted); + var m = a.GetModule("Test.dll"); + m.Assembly.Should().BeSameAs(a); + m.Name.Should().Be("Test.dll"); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionModuleBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionModuleBuilderTests.cs new file mode 100644 index 0000000000..b0fdb8fe99 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionModuleBuilderTests.cs @@ -0,0 +1,45 @@ +using FluentAssertions; + +using IKVM.CoreLib.Symbols; +using IKVM.CoreLib.Symbols.IkvmReflection; +using IKVM.CoreLib.Tests.Symbols.Emit; +using IKVM.Reflection.Emit; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection.Emit +{ + + [TestClass] + public class IkvmReflectionModuleBuilderTests : ModuleSymbolBuilderTests + { + + [TestMethod] + public void CanResolveDefined() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var z = Init.Symbols.ResolveType((TypeSymbol)t, IkvmReflectionSymbolState.Defined); + z.Should().BeOfType(); + z.Namespace.Should().Be("Namespace"); + z.Name.Should().Be("TestType"); + z.FullName.Should().Be("Namespace.TestType"); + } + + [TestMethod] + public void ShouldReturnTypeBuilderOnEmitted() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var z = Init.Symbols.ResolveType((TypeSymbol)t, IkvmReflectionSymbolState.Emitted); + z.Should().BeOfType(); + z.Namespace.Should().Be("Namespace"); + z.Name.Should().Be("TestType"); + z.FullName.Should().Be("Namespace.TestType"); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionTypeBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionTypeBuilderTests.cs new file mode 100644 index 0000000000..531d95ece7 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/Emit/IkvmReflectionTypeBuilderTests.cs @@ -0,0 +1,123 @@ +using FluentAssertions; + +using IKVM.CoreLib.Symbols; +using IKVM.CoreLib.Symbols.IkvmReflection; +using IKVM.CoreLib.Tests.Symbols.Emit; +using IKVM.Reflection; +using IKVM.Reflection.Emit; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Type = IKVM.Reflection.Type; + +namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection.Emit +{ + + [TestClass] + public class IkvmReflectionTypeBuilderTests : TypeSymbolBuilderTests + { + + [TestMethod] + public void ShouldReturnTypeBuilderOnEmitted() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var z = Init.Symbols.ResolveType((TypeSymbol)t, IkvmReflectionSymbolState.Emitted); + z.Should().BeAssignableTo(); + z.Namespace.Should().Be("Namespace"); + z.Name.Should().Be("TestType"); + z.FullName.Should().Be("Namespace.TestType"); + } + + [TestMethod] + public void ShouldReturnTypeOnFinished() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var z = Init.Symbols.ResolveType((TypeSymbol)t, IkvmReflectionSymbolState.Finished); + z.Should().BeAssignableTo(); + z.Namespace.Should().Be("Namespace"); + z.Name.Should().Be("TestType"); + z.FullName.Should().Be("Namespace.TestType"); + } + + [TestMethod] + public void ShouldReturnMethodBuilderOnDefined() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var j = t.DefineMethod("TestMethod", System.Reflection.MethodAttributes.Public); + var z = Init.Symbols.ResolveMethod((MethodSymbol)j, IkvmReflectionSymbolState.Defined); + z.Should().BeOfType(); + z.Name.Should().Be("TestMethod"); + } + + [TestMethod] + public void ShouldReturnMethodBuilderOnEmitted() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var j = t.DefineMethod("TestMethod", System.Reflection.MethodAttributes.Public); + var z = Init.Symbols.ResolveMethod((MethodSymbol)j, IkvmReflectionSymbolState.Emitted); + z.Should().BeOfType(); + z.Name.Should().Be("TestMethod"); + } + + [TestMethod] + public void ShouldReturnMethodInfoOnFinished() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var j = t.DefineMethod("TestMethod", System.Reflection.MethodAttributes.Public); + var z = Init.Symbols.ResolveMethod((MethodSymbol)j, IkvmReflectionSymbolState.Finished); + z.Should().BeAssignableTo(); + z.Should().NotBeSameAs(j); + z.Name.Should().Be("TestMethod"); + } + + [TestMethod] + public void ShouldDefineFieldsInOrder() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var m1 = t.DefineField("TestField2", Init.Symbols.ResolveCoreType("System.Int32"), global::System.Reflection.FieldAttributes.Public); + var m2 = t.DefineField("TestField3", Init.Symbols.ResolveCoreType("System.Int32"), global::System.Reflection.FieldAttributes.Public); + var m3 = t.DefineField("TestField1", Init.Symbols.ResolveCoreType("System.Int32"), global::System.Reflection.FieldAttributes.Public); + var m4 = t.DefineField("TestField4", Init.Symbols.ResolveCoreType("System.Int32"), global::System.Reflection.FieldAttributes.Public); + var z = Init.Symbols.ResolveType(t, IkvmReflectionSymbolState.Emitted); + var l = z.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); + l.Should().Satisfy( + i => i.Name == "TestField2", + i => i.Name == "TestField3", + i => i.Name == "TestField1", + i => i.Name == "TestField4"); + } + + [TestMethod] + public void ShouldDefineMethodsInOrder() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var m1 = t.DefineMethod("TestMethod2", global::System.Reflection.MethodAttributes.Public); + var m2 = t.DefineMethod("TestMethod3", global::System.Reflection.MethodAttributes.Public); + var m3 = t.DefineMethod("TestMethod1", global::System.Reflection.MethodAttributes.Public); + var m4 = t.DefineMethod("TestMethod4", global::System.Reflection.MethodAttributes.Public); + var z = Init.Symbols.ResolveType(t, IkvmReflectionSymbolState.Emitted); + var l = z.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); + l.Should().Satisfy( + i => i.Name == "TestMethod2", + i => i.Name == "TestMethod3", + i => i.Name == "TestMethod1", + i => i.Name == "TestMethod4"); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionAssemblySymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionAssemblySymbolTests.cs new file mode 100644 index 0000000000..ca332693a1 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionAssemblySymbolTests.cs @@ -0,0 +1,83 @@ +using System.Linq; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols.IkvmReflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection +{ + + [TestClass] + public class IkvmReflectionAssemblySymbolTests : AssemblySymbolTests + { + + [TestMethod] + public void ResolvedAssemblyShouldBeSame() + { + var a = Init.Universe.Load(typeof(TestClassAttribute).Assembly.GetName().Name); + var s = Init.Symbols.ResolveAssemblySymbol(a); + var s1 = Init.Symbols.ResolveAssemblySymbol(a); + s.Should().BeSameAs(s1); + } + + [TestMethod] + public void AssemblyPropertiesShouldMatch() + { + var a = Init.Universe.Load(typeof(TestClassAttribute).Assembly.GetName().Name); + var s = Init.Symbols.ResolveAssemblySymbol(a); + s.FullName.Should().Be(a.FullName); + s.Location.Should().Be(a.Location); + s.IsMissing.Should().BeFalse(); + } + + [TestMethod] + public void AssemblyIdentityShouldMatch() + { + var a = Init.Universe.Load(typeof(TestClassAttribute).Assembly.GetName().Name); + var s = Init.Symbols.ResolveAssemblySymbol(a); + s.Identity.Name.Should().Be(a.GetName().Name); + s.Identity.Version.Should().Be(a.GetName().Version); + s.Identity.CultureName.Should().Be(a.GetName().CultureName); + s.Identity.PublicKeyToken.Should().BeEquivalentTo(a.GetName().GetPublicKeyToken()); + } + + [TestMethod] + public void CanGetAssemblyModules() + { + var a = Init.Universe.Load(typeof(TestClassAttribute).Assembly.GetName().Name); + var s = Init.Symbols.ResolveAssemblySymbol(a); + var l = s.GetModules(); + l.Length.Should().Be(1); + l[0].Should().NotBeNull(); + l[0].Name.Should().Be("Microsoft.VisualStudio.TestPlatform.TestFramework.dll"); + } + + [TestMethod] + public void CanGetTypes() + { + var a = Init.Universe.Load(typeof(TestClassAttribute).Assembly.GetName().Name); + var s = Init.Symbols.ResolveAssemblySymbol(a); + var l = s.GetTypes(); + } + + [TestMethod] + public void CanGetCustomAttributes() + { + var a = Init.Universe.Load(typeof(TestClassAttribute).Assembly.GetName().Name); + var s = Init.Symbols.ResolveAssemblySymbol(a); + var l = s.GetCustomAttributes(true); + l.Should().HaveCountGreaterThan(5); + + var companyAttributeType = Init.Symbols.ResolveCoreType("System.Reflection.AssemblyCompanyAttribute"); + var companyAttribute = l.Single(e => e.AttributeType == companyAttributeType); + companyAttribute.NamedArguments.Should().HaveCount(0); + companyAttribute.ConstructorArguments.Should().HaveCount(1); + companyAttribute.ConstructorArguments[0].ArgumentType.Should().Be(Init.Symbols.ResolveCoreType("System.String")); + ((string?)companyAttribute.ConstructorArguments[0].Value).Should().Be("Microsoft Corporation"); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionModuleSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionModuleSymbolTests.cs new file mode 100644 index 0000000000..c181886ef5 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionModuleSymbolTests.cs @@ -0,0 +1,16 @@ +using IKVM.CoreLib.Symbols.IkvmReflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection +{ + + [TestClass] + public class IkvmReflectionModuleSymbolTests : ModuleSymbolTests + { + + + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionSymbolTestInit.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionSymbolTestInit.cs new file mode 100644 index 0000000000..589822265d --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionSymbolTestInit.cs @@ -0,0 +1,79 @@ +using System.IO; +using System.Threading; + +using IKVM.CoreLib.Symbols.IkvmReflection; +using IKVM.Reflection; + +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; + +namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection +{ + + public class IkvmReflectionSymbolTestInit : SymbolTestInit + { + + Universe? _universe; + IkvmReflectionSymbolContext? _symbols; + + /// + /// Gets the universe of types. + /// + public Universe Universe + { + get + { + if (_universe == null) + { + var universe = new Universe(typeof(object).Assembly.GetName().Name); + universe.AssemblyResolve += Universe_AssemblyResolve; + Interlocked.CompareExchange(ref _universe, universe, null); + } + + return _universe; + } + } + + /// + /// Gets the symbol context. + /// + public override IkvmReflectionSymbolContext Symbols + { + get + { + if (_symbols == null) + { + var coreAssembly = Universe.LoadFile(typeof(object).Assembly.GetAssemblyLocation()); + var thisAssembly = Universe.LoadFile(typeof(IkvmReflectionModuleSymbolTests).Assembly.GetAssemblyLocation()); + var symbols = new IkvmReflectionSymbolContext(Universe!, new IkvmReflectionSymbolOptions(true)); + Interlocked.CompareExchange(ref _symbols, symbols, null); + } + + return _symbols; + } + } + + /// + /// Attempt to load assembly from system. + /// + /// + /// + /// + Assembly? Universe_AssemblyResolve(object sender, ResolveEventArgs args) + { + try + { + var asm = System.Reflection.Assembly.Load(args.Name); + if (asm != null && File.Exists(asm.Location)) + return _universe!.LoadFile(asm.Location); + } + catch + { + + } + + return null; + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionSymbolTests.cs index e96a381685..39243a8d76 100644 --- a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionSymbolTests.cs +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionSymbolTests.cs @@ -1,199 +1,199 @@ -using System.IO; - -using FluentAssertions; - -using IKVM.CoreLib.Symbols.IkvmReflection; -using IKVM.Reflection; - -using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection -{ - - [TestClass] - public class IkvmReflectionSymbolTests - { - - class Foo - { - - T? field; - - bool Method(int p1) => true; - - } - - Universe? universe; - Assembly? coreAssembly; - Assembly? thisAssembly; - - [TestInitialize] - public void Setup() - { - universe = new Universe(typeof(object).Assembly.GetName().Name); - universe.AssemblyResolve += Universe_AssemblyResolve; - coreAssembly = universe.LoadFile(typeof(object).Assembly.GetAssemblyLocation()); - thisAssembly = universe.LoadFile(typeof(IkvmReflectionSymbolTests).Assembly.GetAssemblyLocation()); - } - - /// - /// Attempt to load assembly from system. - /// - /// - /// - /// - Assembly? Universe_AssemblyResolve(object sender, ResolveEventArgs args) - { - try - { - var asm = global::System.Reflection.Assembly.Load(args.Name); - if (asm != null && File.Exists(asm.Location)) - return universe!.LoadFile(asm.Location); - } - catch - { - - } - - return null; - } - - [TestMethod] - public void SameTypeShouldBeSame() - { - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "Object")); - var s2 = c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "Object")); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void GenericTypeDefinitionShouldBeSame() - { - var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1"); - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(t); - var s2 = c.GetOrCreateTypeSymbol(t); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void GenericTypeShouldBeSame() - { - var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1").MakeGenericType(universe!.GetBuiltInType("System", "Int32")); - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(t); - var s2 = c.GetOrCreateTypeSymbol(t); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void ArrayTypeShouldBeSame() - { - var t = universe!.GetBuiltInType("System", "Object").MakeArrayType(2); - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(t); - var s2 = c.GetOrCreateTypeSymbol(t); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void SZArrayTypeShouldBeSame() - { - var t = universe!.GetBuiltInType("System", "Object").MakeArrayType(); - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(t); - var s2 = c.GetOrCreateTypeSymbol(t); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public unsafe void PointerTypeShouldBeSame() - { - var t = universe!.GetBuiltInType("System", "Int32").MakePointerType(); - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(t); - var s2 = c.GetOrCreateTypeSymbol(t); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public unsafe void ByRefTypeShouldBeSame() - { - var t = universe!.GetBuiltInType("System", "Int32").MakeByRefType(); - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(t); - var s2 = c.GetOrCreateTypeSymbol(t); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void EnumTypeShouldBeSame() - { - var a = universe!.Load(typeof(global::System.AttributeTargets).Assembly.FullName); - var t = a.GetType("System.AttributeTargets"); - var c = new IkvmReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(t); - var s2 = c.GetOrCreateTypeSymbol(t); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void CanGetType() - { - var t = universe!.GetBuiltInType("System", "Object"); - var c = new IkvmReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(t); - s.Name.Should().Be("Object"); - s.FullName.Should().Be("System.Object"); - } - - [TestMethod] - public void CanGetFieldOfGenericTypeDefinition() - { - var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1"); - var c = new IkvmReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(t); - s.IsGenericType.Should().BeTrue(); - s.IsGenericTypeDefinition.Should().BeTrue(); - var f = s.GetField("field", global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance); - f.Name.Should().Be("field"); - f.FieldType.IsGenericType.Should().BeFalse(); - f.FieldType.IsGenericParameter.Should().BeTrue(); - } - - [TestMethod] - public void CanGetFieldOfGenericType() - { - var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1").MakeGenericType(universe!.GetBuiltInType("System", "Int32")); - var c = new IkvmReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(t); - s.IsGenericType.Should().BeTrue(); - s.IsGenericTypeDefinition.Should().BeFalse(); - var f = s.GetField("field", global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance); - f.Name.Should().Be("field"); - f.FieldType.IsGenericType.Should().BeFalse(); - f.FieldType.IsGenericParameter.Should().BeFalse(); - f.FieldType.Should().BeSameAs(c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "Int32"))); - } - - [TestMethod] - public void CanGetMethod() - { - var t = universe!.GetBuiltInType("System", "Object"); - var c = new IkvmReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(t); - var m = s.GetMethod("ToString"); - m.Name.Should().Be("ToString"); - m.ReturnType.Should().BeSameAs(c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "String"))); - m.ReturnParameter.ParameterType.Should().BeSameAs(c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "String"))); - m.IsGenericMethod.Should().BeFalse(); - m.IsGenericMethodDefinition.Should().BeFalse(); - m.IsPublic.Should().BeTrue(); - m.IsPrivate.Should().BeFalse(); - } - - } - -} +//using System.IO; + +//using FluentAssertions; + +//using IKVM.CoreLib.Symbols.IkvmReflection; +//using IKVM.Reflection; + +//using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; +//using Microsoft.VisualStudio.TestTools.UnitTesting; + +//namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection +//{ + +// [TestClass] +// public class IkvmReflectionSymbolTests +// { + +// class Foo +// { + +// T? field; + +// bool Method(int p1) => true; + +// } + +// Universe? universe; +// Assembly? coreAssembly; +// Assembly? thisAssembly; + +// [TestInitialize] +// public void Setup() +// { +// universe = new Universe(typeof(object).Assembly.GetName().Name); +// universe.AssemblyResolve += Universe_AssemblyResolve; +// coreAssembly = universe.LoadFile(typeof(object).Assembly.GetAssemblyLocation()); +// thisAssembly = universe.LoadFile(typeof(IkvmReflectionSymbolTests).Assembly.GetAssemblyLocation()); +// } + +// /// +// /// Attempt to load assembly from system. +// /// +// /// +// /// +// /// +// Assembly? Universe_AssemblyResolve(object sender, ResolveEventArgs args) +// { +// try +// { +// var asm = global::System.Reflection.Assembly.Load(args.Name); +// if (asm != null && File.Exists(asm.Location)) +// return universe!.LoadFile(asm.Location); +// } +// catch +// { + +// } + +// return null; +// } + +// [TestMethod] +// public void SameTypeShouldBeSame() +// { +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "Object")); +// var s2 = c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "Object")); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public void GenericTypeDefinitionShouldBeSame() +// { +// var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1"); +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(t); +// var s2 = c.GetOrCreateTypeSymbol(t); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public void GenericTypeShouldBeSame() +// { +// var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1").MakeGenericType(universe!.GetBuiltInType("System", "Int32")); +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(t); +// var s2 = c.GetOrCreateTypeSymbol(t); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public void ArrayTypeShouldBeSame() +// { +// var t = universe!.GetBuiltInType("System", "Object").MakeArrayType(2); +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(t); +// var s2 = c.GetOrCreateTypeSymbol(t); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public void SZArrayTypeShouldBeSame() +// { +// var t = universe!.GetBuiltInType("System", "Object").MakeArrayType(); +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(t); +// var s2 = c.GetOrCreateTypeSymbol(t); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public unsafe void PointerTypeShouldBeSame() +// { +// var t = universe!.GetBuiltInType("System", "Int32").MakePointerType(); +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(t); +// var s2 = c.GetOrCreateTypeSymbol(t); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public unsafe void ByRefTypeShouldBeSame() +// { +// var t = universe!.GetBuiltInType("System", "Int32").MakeByRefType(); +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(t); +// var s2 = c.GetOrCreateTypeSymbol(t); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public void EnumTypeShouldBeSame() +// { +// var a = universe!.Load(typeof(global::System.AttributeTargets).Assembly.FullName); +// var t = a.GetType("System.AttributeTargets"); +// var c = new IkvmReflectionSymbolContext(); +// var s1 = c.GetOrCreateTypeSymbol(t); +// var s2 = c.GetOrCreateTypeSymbol(t); +// s1.Should().BeSameAs(s2); +// } + +// [TestMethod] +// public void CanGetType() +// { +// var t = universe!.GetBuiltInType("System", "Object"); +// var c = new IkvmReflectionSymbolContext(); +// var s = c.GetOrCreateTypeSymbol(t); +// s.Name.Should().Be("Object"); +// s.FullName.Should().Be("System.Object"); +// } + +// [TestMethod] +// public void CanGetFieldOfGenericTypeDefinition() +// { +// var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1"); +// var c = new IkvmReflectionSymbolContext(); +// var s = c.GetOrCreateTypeSymbol(t); +// s.IsGenericType.Should().BeTrue(); +// s.IsGenericTypeDefinition.Should().BeTrue(); +// var f = s.GetField("field", global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance); +// f.Name.Should().Be("field"); +// f.FieldType.IsGenericType.Should().BeFalse(); +// f.FieldType.IsGenericParameter.Should().BeTrue(); +// } + +// [TestMethod] +// public void CanGetFieldOfGenericType() +// { +// var t = thisAssembly!.GetType("IKVM.CoreLib.Tests.Symbols.IkvmReflection.IkvmReflectionSymbolTests+Foo`1").MakeGenericType(universe!.GetBuiltInType("System", "Int32")); +// var c = new IkvmReflectionSymbolContext(); +// var s = c.GetOrCreateTypeSymbol(t); +// s.IsGenericType.Should().BeTrue(); +// s.IsGenericTypeDefinition.Should().BeFalse(); +// var f = s.GetField("field", global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance); +// f.Name.Should().Be("field"); +// f.FieldType.IsGenericType.Should().BeFalse(); +// f.FieldType.IsGenericParameter.Should().BeFalse(); +// f.FieldType.Should().BeSameAs(c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "Int32"))); +// } + +// [TestMethod] +// public void CanGetMethod() +// { +// var t = universe!.GetBuiltInType("System", "Object"); +// var c = new IkvmReflectionSymbolContext(); +// var s = c.GetOrCreateTypeSymbol(t); +// var m = s.GetMethod("ToString"); +// m.Name.Should().Be("ToString"); +// m.ReturnType.Should().BeSameAs(c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "String"))); +// m.ReturnParameter.ParameterType.Should().BeSameAs(c.GetOrCreateTypeSymbol(universe!.GetBuiltInType("System", "String"))); +// m.IsGenericMethod.Should().BeFalse(); +// m.IsGenericMethodDefinition.Should().BeFalse(); +// m.IsPublic.Should().BeTrue(); +// m.IsPrivate.Should().BeFalse(); +// } + +// } + +//} diff --git a/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionTypeSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionTypeSymbolTests.cs new file mode 100644 index 0000000000..5263731301 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/IkvmReflection/IkvmReflectionTypeSymbolTests.cs @@ -0,0 +1,20 @@ +using IKVM.CoreLib.Symbols.IkvmReflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.IkvmReflection +{ + + [TestClass] + public class IkvmReflectionTypeSymbolTests : TypeSymbolTests + { + + [TestMethod] + public void DoNothing() + { + + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/ModuleSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/ModuleSymbolTests.cs new file mode 100644 index 0000000000..f676950b50 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/ModuleSymbolTests.cs @@ -0,0 +1,77 @@ +using FluentAssertions; + +using IKVM.CoreLib.Symbols; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols +{ + + [TestClass] + public abstract class ModuleSymbolTests + where TInit : SymbolTestInit, new() + where TSymbols : SymbolContext + { + + protected TInit Init { get; } = new TInit(); + + [TestMethod] + public void CanResolve() + { + var m = Init.Symbols.ResolveCoreType("System.Object").Module; + m.Name.Should().Be(typeof(object).Module.Name); + } + + [TestMethod] + public void ShouldBeSame() + { + var s1 = Init.Symbols.ResolveCoreType("System.Object").Module; + var s2 = Init.Symbols.ResolveCoreType("System.Object").Module; + s1.Should().BeSameAs(s2); + } + + [TestMethod] + public void PropertiesShouldMatch() + { + var m = typeof(object).Module; + var s = Init.Symbols.ResolveCoreType("System.Object").Module; + s.Name.Should().Be(m.Name); + s.IsMissing.Should().BeFalse(); + s.FullyQualifiedName.Should().Be(m.FullyQualifiedName); + } + + [TestMethod] + public void CanGetFields() + { + var m = typeof(object).Module; + var s = Init.Symbols.ResolveCoreType("System.Object").Module; + var l = s.GetFields(); + } + + [TestMethod] + public void CanGetMethods() + { + var m = typeof(object).Module; + var s = Init.Symbols.ResolveCoreType("System.Object").Module; + var l = s.GetMethods(); + } + + [TestMethod] + public void CanGetTypes() + { + var m = typeof(object).Module; + var s = Init.Symbols.ResolveCoreType("System.Object").Module; + var l = s.GetTypes(); + } + + [TestMethod] + public void CanGetCustomAttributes() + { + var m = typeof(object).Module; + var s = Init.Symbols.ResolveCoreType("System.Object").Module; + var l = s.GetCustomAttributes(true); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionAssemblyBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionAssemblyBuilderTests.cs new file mode 100644 index 0000000000..dd4015df76 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionAssemblyBuilderTests.cs @@ -0,0 +1,64 @@ +using System.Reflection.Emit; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols; +using IKVM.CoreLib.Symbols.Reflection; +using IKVM.CoreLib.Tests.Symbols.Emit; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Reflection.Emit +{ + + [TestClass] + public class ReflectionAssemblyBuilderTests : AssemblySymbolBuilderTests + { + + [TestMethod] + public void CanResolveDefined() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + b.FullName.Should().Be("Test, Version=0.0.0.0, PublicKeyToken=null"); + var a = Init.Symbols.ResolveAssembly(b, ReflectionSymbolState.Defined); + a.Should().BeAssignableTo(); + a.FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Name.Should().Be("Test"); + a.GetName().FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Version.Should().Be(new System.Version("0.0.0.0")); + a.GetName().CultureName.Should().Be(""); + a.GetName().GetPublicKeyToken().Should().BeEmpty(); + } + + [TestMethod] + public void CanResolveEmitted() + { + var s = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var a = Init.Symbols.ResolveAssembly(s, ReflectionSymbolState.Emitted); + a.Should().BeAssignableTo(); + a.FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Name.Should().Be("Test"); + a.GetName().FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Version.Should().Be(new System.Version("0.0.0.0")); + a.GetName().CultureName.Should().Be(""); + a.GetName().GetPublicKeyToken().Should().BeEmpty(); + } + + [TestMethod] + public void CanResolveFinished() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + b.FullName.Should().Be("Test, Version=0.0.0.0, PublicKeyToken=null"); + var a = Init.Symbols.ResolveAssembly(b, ReflectionSymbolState.Emitted); + a.Should().BeAssignableTo(); + a.FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Name.Should().Be("Test"); + a.GetName().FullName.Should().Be("Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + a.GetName().Version.Should().Be(new System.Version("0.0.0.0")); + a.GetName().CultureName.Should().Be(""); + a.GetName().GetPublicKeyToken().Should().BeEmpty(); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionModuleBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionModuleBuilderTests.cs new file mode 100644 index 0000000000..434396136d --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionModuleBuilderTests.cs @@ -0,0 +1,15 @@ +using IKVM.CoreLib.Symbols.Reflection; +using IKVM.CoreLib.Tests.Symbols.Emit; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Reflection.Emit +{ + + [TestClass] + public class ReflectionModuleBuilderTests : ModuleSymbolBuilderTests + { + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionTypeBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionTypeBuilderTests.cs new file mode 100644 index 0000000000..5cbf288c28 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Reflection/Emit/ReflectionTypeBuilderTests.cs @@ -0,0 +1,123 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols; +using IKVM.CoreLib.Symbols.Reflection; +using IKVM.CoreLib.Tests.Symbols.Emit; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Reflection.Emit +{ + + [TestClass] + public class ReflectionTypeBuilderTests : TypeSymbolBuilderTests + { + + [TestMethod] + public void ShouldReturnTypeBuilderOnEmitted() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var z = Init.Symbols.ResolveType((TypeSymbol)t, ReflectionSymbolState.Emitted); + z.Should().BeAssignableTo(); + z.Namespace.Should().Be("Namespace"); + z.Name.Should().Be("TestType"); + z.FullName.Should().Be("Namespace.TestType"); + } + + [TestMethod] + public void ShouldReturnTypeOnFinished() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var z = Init.Symbols.ResolveType((TypeSymbol)t, ReflectionSymbolState.Finished); + z.Should().BeAssignableTo(); + z.Namespace.Should().Be("Namespace"); + z.Name.Should().Be("TestType"); + z.FullName.Should().Be("Namespace.TestType"); + } + + [TestMethod] + public void ShouldReturnMethodBuilderOnDefined() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var j = t.DefineMethod("TestMethod", MethodAttributes.Public); + var z = Init.Symbols.ResolveMethod((MethodSymbol)j, ReflectionSymbolState.Defined); + z.Should().BeOfType(); + z.Name.Should().Be("TestMethod"); + } + + [TestMethod] + public void ShouldReturnMethodBuilderOnEmitted() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var j = t.DefineMethod("TestMethod", MethodAttributes.Public); + var z = Init.Symbols.ResolveMethod((MethodSymbol)j, ReflectionSymbolState.Emitted); + z.Should().BeOfType(); + z.Name.Should().Be("TestMethod"); + } + + [TestMethod] + public void ShouldReturnMethodInfoOnFinished() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var j = t.DefineMethod("TestMethod", MethodAttributes.Public); + var z = Init.Symbols.ResolveMethod((MethodSymbol)j, ReflectionSymbolState.Finished); + z.Should().BeAssignableTo(); + z.Should().NotBeSameAs(j); + z.Name.Should().Be("TestMethod"); + } + + [TestMethod] + public void ShouldDefineFieldsInOrder() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var m1 = t.DefineField("TestField2", Init.Symbols.ResolveCoreType("System.Int32"), FieldAttributes.Public); + var m2 = t.DefineField("TestField3", Init.Symbols.ResolveCoreType("System.Int32"), FieldAttributes.Public); + var m3 = t.DefineField("TestField1", Init.Symbols.ResolveCoreType("System.Int32"), FieldAttributes.Public); + var m4 = t.DefineField("TestField4", Init.Symbols.ResolveCoreType("System.Int32"), FieldAttributes.Public); + var z = Init.Symbols.ResolveType(t, ReflectionSymbolState.Emitted); + var l = z.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); + l.Should().Satisfy( + i => i.Name == "TestField2", + i => i.Name == "TestField3", + i => i.Name == "TestField1", + i => i.Name == "TestField4"); + } + + [TestMethod] + public void ShouldDefineMethodsInOrder() + { + var b = Init.Symbols.DefineAssembly(new AssemblyIdentity("Test"), []); + var m = b.DefineModule("Test.dll", "Test.dll"); + var t = m.DefineType("Namespace.TestType"); + var m1 = t.DefineMethod("TestMethod2", MethodAttributes.Public); + var m2 = t.DefineMethod("TestMethod3", MethodAttributes.Public); + var m3 = t.DefineMethod("TestMethod1", MethodAttributes.Public); + var m4 = t.DefineMethod("TestMethod4", MethodAttributes.Public); + var z = Init.Symbols.ResolveType(t, ReflectionSymbolState.Emitted); + var l = z.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); + l.Should().Satisfy( + i => i.Name == "TestMethod2", + i => i.Name == "TestMethod3", + i => i.Name == "TestMethod1", + i => i.Name == "TestMethod4"); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionAssemblySymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionAssemblySymbolTests.cs new file mode 100644 index 0000000000..0c78b17e42 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionAssemblySymbolTests.cs @@ -0,0 +1,83 @@ +using System.Linq; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols.Reflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Reflection +{ + + [TestClass] + public class ReflectionAssemblySymbolTests : AssemblySymbolTests + { + + [TestMethod] + public void ResolvedAssemblyShouldBeSame() + { + var a = typeof(TestClassAttribute).Assembly; + var s = Init.Symbols.ResolveAssemblySymbol(a); + var s1 = Init.Symbols.ResolveAssemblySymbol(a); + s.Should().BeSameAs(s1); + } + + [TestMethod] + public void AssemblyPropertiesShouldMatch() + { + var a = typeof(TestClassAttribute).Assembly; + var s = Init.Symbols.ResolveAssemblySymbol(a); + s.FullName.Should().Be(a.FullName); + s.Location.Should().Be(a.Location); + s.IsMissing.Should().BeFalse(); + } + + [TestMethod] + public void AssemblyIdentityShouldMatch() + { + var a = typeof(TestClassAttribute).Assembly; + var s = Init.Symbols.ResolveAssemblySymbol(a); + s.Identity.Name.Should().Be(a.GetName().Name); + s.Identity.Version.Should().Be(a.GetName().Version); + s.Identity.CultureName.Should().Be(a.GetName().CultureName); + s.Identity.PublicKeyToken.Should().BeEquivalentTo(a.GetName().GetPublicKeyToken()); + } + + [TestMethod] + public void CanGetAssemblyModules() + { + var a = typeof(TestClassAttribute).Assembly; + var s = Init.Symbols.ResolveAssemblySymbol(a); + var l = s.GetModules(); + l.Length.Should().Be(1); + l[0].Should().NotBeNull(); + l[0].Name.Should().Be("Microsoft.VisualStudio.TestPlatform.TestFramework.dll"); + } + + [TestMethod] + public void CanGetTypes() + { + var a = typeof(TestClassAttribute).Assembly; + var s = Init.Symbols.ResolveAssemblySymbol(a); + var l = s.GetTypes(); + } + + [TestMethod] + public void CanGetCustomAttributes() + { + var a = typeof(TestClassAttribute).Assembly; + var s = Init.Symbols.ResolveAssemblySymbol(a); + var l = s.GetCustomAttributes(true); + l.Should().HaveCountGreaterThan(5); + + var companyAttributeType = Init.Symbols.ResolveCoreType("System.Reflection.AssemblyCompanyAttribute"); + var companyAttribute = l.Single(e => e.AttributeType == companyAttributeType); + companyAttribute.NamedArguments.Should().HaveCount(0); + companyAttribute.ConstructorArguments.Should().HaveCount(1); + companyAttribute.ConstructorArguments[0].ArgumentType.Should().Be(Init.Symbols.ResolveCoreType("System.String")); + ((string?)companyAttribute.ConstructorArguments[0].Value).Should().Be("Microsoft Corporation"); + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionModuleSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionModuleSymbolTests.cs new file mode 100644 index 0000000000..c442528857 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionModuleSymbolTests.cs @@ -0,0 +1,16 @@ +using IKVM.CoreLib.Symbols.Reflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Reflection +{ + + [TestClass] + public class ReflectionModuleSymbolTests : ModuleSymbolTests + { + + + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionSymbolTestInit.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionSymbolTestInit.cs new file mode 100644 index 0000000000..d61361f298 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionSymbolTestInit.cs @@ -0,0 +1,29 @@ +using System.Threading; + +using IKVM.CoreLib.Symbols.Reflection; + +namespace IKVM.CoreLib.Tests.Symbols.Reflection +{ + + public class ReflectionSymbolTestInit : SymbolTestInit + { + + ReflectionSymbolContext? _symbols; + + /// + /// Gets the symbol context. + /// + public override ReflectionSymbolContext Symbols + { + get + { + if (_symbols == null) + Interlocked.CompareExchange(ref _symbols, new ReflectionSymbolContext(typeof(object).Assembly, new ReflectionSymbolOptions(true)), null); + + return _symbols; + } + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionSymbolTests.cs deleted file mode 100644 index 3bc5b06b20..0000000000 --- a/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionSymbolTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; - -using FluentAssertions; - -using IKVM.CoreLib.Symbols.Reflection; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace IKVM.CoreLib.Tests.Symbols.Reflection -{ - - [TestClass] - public class ReflectionSymbolTests - { - - class Foo - { - T? field; - } - - [TestMethod] - public void SameTypeShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(object)); - var s2 = c.GetOrCreateTypeSymbol(typeof(object)); - s1.Should().BeOfType(); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void GenericTypeDefinitionShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(Foo<>)); - var s2 = c.GetOrCreateTypeSymbol(typeof(Foo<>)); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void GenericTypeShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(Foo)); - var s2 = c.GetOrCreateTypeSymbol(typeof(Foo)); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void ArrayTypeShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(object[,])); - var s2 = c.GetOrCreateTypeSymbol(typeof(object[,])); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void SZArrayTypeShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(object[])); - var s2 = c.GetOrCreateTypeSymbol(typeof(object[])); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public unsafe void PointerTypeShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(int*)); - var s2 = c.GetOrCreateTypeSymbol(typeof(int*)); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public unsafe void ByRefTypeShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(int).MakeByRefType()); - var s2 = c.GetOrCreateTypeSymbol(typeof(int).MakeByRefType()); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void EnumTypeShouldBeSame() - { - var c = new ReflectionSymbolContext(); - var s1 = c.GetOrCreateTypeSymbol(typeof(AttributeTargets)); - var s2 = c.GetOrCreateTypeSymbol(typeof(AttributeTargets)); - s1.Should().BeSameAs(s2); - } - - [TestMethod] - public void CanGetType() - { - var c = new ReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(typeof(object)); - s.Name.Should().Be("Object"); - s.FullName.Should().Be("System.Object"); - } - - [TestMethod] - public void CanGetFieldOfGenericTypeDefinition() - { - var c = new ReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(typeof(Foo<>)); - s.IsGenericType.Should().BeTrue(); - s.IsGenericTypeDefinition.Should().BeTrue(); - var f = s.GetField("field", global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance); - f.Name.Should().Be("field"); - f.FieldType.IsGenericType.Should().BeFalse(); - f.FieldType.IsGenericParameter.Should().BeTrue(); - } - - [TestMethod] - public void CanGetFieldOfGenericType() - { - var c = new ReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(typeof(Foo)); - s.IsGenericType.Should().BeTrue(); - s.IsGenericTypeDefinition.Should().BeFalse(); - var f = s.GetField("field", global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance); - f.Name.Should().Be("field"); - f.FieldType.IsGenericType.Should().BeFalse(); - f.FieldType.IsGenericParameter.Should().BeFalse(); - f.FieldType.Should().BeSameAs(c.GetOrCreateTypeSymbol(typeof(int))); - } - - [TestMethod] - public void CanGetMethod() - { - var c = new ReflectionSymbolContext(); - var s = c.GetOrCreateTypeSymbol(typeof(object)); - var m = s.GetMethod("ToString"); - m.Name.Should().Be("ToString"); - m.ReturnType.Should().BeSameAs(c.GetOrCreateTypeSymbol(typeof(string))); - m.ReturnParameter.ParameterType.Should().BeSameAs(c.GetOrCreateTypeSymbol(typeof(string))); - m.IsGenericMethod.Should().BeFalse(); - m.IsGenericMethodDefinition.Should().BeFalse(); - m.IsPublic.Should().BeTrue(); - m.IsPrivate.Should().BeFalse(); - } - - } - -} diff --git a/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionTypeSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionTypeSymbolTests.cs new file mode 100644 index 0000000000..04cbd5a0f6 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/Reflection/ReflectionTypeSymbolTests.cs @@ -0,0 +1,20 @@ +using IKVM.CoreLib.Symbols.Reflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols.Reflection +{ + + [TestClass] + public class ReflectionTypeSymbolTests : TypeSymbolTests + { + + [TestMethod] + public void DoNothing() + { + + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/SymbolTestInit.cs b/src/IKVM.CoreLib.Tests/Symbols/SymbolTestInit.cs new file mode 100644 index 0000000000..064ac386d8 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/SymbolTestInit.cs @@ -0,0 +1,13 @@ +using IKVM.CoreLib.Symbols; + +namespace IKVM.CoreLib.Tests.Symbols +{ + + public abstract class SymbolTestInit where TSymbols : SymbolContext + { + + public abstract TSymbols Symbols { get; } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/TypeSymbolNameBuilderTests.cs b/src/IKVM.CoreLib.Tests/Symbols/TypeSymbolNameBuilderTests.cs new file mode 100644 index 0000000000..1de28332da --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/TypeSymbolNameBuilderTests.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols +{ + + [TestClass] + public class TypeSymbolNameBuilderTests + { + + [TestMethod] + public void Foo() + { + + } + + } + +} diff --git a/src/IKVM.CoreLib.Tests/Symbols/TypeSymbolTests.cs b/src/IKVM.CoreLib.Tests/Symbols/TypeSymbolTests.cs new file mode 100644 index 0000000000..738d80c6a8 --- /dev/null +++ b/src/IKVM.CoreLib.Tests/Symbols/TypeSymbolTests.cs @@ -0,0 +1,412 @@ +using System.Collections.Generic; +using System.Reflection; + +using FluentAssertions; + +using IKVM.CoreLib.Symbols; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.CoreLib.Tests.Symbols +{ + + /// + /// Base class for some tests that interact with . + /// + public abstract class TypeSymbolTests + where TInit : SymbolTestInit, new() + where TSymbols : SymbolContext + { + + protected TInit Init { get; } = new TInit(); + + [TestMethod] + public void SystemObjectPropertiesShouldMatch() + { + var s = Init.Symbols.ResolveCoreType("System.Object"); + s.AssemblyQualifiedName.Should().Be(typeof(object).AssemblyQualifiedName); + s.Name.Should().Be(typeof(object).Name); + s.Namespace.Should().Be(typeof(object).Namespace); + s.FullName.Should().Be(typeof(object).FullName); + s.BaseType.Should().BeNull(); + s.HasElementType.Should().BeFalse(); + s.IsAbstract.Should().BeFalse(); + s.IsArray.Should().BeFalse(); + s.IsAutoLayout.Should().BeTrue(); + s.IsByRef.Should().BeFalse(); + s.IsClass.Should().BeTrue(); + s.IsConstructedGenericType.Should().BeFalse(); + s.IsEnum.Should().BeFalse(); + s.IsExplicitLayout.Should().BeFalse(); + s.IsFunctionPointer.Should().BeFalse(); + s.IsGenericMethodParameter.Should().BeFalse(); + s.IsGenericParameter.Should().BeFalse(); + s.IsGenericType.Should().BeFalse(); + s.IsGenericTypeDefinition.Should().BeFalse(); + s.IsGenericTypeParameter.Should().BeFalse(); + s.IsInterface.Should().BeFalse(); + s.IsLayoutSequential.Should().BeFalse(); + s.IsMissing.Should().BeFalse(); + s.IsNested.Should().BeFalse(); + s.IsNestedAssembly.Should().BeFalse(); + s.IsNestedFamANDAssem.Should().BeFalse(); + s.IsNestedFamily.Should().BeFalse(); + s.IsNestedFamORAssem.Should().BeFalse(); + s.IsNestedPrivate.Should().BeFalse(); + s.IsNestedPublic.Should().BeFalse(); + s.IsNotPublic.Should().BeFalse(); + s.IsPointer.Should().BeFalse(); + s.IsPrimitive.Should().BeFalse(); + s.IsPublic.Should().BeTrue(); + s.IsSealed.Should().BeFalse(); + s.IsSerializable.Should().BeTrue(); + s.IsSZArray.Should().BeFalse(); + s.IsTypeDefinition.Should().BeTrue(); + s.IsUnmanagedFunctionPointer.Should().BeFalse(); + s.IsValueType.Should().BeFalse(); + s.IsVisible.Should().BeTrue(); + s.ToString().Should().Be(typeof(object).ToString()); + s.GetCustomAttributes(true); + } + + [TestMethod] + public void ShouldInt32PropertiesShouldMatch() + { + var s = Init.Symbols.ResolveCoreType("System.Int32"); + s.AssemblyQualifiedName.Should().Be(typeof(int).AssemblyQualifiedName); + s.Name.Should().Be(typeof(int).Name); + s.Namespace.Should().Be(typeof(int).Namespace); + s.FullName.Should().Be(typeof(int).FullName); + s.BaseType.Should().Be(Init.Symbols.ResolveCoreType("System.ValueType")); + s.HasElementType.Should().BeFalse(); + s.IsAbstract.Should().BeFalse(); + s.IsArray.Should().BeFalse(); + s.IsAutoLayout.Should().BeFalse(); + s.IsByRef.Should().BeFalse(); + s.IsClass.Should().BeFalse(); + s.IsConstructedGenericType.Should().BeFalse(); + s.IsEnum.Should().BeFalse(); + s.IsExplicitLayout.Should().BeFalse(); + s.IsFunctionPointer.Should().BeFalse(); + s.IsGenericMethodParameter.Should().BeFalse(); + s.IsGenericParameter.Should().BeFalse(); + s.IsGenericType.Should().BeFalse(); + s.IsGenericTypeDefinition.Should().BeFalse(); + s.IsGenericTypeParameter.Should().BeFalse(); + s.IsInterface.Should().BeFalse(); + s.IsLayoutSequential.Should().BeTrue(); + s.IsMissing.Should().BeFalse(); + s.IsNested.Should().BeFalse(); + s.IsNestedAssembly.Should().BeFalse(); + s.IsNestedFamANDAssem.Should().BeFalse(); + s.IsNestedFamily.Should().BeFalse(); + s.IsNestedFamORAssem.Should().BeFalse(); + s.IsNestedPrivate.Should().BeFalse(); + s.IsNestedPublic.Should().BeFalse(); + s.IsNotPublic.Should().BeFalse(); + s.IsPointer.Should().BeFalse(); + s.IsPrimitive.Should().BeTrue(); + s.IsPublic.Should().BeTrue(); + s.IsSealed.Should().BeTrue(); + s.IsSerializable.Should().BeTrue(); + s.IsSZArray.Should().BeFalse(); + s.IsTypeDefinition.Should().BeTrue(); + s.IsUnmanagedFunctionPointer.Should().BeFalse(); + s.IsValueType.Should().BeTrue(); + s.IsVisible.Should().BeTrue(); + s.ToString().Should().Be(typeof(int).ToString()); + s.GetCustomAttributes(true); + } + + [TestMethod] + public void SystemInt32ShouldReturnSameInstance() + { + var s1 = Init.Symbols.ResolveCoreType("System.Int32"); + var s2 = Init.Symbols.ResolveCoreType("System.Int32"); + s1.Should().BeSameAs(s2); + } + + [TestMethod] + public void GetFieldShouldNotReturnInternalField() + { + var s = Init.Symbols.ResolveCoreType("System.Nullable`1"); + var f = s.GetField("value"); + f.Should().BeNull(); + } + + [TestMethod] + public void CanMakeArrayType() + { + var s = Init.Symbols.ResolveCoreType("System.Int32"); + var p = s.MakeArrayType(); + p.AssemblyQualifiedName.Should().Be(typeof(int[]).AssemblyQualifiedName); + p.Name.Should().Be(typeof(int[]).Name); + p.Namespace.Should().Be(typeof(int[]).Namespace); + p.FullName.Should().Be(typeof(int[]).FullName); + p.BaseType.Should().Be(Init.Symbols.ResolveCoreType("System.Array")); + p.HasElementType.Should().BeTrue(); + p.IsAbstract.Should().BeFalse(); + p.IsArray.Should().BeTrue(); + p.IsAutoLayout.Should().BeTrue(); + p.IsByRef.Should().BeFalse(); + p.IsClass.Should().BeTrue(); + p.IsConstructedGenericType.Should().BeFalse(); + p.IsEnum.Should().BeFalse(); + p.IsExplicitLayout.Should().BeFalse(); + p.IsFunctionPointer.Should().BeFalse(); + p.IsGenericMethodParameter.Should().BeFalse(); + p.IsGenericParameter.Should().BeFalse(); + p.IsGenericType.Should().BeFalse(); + p.IsGenericTypeDefinition.Should().BeFalse(); + p.IsGenericTypeParameter.Should().BeFalse(); + p.IsInterface.Should().BeFalse(); + p.IsLayoutSequential.Should().BeFalse(); + p.IsMissing.Should().BeFalse(); + p.IsNested.Should().BeFalse(); + p.IsNestedAssembly.Should().BeFalse(); + p.IsNestedFamANDAssem.Should().BeFalse(); + p.IsNestedFamily.Should().BeFalse(); + p.IsNestedFamORAssem.Should().BeFalse(); + p.IsNestedPrivate.Should().BeFalse(); + p.IsNestedPublic.Should().BeFalse(); + p.IsNotPublic.Should().BeFalse(); + p.IsPointer.Should().BeFalse(); + p.IsPrimitive.Should().BeFalse(); + p.IsPublic.Should().BeTrue(); + p.IsSealed.Should().BeTrue(); + p.IsSerializable.Should().BeTrue(); + p.IsSZArray.Should().BeTrue(); + p.IsTypeDefinition.Should().BeFalse(); + p.IsUnmanagedFunctionPointer.Should().BeFalse(); + p.IsValueType.Should().BeFalse(); + p.IsVisible.Should().BeTrue(); + p.ToString().Should().Be(typeof(int[]).ToString()); + p.GetCustomAttributes(true); + } + + [TestMethod] + public void SystemInt32ArrayShouldReturnSameInstance() + { + var s1 = Init.Symbols.ResolveCoreType("System.Int32").MakeArrayType(); + var s2 = Init.Symbols.ResolveCoreType("System.Int32").MakeArrayType(); + s1.Should().BeSameAs(s2); + } + + [TestMethod] + public void CanMakePointerType() + { + var s = Init.Symbols.ResolveCoreType("System.Int32"); + var p = s.MakePointerType(); + p.AssemblyQualifiedName.Should().Be(typeof(int*).AssemblyQualifiedName); + p.Name.Should().Be(typeof(int*).Name); + p.Namespace.Should().Be(typeof(int*).Namespace); + p.FullName.Should().Be(typeof(int*).FullName); + p.BaseType.Should().BeNull(); + p.HasElementType.Should().BeTrue(); + p.IsAbstract.Should().BeFalse(); + p.IsArray.Should().BeFalse(); + p.IsAutoLayout.Should().BeTrue(); + p.IsByRef.Should().BeFalse(); + p.IsClass.Should().BeTrue(); + p.IsConstructedGenericType.Should().BeFalse(); + p.IsEnum.Should().BeFalse(); + p.IsExplicitLayout.Should().BeFalse(); + p.IsFunctionPointer.Should().BeFalse(); + p.IsGenericMethodParameter.Should().BeFalse(); + p.IsGenericParameter.Should().BeFalse(); + p.IsGenericType.Should().BeFalse(); + p.IsGenericTypeDefinition.Should().BeFalse(); + p.IsGenericTypeParameter.Should().BeFalse(); + p.IsInterface.Should().BeFalse(); + p.IsLayoutSequential.Should().BeFalse(); + p.IsMissing.Should().BeFalse(); + p.IsNested.Should().BeFalse(); + p.IsNestedAssembly.Should().BeFalse(); + p.IsNestedFamANDAssem.Should().BeFalse(); + p.IsNestedFamily.Should().BeFalse(); + p.IsNestedFamORAssem.Should().BeFalse(); + p.IsNestedPrivate.Should().BeFalse(); + p.IsNestedPublic.Should().BeFalse(); + p.IsNotPublic.Should().BeFalse(); + p.IsPointer.Should().BeTrue(); + p.IsPrimitive.Should().BeFalse(); + p.IsPublic.Should().BeTrue(); + p.IsSealed.Should().BeFalse(); + p.IsSerializable.Should().BeFalse(); + p.IsSZArray.Should().BeFalse(); + p.IsTypeDefinition.Should().BeFalse(); + p.IsUnmanagedFunctionPointer.Should().BeFalse(); + p.IsValueType.Should().BeFalse(); + p.IsVisible.Should().BeTrue(); + p.ToString().Should().Be(typeof(int*).ToString()); + p.GetCustomAttributes(true); + } + + [TestMethod] + public void SystemInt32PointerShouldReturnSameInstance() + { + var s1 = Init.Symbols.ResolveCoreType("System.Int32").MakePointerType(); + var s2 = Init.Symbols.ResolveCoreType("System.Int32").MakePointerType(); + s1.Should().BeSameAs(s2); + } + + [TestMethod] + public void CanMakeGenericType() + { + var s = Init.Symbols.ResolveCoreType("System.Collections.Generic.List`1"); + var t = s.MakeGenericType([Init.Symbols.ResolveCoreType("System.Int32")]); + t.AssemblyQualifiedName.Should().Be(typeof(List).AssemblyQualifiedName); + t.Name.Should().Be(typeof(List).Name); + t.Namespace.Should().Be(typeof(List).Namespace); + t.FullName.Should().Be(typeof(List).FullName); + t.BaseType.Should().Be(Init.Symbols.ResolveCoreType("System.Object")); + t.HasElementType.Should().BeFalse(); + t.IsAbstract.Should().BeFalse(); + t.IsArray.Should().BeFalse(); + t.IsAutoLayout.Should().BeTrue(); + t.IsByRef.Should().BeFalse(); + t.IsClass.Should().BeTrue(); + t.IsConstructedGenericType.Should().BeTrue(); + t.IsEnum.Should().BeFalse(); + t.IsExplicitLayout.Should().BeFalse(); + t.IsFunctionPointer.Should().BeFalse(); + t.IsGenericMethodParameter.Should().BeFalse(); + t.IsGenericParameter.Should().BeFalse(); + t.IsGenericType.Should().BeTrue(); + t.IsGenericTypeDefinition.Should().BeFalse(); + t.IsGenericTypeParameter.Should().BeFalse(); + t.IsInterface.Should().BeFalse(); + t.IsLayoutSequential.Should().BeFalse(); + t.IsMissing.Should().BeFalse(); + t.IsNested.Should().BeFalse(); + t.IsNestedAssembly.Should().BeFalse(); + t.IsNestedFamANDAssem.Should().BeFalse(); + t.IsNestedFamily.Should().BeFalse(); + t.IsNestedFamORAssem.Should().BeFalse(); + t.IsNestedPrivate.Should().BeFalse(); + t.IsNestedPublic.Should().BeFalse(); + t.IsNotPublic.Should().BeFalse(); + t.IsPointer.Should().BeFalse(); + t.IsPrimitive.Should().BeFalse(); + t.IsPublic.Should().BeTrue(); + t.IsSealed.Should().BeFalse(); + t.IsSerializable.Should().BeTrue(); + t.IsSZArray.Should().BeFalse(); + t.IsTypeDefinition.Should().BeFalse(); + t.IsUnmanagedFunctionPointer.Should().BeFalse(); + t.IsValueType.Should().BeFalse(); + t.IsVisible.Should().BeTrue(); + t.ToString().Should().Be(typeof(List).ToString()); + t.GetCustomAttributes(true); + } + + [TestMethod] + public void CanGetFieldFromConstructedType() + { + var s = Init.Symbols.ResolveCoreType("System.Nullable`1"); + var t = s.MakeGenericType([Init.Symbols.ResolveCoreType("System.Int32")]); + var f = t.GetField("value", BindingFlags.NonPublic | BindingFlags.Instance); + f.Should().BeOfType(typeof(SpecializedFieldSymbol)); + f.DeclaringType.Should().BeSameAs(t); + f.Name.Should().Be("value"); + f.IsAssembly.Should().BeTrue(); + f.IsFamily.Should().BeFalse(); + f.IsFamilyAndAssembly.Should().BeFalse(); + f.IsFamilyOrAssembly.Should().BeFalse(); + f.IsInitOnly.Should().BeFalse(); + f.IsLiteral.Should().BeFalse(); + f.IsMissing.Should().BeFalse(); + f.IsNotSerialized.Should().BeFalse(); + f.IsPrivate.Should().BeFalse(); + f.IsPublic.Should().BeFalse(); + f.IsSpecialName.Should().BeFalse(); + f.IsStatic.Should().BeFalse(); + f.GetCustomAttributes(true); + + var ft = f.FieldType; + ft.Should().BeSameAs(Init.Symbols.ResolveCoreType("System.Int32")); + } + + [TestMethod] + public void CanGetMethodFromConstructedType() + { + var s = Init.Symbols.ResolveCoreType("System.Collections.Generic.List`1"); + var t = s.MakeGenericType([Init.Symbols.ResolveCoreType("System.Int32")]); + var m = t.GetMethod("Add"); + m.Should().BeOfType(typeof(SpecializedMethodSymbol)); + m.DeclaringType.Should().BeSameAs(t); + m.Name.Should().Be("Add"); + m.ContainsGenericParameters.Should().BeFalse(); + m.IsAbstract.Should().BeFalse(); + m.IsAssembly.Should().BeFalse(); + m.IsConstructor.Should().BeFalse(); + m.IsFamily.Should().BeFalse(); + m.IsFamilyAndAssembly.Should().BeFalse(); + m.IsFamilyOrAssembly.Should().BeFalse(); + m.IsFinal.Should().BeTrue(); + m.IsGenericMethod.Should().BeFalse(); + m.IsGenericMethodDefinition.Should().BeFalse(); + m.IsHideBySig.Should().BeTrue(); + m.IsMissing.Should().BeFalse(); + m.IsPrivate.Should().BeFalse(); + m.IsPublic.Should().BeTrue(); + m.IsSpecialName.Should().BeFalse(); + m.IsStatic.Should().BeFalse(); + m.IsVirtual.Should().BeTrue(); + m.MethodImplementationFlags.Should().Be(typeof(List).GetMethod("Add")!.MethodImplementationFlags); + m.MemberType.Should().Be(MemberTypes.Method); + m.GetCustomAttributes(true); + + var pl = m.Parameters; + pl.Should().HaveCount(1); + var p0 = pl[0]; + p0.Name.Should().Be("item"); + p0.ParameterType.Should().BeSameAs(Init.Symbols.ResolveCoreType("System.Int32")); + } + + [TestMethod] + public void CanGetConstructor() + { + var s = Init.Symbols.ResolveCoreType("System.Object"); + var c = s.GetConstructor([]); + c.Should().NotBeNull(); + c.Name.Should().Be(ConstructorInfo.ConstructorName); + c.IsConstructor.Should().BeTrue(); + c.Attributes.Should().HaveFlag(MethodAttributes.SpecialName); + c.Attributes.Should().HaveFlag(MethodAttributes.RTSpecialName); + } + + [TestMethod] + public void CanGetStaticConstructor() + { + var s = Init.Symbols.ResolveCoreType("System.Reflection.Module"); + var c = s.TypeInitializer; + c.Should().NotBeNull(); + c.Name.Should().Be(ConstructorInfo.TypeConstructorName); + c.IsConstructor.Should().BeTrue(); + c.Attributes.Should().HaveFlag(MethodAttributes.SpecialName); + c.Attributes.Should().HaveFlag(MethodAttributes.RTSpecialName); + } + + [TestMethod] + public void CanGetGenericMethodFromGenericType() + { + var typeOfFunc2 = Init.Symbols.ResolveCoreType("System.Func`2"); + var typeOfTask = Init.Symbols.ResolveCoreType("System.Threading.Tasks.Task"); + var typeOfTask1 = Init.Symbols.ResolveCoreType("System.Threading.Tasks.Task`1"); + + var m = typeOfTask1.GetMethod("ContinueWith", 1, [ + TypeSymbolSelector.Predicate(t => + t.GenericTypeDefinition == typeOfFunc2 && + t.GenericParameters is [var arg1, { IsGenericMethodParameter: true, GenericParameterPosition: 0 }] && arg1 == typeOfTask), + Init.Symbols.ResolveCoreType("System.Threading.CancellationToken")], + default); + + var m2 = m.MakeGenericMethod([Init.Symbols.ResolveCoreType("System.Int32")]); + + var p = m2.ParameterTypes; + } + + } + +} diff --git a/src/IKVM.CoreLib/Collections/EnumerableExtensions.cs b/src/IKVM.CoreLib/Collections/EnumerableExtensions.cs new file mode 100644 index 0000000000..25fade2d70 --- /dev/null +++ b/src/IKVM.CoreLib/Collections/EnumerableExtensions.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; + +namespace IKVM.CoreLib.Collections +{ + + static class EnumerableExtensions + { + + /// + /// Returns the only item in the collection, or throws the exception. + /// + /// + /// + /// + /// + /// + public static TSource? SingleOrDefaultOrThrow(this IEnumerable source, Func exception) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + if (source is IReadOnlyList list) + { + switch (list.Count) + { + case 0: + return default; + case 1: + return list[0]; + } + } + else + { + using IEnumerator enumerator = source.GetEnumerator(); + if (enumerator.MoveNext() == false) + return default; + + var current = enumerator.Current; + if (enumerator.MoveNext() == false) + return current; + } + + throw exception(); + } + + /// + /// Returns the only item in the collection, or throws the exception. + /// + /// + /// + /// + /// + /// + /// + /// + public static TSource? SingleOrDefaultOrThrow(this IEnumerable source, Predicate predicate, Func exception) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + var val = default(TSource); + var num = 0; + + foreach (var item in source) + { + if (predicate(item)) + { + val = item; + if ((++num) >= 2) + throw exception(); + } + } + + return num switch + { + 0 => default, + 1 => val, + _ => throw new InvalidOperationException(), + }; + } + + } + +} diff --git a/src/IKVM.CoreLib/Collections/ImmutableExtensions.cs b/src/IKVM.CoreLib/Collections/ImmutableExtensions.cs new file mode 100644 index 0000000000..044993b55c --- /dev/null +++ b/src/IKVM.CoreLib/Collections/ImmutableExtensions.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace IKVM.CoreLib.Collections +{ + + public static class ImmutableExtensions + { + + /// + /// Value-type implementation of . + /// + /// + public readonly struct ImmutableArrayValueComparer : IEqualityComparer> + where TComparer : IEqualityComparer + { + + readonly TComparer comparer; + + /// + /// Initializes a new instance. + /// + /// + public ImmutableArrayValueComparer(TComparer comparer) + { + this.comparer = comparer; + } + + /// + public bool Equals(ImmutableArray x, ImmutableArray y) + { + if (x == y) + return true; + + if (x.Length != y.Length) + return false; + + for (int i = 0; i < x.Length; i++) + if (comparer.Equals(x[i], y[i]) == false) + return false; + + return true; + } + + /// + public int GetHashCode([DisallowNull] ImmutableArray obj) + { + var h = new HashCode(); + h.Add(obj.Length); + + for (int i = 0; i < obj.Length; i++) + h.Add(obj[i], comparer); + + return h.ToHashCode(); + } + } + + /// + /// Value-type implementation of . + /// + /// + public readonly struct ValueReferenceEqualityComparer : IEqualityComparer + where T : class + { + + /// + public bool Equals(T? x, T? y) + { + return x == y; + } + + /// + public int GetHashCode([DisallowNull] T obj) + { + return obj.GetHashCode(); + } + + } + + /// + /// Returns true if the two given instances are exactly equal, including their contents. + /// + /// + /// + /// + /// + public static bool ImmutableArrayReferenceEquals(this ImmutableArray x, ImmutableArray y) + where T : class + { + return ImmutableArrayEquals(x, y, new ValueReferenceEqualityComparer()); + } + + /// + /// Returns true if the two given instances are exactly equal, including their contents. + /// + /// + /// + /// + /// + public static bool ImmutableArrayEquals(this ImmutableArray x, ImmutableArray y) + { + return ImmutableArrayEquals(x, y, EqualityComparer.Default); + } + + /// + /// Returns true if the two given instances are exactly equal, including their contents. + /// + /// + /// + /// + /// + public static bool ImmutableArrayEquals(this ImmutableArray x, ImmutableArray y, TComparer comparer) + where TComparer : IEqualityComparer + { + return new ImmutableArrayValueComparer(comparer).Equals(x, y); + } + + } + +} diff --git a/src/IKVM.CoreLib/Collections/IndexRangeDictionary.cs b/src/IKVM.CoreLib/Collections/IndexRangeDictionary.cs new file mode 100644 index 0000000000..e361df9bbb --- /dev/null +++ b/src/IKVM.CoreLib/Collections/IndexRangeDictionary.cs @@ -0,0 +1,159 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace IKVM.CoreLib.Collections +{ + + /// + /// Represents a dictionary that can store int keys mapped to values, where the underlying storage is an array that + /// holds the minimum number of items for the minimum and maximum key values. + /// + struct IndexRangeDictionary + { + + const int ALIGNMENT = 8; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static internal int AlignTowardsInfinity(int i) + { + if (i >= 0) + return (i + (ALIGNMENT - 1)) & -ALIGNMENT; + else + return -((-i + (ALIGNMENT - 1)) & -ALIGNMENT); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static internal int AlignTowardsZero(int i) + { + if (i >= 0) + return i - (i % ALIGNMENT); + else + return -(-i - (-i % ALIGNMENT)); + } + + int _maxCapacity; + internal int _minKey = 0; + internal int _maxKey = 0; + internal T?[] _items = []; + + /// + /// Initializes a new instance. + /// + public IndexRangeDictionary() : this(maxCapacity: int.MaxValue) + { + + } + + /// + /// Initializes a new instance. + /// + public IndexRangeDictionary(int maxCapacity = int.MaxValue) + { + if (maxCapacity < 0) + throw new ArgumentOutOfRangeException(nameof(maxCapacity)); + + _maxCapacity = maxCapacity; + } + + /// + /// Gets the capacity of the dictionary. + /// + public readonly int Capacity => _items.Length; + + /// + /// Gets or sets the item with the specified key, optionally growing the list to accomidate. + /// + /// + /// + public T? this[int key] + { + readonly get => Get(key); + set => Set(key, value); + } + + /// + /// Ensures the list is sized such that it can hold the specified key. + /// + /// + /// + public void EnsureCapacity(int key) + { + // on first hit, set keys to this key (not 0) + if (_items.Length == 0) + { + _minKey = key; + _maxKey = key; + } + + // calculate new min and max aligned + var newMin = Math.Min(_minKey, Math.Min(AlignTowardsZero(key), AlignTowardsInfinity(key))); + var newMax = Math.Max(_maxKey, Math.Max(AlignTowardsZero(key), AlignTowardsInfinity(key))); + + // calculate desired length + var len = Math.Max(_items.Length, 8); + while (len < newMax - newMin + 1) + len *= 2; + + // calculate amount to shift + int sft = 0; + if (newMin < _minKey) + sft = _minKey - newMin; + + // if we calculated any resize or shift operation, apply + if (_items.Length != len || sft > 0) + { + // we will be copying data either to either existing array or new array + var src = _items; + if (_items.Length != len) + _items = new T[len]; + + // copy source data to destination at shift + // clear newly exposed positions + if (src.Length > 0) + { + Array.Copy(src, 0, _items, sft, _maxKey - _minKey + 1); + Array.Clear(_items, 0, sft); + } + } + + // reset our min and max range + _minKey = newMin; + _maxKey = newMax; + + Debug.Assert(key - _minKey >= 0); + Debug.Assert(key - _minKey < _items.Length); + } + + /// + /// Adds a new item to the list. + /// + /// + readonly T? Get(int key) + { + var pos = key - _minKey; + if (pos < 0 || pos >= _items.Length) + return default; + else + return _items[pos]; + } + + /// + /// Adds a new item to the list. + /// + /// + /// + void Set(int key, T? value) + { + EnsureCapacity(key); + if (_items == null) + throw new InvalidOperationException(); + + Debug.Assert(key - _minKey >= 0); + Debug.Assert(key - _minKey < _items.Length); + _items[key - _minKey] = value; + } + + } + +} diff --git a/src/IKVM.CoreLib/System/LexicographicListComparer.cs b/src/IKVM.CoreLib/Collections/LexicographicListComparer.cs similarity index 98% rename from src/IKVM.CoreLib/System/LexicographicListComparer.cs rename to src/IKVM.CoreLib/Collections/LexicographicListComparer.cs index 6c01ef2a42..a310fccc1c 100644 --- a/src/IKVM.CoreLib/System/LexicographicListComparer.cs +++ b/src/IKVM.CoreLib/Collections/LexicographicListComparer.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace IKVM.CoreLib.System +namespace IKVM.CoreLib.Collections { /// diff --git a/src/IKVM.CoreLib/Collections/WeakHashTable.cs b/src/IKVM.CoreLib/Collections/WeakHashTable.cs new file mode 100644 index 0000000000..1715a00384 --- /dev/null +++ b/src/IKVM.CoreLib/Collections/WeakHashTable.cs @@ -0,0 +1,872 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Threading; + +using IKVM.CoreLib.Runtime; + +namespace IKVM.CoreLib.Collections +{ + + public sealed class WeakHashTable : + IEnumerable> + where TKey : class + where TValue : class? + { + + const int InitialCapacity = 8; + + readonly Lock _lock; + readonly IEqualityComparer _comparer; + volatile Container _container; + int _activeEnumeratorRefCount; + + /// + /// Initializes a new instance. + /// + public WeakHashTable() : + this(EqualityComparer.Default) + { + + } + + /// + /// Initializes a new instance. + /// + /// + /// + public WeakHashTable(IEqualityComparer comparer) + { + _lock = new Lock(); + _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + _container = new Container(this); + } + + /// + /// Gets the value of the specified key. + /// + /// Key of the value to find. Cannot be null. + /// + /// If the key is found, contains the value associated with the key upon method return. + /// If the key is not found, contains default(TValue). + /// + /// Returns "true" if key was found, "false" otherwise. + /// + /// The key may get garbage collected during the TryGetValue operation. If so, TryGetValue + /// may at its discretion, return "false" and set "value" to the default (as if the key was not present.) + /// + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + return _container.TryGetValueWorker(key, out value); + } + + /// Adds a key to the table. + /// key to add. May not be null. + /// value to associate with key. + /// + /// If the key is already entered into the dictionary, this method throws an exception. + /// The key may get garbage collected during the Add() operation. If so, Add() + /// has the right to consider any prior entries successfully removed and add a new entry without + /// throwing an exception. + /// + public void Add(TKey key, TValue value) + { + lock (_lock) + { + int entryIndex = _container.FindEntry(key, out _); + if (entryIndex != -1) + throw new ArgumentException("An element with the same key already exists."); + + CreateEntry(key, value); + } + } + + /// Adds a key to the table if it doesn't already exist. + /// The key to add. + /// The key's property value. + /// true if the key/value pair was added; false if the table already contained the key. + public bool TryAdd(TKey key, TValue value) + { + lock (_lock) + { + int entryIndex = _container.FindEntry(key, out _); + if (entryIndex != -1) + return false; + + CreateEntry(key, value); + return true; + } + } + + /// + /// Adds the key and value if the key doesn't exist, or updates the existing key's value if it does exist. + /// + /// key to add or update. May not be null. + /// value to associate with key. + public void AddOrUpdate(TKey key, TValue value) + { + lock (_lock) + { + int entryIndex = _container.FindEntry(key, out _); + + // if we found a key we should just update, if no we should create a new entry. + if (entryIndex != -1) + { + _container.UpdateValue(entryIndex, value); + } + else + { + CreateEntry(key, value); + } + } + } + + /// + /// Removes a key and its value from the table. + /// + /// key to remove. May not be null. + /// true if the key is found and removed. Returns false if the key was not in the dictionary. + /// + /// The key may get garbage collected during the Remove() operation. If so, + /// Remove() will not fail or throw, however, the return value can be either true or false + /// depending on who wins the race. + /// + public bool Remove(TKey key) + { + lock (_lock) + { + return _container.Remove(key); + } + } + + /// + /// Clear all the key/value pairs. + /// + public void Clear() + { + lock (_lock) + { + // To clear, we would prefer to simply drop the existing container + // and replace it with an empty one, as that's overall more efficient. + // However, if there are any active enumerators, we don't want to do + // that as it will end up removing all of the existing entries and + // allowing new items to be added at the same indices when the container + // is filled and replaced, and one of the guarantees we try to make with + // enumeration is that new items added after enumeration starts won't be + // included in the enumeration. As such, if there are active enumerators, + // we simply use the container's removal functionality to remove all of the + // keys; then when the table is resized, if there are still active enumerators, + // these empty slots will be maintained. + if (_activeEnumeratorRefCount > 0) + { + _container.RemoveAllKeys(); + } + else + { + _container = new Container(this); + } + } + } + + /// + /// Atomically searches for a specified key in the table and returns the corresponding value. + /// If the key does not exist in the table, the method invokes a callback method to create a + /// value that is bound to the specified key. + /// + /// key of the value to find. Cannot be null. + /// callback that creates value for key. Cannot be null. + /// + /// + /// If multiple threads try to initialize the same key, the table may invoke createValueCallback + /// multiple times with the same key. Exactly one of these calls will succeed and the returned + /// value of that call will be the one added to the table and returned by all the racing GetValue() calls. + /// This rule permits the table to invoke createValueCallback outside the internal table lock + /// to prevent deadlocks. + /// + public TValue GetOrCreateValue(TKey key, Func createFunc) + { + // key is validated by TryGetValue + return TryGetValue(key, out TValue? existingValue) ? + existingValue : + GetValueLocked(key, createFunc); + } + + TValue GetValueLocked(TKey key, Func createFunc) + { + // If we got here, the key was not in the table. Invoke the callback (outside the lock) + // to generate the new value for the key. + var newValue = createFunc(key); + + lock (_lock) + { + // Now that we've taken the lock, must recheck in case we lost a race to add the key. + if (_container.TryGetValueWorker(key, out TValue? existingValue)) + { + return existingValue; + } + else + { + // Verified in-lock that we won the race to add the key. Add it now. + CreateEntry(key, newValue); + return newValue; + } + } + } + + /// + /// Gets an enumerator for the table. + /// + /// + /// The returned enumerator will not extend the lifetime of + /// any object pairs in the table, other than the one that's Current. It will not return entries + /// that have already been collected, nor will it return entries added after the enumerator was + /// retrieved. It may not return all entries that were present when the enumerat was retrieved, + /// however, such as not returning entries that were collected or removed after the enumerator + /// was retrieved but before they were enumerated. + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + lock (_lock) + { + var c = _container; + return c is null || c.FirstFreeEntry == 0 ? + Enumerable.Empty>().GetEnumerator() : + new Enumerator(this); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); + + /// + /// Provides an enumerator for the table. + /// + sealed class Enumerator : IEnumerator> + { + // The enumerator would ideally hold a reference to the Container and the end index within that + // container. However, the safety of the CWT depends on the only reference to the Container being + // from the CWT itself; the Container then employs a two-phase finalization scheme, where the first + // phase nulls out that parent CWT's reference, guaranteeing that the second time it's finalized there + // can be no other existing references to it in use that would allow for concurrent usage of the + // native handles with finalization. We would break that if we allowed this Enumerator to hold a + // reference to the Container. Instead, the Enumerator holds a reference to the CWT rather than to + // the Container, and it maintains the CWT._activeEnumeratorRefCount field to track whether there + // are outstanding enumerators that have yet to be disposed/finalized. If there aren't any, the CWT + // behaves as it normally does. If there are, certain operations are affected, in particular resizes. + // Normally when the CWT is resized, it enumerates the contents of the table looking for indices that + // contain entries which have been collected or removed, and it frees those up, effectively moving + // down all subsequent entries in the container (not in the existing container, but in a replacement). + // This, however, would cause the enumerator's understanding of indices to break. So, as long as + // there is any outstanding enumerator, no compaction is performed. + + WeakHashTable? _table; // parent table, set to null when disposed + readonly int _maxIndexInclusive; // last index in the container that should be enumerated + int _currentIndex; // the current index into the container + KeyValuePair _current; // the current entry set by MoveNext and returned from Current + + /// + /// Initializes a new instance. + /// + /// + public Enumerator(WeakHashTable table) + { + Debug.Assert(table != null, "Must provide a valid table"); + Debug.Assert(table._lock.IsHeldByCurrentThread, "Must hold the _lock lock to construct the enumerator"); + Debug.Assert(table._container != null, "Should not be used on a finalized table"); + Debug.Assert(table._container.FirstFreeEntry > 0, "Should have returned an empty enumerator instead"); + + // Store a reference to the parent table and increase its active enumerator count. + _table = table; + Debug.Assert(table._activeEnumeratorRefCount >= 0, "Should never have a negative ref count before incrementing"); + table._activeEnumeratorRefCount++; + + // Store the max index to be enumerated. + _maxIndexInclusive = table._container.FirstFreeEntry - 1; + _currentIndex = -1; + } + + /// + /// Finalize the instance. + /// + ~Enumerator() + { + Dispose(); + } + + /// + /// Disposes of the instance. + /// + public void Dispose() + { + // Use an interlocked operation to ensure that only one thread can get access to + // the _table for disposal and thus only decrement the ref count once. + var table = Interlocked.Exchange(ref _table, null); + if (table != null) + { + // Ensure we don't keep the last current alive unnecessarily + _current = default; + + // Decrement the ref count that was incremented when constructed + lock (table._lock) + { + table._activeEnumeratorRefCount--; + Debug.Assert(table._activeEnumeratorRefCount >= 0, "Should never have a negative ref count after decrementing"); + } + + // Finalization is purely to decrement the ref count. We can suppress it now. + GC.SuppressFinalize(this); + } + } + + public bool MoveNext() + { + // Start by getting the current table. If it's already been disposed, it will be null. + var table = _table; + if (table != null) + { + // Once have the table, we need to lock to synchronize with other operations on + // the table, like adding. + lock (table._lock) + { + // From the table, we have to get the current container. This could have changed + // since we grabbed the enumerator, but the index-to-pair mapping should not have + // due to there being at least one active enumerator. If the table (or rather its + // container at the time) has already been finalized, this will be null. + var c = table._container; + if (c != null) + { + // We have the container. Find the next entry to return, if there is one. + // We need to loop as we may try to get an entry that's already been removed + // or collected, in which case we try again. + while (_currentIndex < _maxIndexInclusive) + { + _currentIndex++; + if (c.TryGetEntry(_currentIndex, out TKey? key, out TValue? value)) + { + _current = new KeyValuePair(key, value!); + return true; + } + } + } + } + } + + // Nothing more to enumerate. + return false; + } + + /// + public KeyValuePair Current + { + get + { + if (_currentIndex < 0) + throw new InvalidOperationException("Enumeration cannot happen."); + + return _current; + } + } + + /// + object? IEnumerator.Current => Current; + + /// + public void Reset() { } + + } + + /// + /// Worker for adding a new key/value pair. Will resize the container if it is full. + /// + /// + /// + void CreateEntry(TKey key, TValue value) + { + Debug.Assert(_lock.IsHeldByCurrentThread); + Debug.Assert(key != null); // key already validated as non-null and not already in table. + + var c = _container; + if (!c.HasCapacity) + _container = c = c.Resize(); + + c.CreateEntryNoResize(key, value); + } + + //-------------------------------------------------------------------------------------------- + // Entry can be in one of four states: + // + // - Unused (stored with an index _firstFreeEntry and above) + // depHnd.IsAllocated == false + // hashCode == + // next == ) + // + // - Used with live key (linked into a bucket list where _buckets[hashCode & (_buckets.Length - 1)] points to first entry) + // depHnd.IsAllocated == true, depHnd.GetPrimary() != null + // hashCode == RuntimeHelpers.GetHashCode(depHnd.GetPrimary()) & int.MaxValue + // next links to next Entry in bucket. + // + // - Used with dead key (linked into a bucket list where _buckets[hashCode & (_buckets.Length - 1)] points to first entry) + // depHnd.IsAllocated == true, depHnd.GetPrimary() is null + // hashCode == + // next links to next Entry in bucket. + // + // - Has been removed from the table (by a call to Remove) + // depHnd.IsAllocated == true, depHnd.GetPrimary() == + // hashCode == -1 + // next links to next Entry in bucket. + // + // The only difference between "used with live key" and "used with dead key" is that + // depHnd.GetPrimary() returns null. The transition from "used with live key" to "used with dead key" + // happens asynchronously as a result of normal garbage collection. The dictionary itself + // receives no notification when this happens. + // + // When the dictionary grows the _entries table, it scours it for expired keys and does not + // add those to the new container. + //-------------------------------------------------------------------------------------------- + struct Entry + { + + public DependentHandle Handle; // Holds key and value using a weak reference for the key and a strong reference + // for the value that is traversed only if the key is reachable without going through the value. + public int HashCode; // Cached copy of key's hashcode + public int Next; // Index of next entry, -1 if last + + } + + /// + /// Container holds the actual data for the table. A given instance of Container always has the same capacity. When we need + /// more capacity, we create a new Container, copy the old one into the new one, and discard the old one. This helps enable lock-free + /// reads from the table, as readers never need to deal with motion of entries due to rehashing. + /// + sealed class Container + { + + readonly WeakHashTable _parent; // the ConditionalWeakTable with which this container is associated + int[] _buckets; // _buckets[hashcode & (_buckets.Length - 1)] contains index of the first entry in bucket (-1 if empty) + Entry[] _entries; // the table entries containing the stored dependency handles + int _firstFreeEntry; // _firstFreeEntry < _entries.Length => table has capacity, entries grow from the bottom of the table. + bool _invalid; // flag detects if OOM or other background exception threw us out of the lock. + bool _finalized; // set to true when initially finalized + volatile object? _oldKeepAlive; // used to ensure the next allocated container isn't finalized until this one is GC'd + + /// + /// Initializes a new instance. + /// + /// + internal Container(WeakHashTable parent) + { + Debug.Assert(parent != null); +#if NET + Debug.Assert(BitOperations.IsPow2(InitialCapacity)); +#endif + + const int Size = InitialCapacity; + + _buckets = new int[Size]; + for (int i = 0; i < _buckets.Length; i++) + _buckets[i] = -1; + + _entries = new Entry[Size]; + + // Only store the parent after all of the allocations have happened successfully. + // Otherwise, as part of growing or clearing the container, we could end up allocating + // a new Container that fails (OOMs) part way through construction but that gets finalized + // and ends up clearing out some other container present in the associated CWT. + _parent = parent; + } + + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + Container(WeakHashTable parent, int[] buckets, Entry[] entries, int firstFreeEntry) + { + Debug.Assert(parent != null); + Debug.Assert(buckets != null); + Debug.Assert(entries != null); + Debug.Assert(buckets.Length == entries.Length); +#if NET + Debug.Assert(BitOperations.IsPow2(buckets.Length)); +#endif + + _parent = parent; + _buckets = buckets; + _entries = entries; + _firstFreeEntry = firstFreeEntry; + } + + /// + /// Returns true if the container has free capacity. + /// + internal bool HasCapacity => _firstFreeEntry < _entries.Length; + + /// + /// Returns the first free entry index. + /// + internal int FirstFreeEntry => _firstFreeEntry; + + /// + /// Worker for adding a new key/value pair. Container must NOT be full. + /// + internal void CreateEntryNoResize(TKey key, TValue value) + { + Debug.Assert(key != null); // key already validated as non-null and not already in table. + Debug.Assert(HasCapacity); + + VerifyIntegrity(); + _invalid = true; + + int hashCode = _parent._comparer.GetHashCode(key) & int.MaxValue; + int newEntry = _firstFreeEntry++; + + _entries[newEntry].HashCode = hashCode; + _entries[newEntry].Handle = new(value, key); + int bucket = hashCode & (_buckets.Length - 1); + _entries[newEntry].Next = _buckets[bucket]; + + // This write must be volatile, as we may be racing with concurrent readers. If they see + // the new entry, they must also see all of the writes earlier in this method. + Volatile.Write(ref _buckets[bucket], newEntry); + + _invalid = false; + } + + /// + /// Worker for finding a key/value pair. Must hold _lock. + /// + internal bool TryGetValueWorker(TKey key, [MaybeNullWhen(false)] out TValue value) + { + Debug.Assert(key != null); // Key already validated as non-null + + int entryIndex = FindEntry(key, out TValue? secondary); + value = Unsafe.As(secondary); + return entryIndex != -1; + } + + /// + /// Returns -1 if not found (if key expires during FindEntry, this can be treated as "not found."). + /// Must hold _lock, or be prepared to retry the search while holding _lock. + /// + /// This method requires to be on the stack to be properly tracked. + internal int FindEntry(TKey key, out TValue? value) + { + Debug.Assert(key != null); + + int hashCode = _parent._comparer.GetHashCode(key); + if (hashCode == 0) + { + value = null; + return -1; + } + + hashCode &= int.MaxValue; + int bucket = hashCode & (_buckets.Length - 1); + for (int entriesIndex = Volatile.Read(ref _buckets[bucket]); entriesIndex != -1; entriesIndex = _entries[entriesIndex].Next) + { + if (_entries[entriesIndex].HashCode == hashCode) + { + var (target, dependent) = _entries[entriesIndex].Handle.TargetAndDependent; + if (_parent._comparer.Equals(dependent!, key)) + { + value = target; + + GC.KeepAlive(this); // ensure we don't get finalized while accessing DependentHandle + return entriesIndex; + } + } + } + + GC.KeepAlive(this); // ensure we don't get finalized while accessing DependentHandle + value = null; + return -1; + } + + /// + /// Gets the entry at the specified entry index. + /// + internal bool TryGetEntry(int index, [NotNullWhen(true)] out TKey? key, [MaybeNullWhen(false)] out TValue? value) + { + if (index < _entries.Length) + { + var (target, dependent) = _entries[index].Handle.TargetAndDependent; + GC.KeepAlive(this); // ensure we don't get finalized while accessing DependentHandle + + if (dependent != null) + { + key = dependent; + value = target; + return true; + } + } + + key = default; + value = default; + return false; + } + + /// + /// Removes all of the keys in the table. + /// + internal void RemoveAllKeys() + { + for (int i = 0; i < _firstFreeEntry; i++) + RemoveIndex(i); + } + + /// + /// Removes the specified key from the table, if it exists. + /// + internal bool Remove(TKey key) + { + VerifyIntegrity(); + + int entryIndex = FindEntry(key, out _); + if (entryIndex != -1) + { + RemoveIndex(entryIndex); + return true; + } + + return false; + } + + /// + /// Removes the entry at the specified index. + /// + /// + void RemoveIndex(int entryIndex) + { + Debug.Assert(entryIndex >= 0 && entryIndex < _firstFreeEntry); + + ref Entry entry = ref _entries[entryIndex]; + + // We do not free the handle here, as we may be racing with readers who already saw the hash code. + // Instead, we simply overwrite the entry's hash code, so subsequent reads will ignore it. + // The handle will be free'd in Container's finalizer, after the table is resized or discarded. + Volatile.Write(ref entry.HashCode, -1); + + // Also, clear the value to allow GC to collect objects pointed to by the entry + entry.Handle.Target = null; + } + + internal void UpdateValue(int entryIndex, TValue newValue) + { + Debug.Assert(entryIndex != -1); + + VerifyIntegrity(); + _invalid = true; + + _entries[entryIndex].Handle.Target = newValue; + + _invalid = false; + } + + /// Resize, and scrub expired keys off bucket lists. Must hold _lock. + /// + /// _firstEntry is less than _entries.Length on exit, that is, the table has at least one free entry. + /// + internal Container Resize() + { + Debug.Assert(!HasCapacity); + + bool hasExpiredEntries = false; + int newSize = _buckets.Length; + + if (_parent is null || _parent._activeEnumeratorRefCount == 0) + { + // If any expired or removed keys exist, we won't resize. + // If there any active enumerators, though, we don't want + // to compact and thus have no expired entries. + for (int entriesIndex = 0; entriesIndex < _entries.Length; entriesIndex++) + { + ref Entry entry = ref _entries[entriesIndex]; + + if (entry.HashCode == -1) + { + // the entry was removed + hasExpiredEntries = true; + break; + } + + if (entry.Handle.IsAllocated && entry.Handle.Target is null) + { + // the entry has expired + hasExpiredEntries = true; + break; + } + } + } + + if (!hasExpiredEntries) + { + // Not necessary to check for overflow here, the attempt to allocate new arrays will throw + newSize = _buckets.Length * 2; + } + + return Resize(newSize); + } + + internal Container Resize(int newSize) + { + Debug.Assert(newSize >= _buckets.Length); +#if NET + Debug.Assert(BitOperations.IsPow2(newSize)); +#endif + + // Reallocate both buckets and entries and rebuild the bucket and entries from scratch. + // This serves both to scrub entries with expired keys and to put the new entries in the proper bucket. + var newBuckets = new int[newSize]; + for (int bucketIndex = 0; bucketIndex < newBuckets.Length; bucketIndex++) + newBuckets[bucketIndex] = -1; + + var newEntries = new Entry[newSize]; + var newEntriesIndex = 0; + var activeEnumerators = _parent != null && _parent._activeEnumeratorRefCount > 0; + + // Migrate existing entries to the new table. + if (activeEnumerators) + { + // There's at least one active enumerator, which means we don't want to + // remove any expired/removed entries, in order to not affect existing + // entries indices. Copy over the entries while rebuilding the buckets list, + // as the buckets are dependent on the buckets list length, which is changing. + for (; newEntriesIndex < _entries.Length; newEntriesIndex++) + { + ref var oldEntry = ref _entries[newEntriesIndex]; + ref var newEntry = ref newEntries[newEntriesIndex]; + int hashCode = oldEntry.HashCode; + int bucket = hashCode & (newBuckets.Length - 1); + + newEntry.HashCode = hashCode; + newEntry.Handle = oldEntry.Handle; + newEntry.Next = newBuckets[bucket]; + newBuckets[bucket] = newEntriesIndex; + } + } + else + { + // There are no active enumerators, which means we want to compact by + // removing expired/removed entries. + for (int entriesIndex = 0; entriesIndex < _entries.Length; entriesIndex++) + { + ref var oldEntry = ref _entries[entriesIndex]; + int hashCode = oldEntry.HashCode; + var handle = oldEntry.Handle; + if (hashCode != -1 && handle.IsAllocated) + { + if (handle.Target is not null) + { + ref var newEntry = ref newEntries[newEntriesIndex]; + int bucket = hashCode & (newBuckets.Length - 1); + + // Entry is used and has not expired. Link it into the appropriate bucket list. + newEntry.HashCode = hashCode; + newEntry.Handle = handle; + newEntry.Next = newBuckets[bucket]; + newBuckets[bucket] = newEntriesIndex; + newEntriesIndex++; + } + else + { + // Pretend the item was removed, so that this container's finalizer + // will clean up this dependent handle. + Volatile.Write(ref oldEntry.HashCode, -1); + } + } + } + } + + // Create the new container. We want to transfer the responsibility of freeing the handles from + // the old container to the new container, and also ensure that the new container isn't finalized + // while the old container may still be in use. As such, we store a reference from the old container + // to the new one, which will keep the new container alive as long as the old one is. + var newContainer = new Container(_parent!, newBuckets, newEntries, newEntriesIndex); + if (activeEnumerators) + { + // If there are active enumerators, both the old container and the new container may be storing + // the same entries with -1 hash codes, which the finalizer will clean up even if the container + // is not the active container for the table. To prevent that, we want to stop the old container + // from being finalized, as it no longer has any responsibility for any cleanup. + GC.SuppressFinalize(this); + } + + _oldKeepAlive = newContainer; // once this is set, the old container's finalizer will not free transferred dependent handles + + GC.KeepAlive(this); // ensure we don't get finalized while accessing DependentHandles. + + return newContainer; + } + + /// + /// Verifies the integrity of the container. + /// + /// + void VerifyIntegrity() + { + if (_invalid) + throw new InvalidOperationException("Collection corrupted."); + } + + /// + /// Finalizes the instance. + /// + ~Container() + { + // Skip doing anything if the container is invalid, including if somehow + // the container object was allocated but its associated table never set. + if (_invalid || _parent is null) + return; + + // It's possible that the ConditionalWeakTable could have been resurrected, in which case code could + // be accessing this Container as it's being finalized. We don't support usage after finalization, + // but we also don't want to potentially corrupt state by allowing dependency handles to be used as + // or after they've been freed. To avoid that, if it's at all possible that another thread has a + // reference to this container via the CWT, we remove such a reference and then re-register for + // finalization: the next time around, we can be sure that no references remain to this and we can + // clean up the dependency handles without fear of corruption. + if (!_finalized) + { + _finalized = true; + + lock (_parent._lock) + if (_parent._container == this) + _parent._container = null!; + + GC.ReRegisterForFinalize(this); // next time it's finalized, we'll be sure there are no remaining refs + return; + } + + var entries = _entries; + _invalid = true; + _entries = null!; + _buckets = null!; + + if (entries != null) + { + for (int entriesIndex = 0; entriesIndex < entries.Length; entriesIndex++) + { + // We need to free handles in two cases: + // - If this container still owns the dependency handle (meaning ownership hasn't been transferred + // to another container that replaced this one), then it should be freed. + // - If this container had the entry removed, then even if in general ownership was transferred to + // another container, removed entries are not, therefore this container must free them. + if (_oldKeepAlive is null || entries[entriesIndex].HashCode == -1) + { + entries[entriesIndex].Handle.Dispose(); + } + } + } + } + + } + + } + +} \ No newline at end of file diff --git a/src/IKVM.CoreLib/Diagnostics/DiagnosticEventException.cs b/src/IKVM.CoreLib/Diagnostics/DiagnosticEventException.cs new file mode 100644 index 0000000000..bf64df0b37 --- /dev/null +++ b/src/IKVM.CoreLib/Diagnostics/DiagnosticEventException.cs @@ -0,0 +1,51 @@ +using System; + +namespace IKVM.CoreLib.Diagnostics +{ + + sealed class DiagnosticEventException : Exception + { + + /// + /// Returns the output text for the given . + /// + /// + /// + /// + static string FormatDiagnosticLevel(DiagnosticLevel level) + { + return level switch + { + DiagnosticLevel.Trace => "trace", + DiagnosticLevel.Info => "info", + DiagnosticLevel.Warning => "warning", + DiagnosticLevel.Error => "error", + DiagnosticLevel.Fatal => "fatal", + _ => throw new InvalidOperationException(), + }; + } + + readonly DiagnosticEvent _event; + + /// + /// Initializes a new instance. + /// + /// + internal DiagnosticEventException(in DiagnosticEvent evt) : +#if NET8_0_OR_GREATER + base($"{FormatDiagnosticLevel(evt.Diagnostic.Level)} IKVM{evt.Diagnostic.Id:D4}: {string.Format(null, evt.Diagnostic.Message, evt.Args)}") +#else + base($"{FormatDiagnosticLevel(evt.Diagnostic.Level)} IKVM{evt.Diagnostic.Id:D4}: {string.Format(null, evt.Diagnostic.Message, evt.Args)}") +#endif + { + _event = evt; + } + + /// + /// Gets the event that triggered this exception. + /// + public DiagnosticEvent Event => _event; + + } + +} diff --git a/src/IKVM.CoreLib/Diagnostics/JsonDiagnosticFormat.cs b/src/IKVM.CoreLib/Diagnostics/JsonDiagnosticFormat.cs index 627a8959f6..d0781a8e51 100644 --- a/src/IKVM.CoreLib/Diagnostics/JsonDiagnosticFormat.cs +++ b/src/IKVM.CoreLib/Diagnostics/JsonDiagnosticFormat.cs @@ -4,6 +4,7 @@ using System.Text.Json; using IKVM.CoreLib.Buffers; +using IKVM.Text; namespace IKVM.CoreLib.Diagnostics { diff --git a/src/IKVM.CoreLib/System/HashCodeExtensions.cs b/src/IKVM.CoreLib/HashCodeExtensions.cs similarity index 96% rename from src/IKVM.CoreLib/System/HashCodeExtensions.cs rename to src/IKVM.CoreLib/HashCodeExtensions.cs index ea7f26aab7..31693ef501 100644 --- a/src/IKVM.CoreLib/System/HashCodeExtensions.cs +++ b/src/IKVM.CoreLib/HashCodeExtensions.cs @@ -3,7 +3,7 @@ using System.Collections.Immutable; using System.Linq; -namespace IKVM.CoreLib.System +namespace IKVM.CoreLib { internal static class HashCodeExtensions @@ -104,7 +104,7 @@ public static void AddRange(this ref HashCode self, ImmutableHashSet items /// public static void AddRange(this ref HashCode self, ImmutableHashSet items) { - AddRange(ref self, items, Comparer.Default); + self.AddRange(items, Comparer.Default); } /// @@ -142,7 +142,7 @@ public static void AddRange(this ref HashCode self, ISet items, IComparer< /// public static void AddRange(this ref HashCode self, ISet items) { - AddRange(ref self, items, Comparer.Default); + self.AddRange(items, Comparer.Default); } } diff --git a/src/IKVM.CoreLib/System/HexConverter.cs b/src/IKVM.CoreLib/HexConverter.cs similarity index 99% rename from src/IKVM.CoreLib/System/HexConverter.cs rename to src/IKVM.CoreLib/HexConverter.cs index 572f1fb00b..69cdfa7ec9 100644 --- a/src/IKVM.CoreLib/System/HexConverter.cs +++ b/src/IKVM.CoreLib/HexConverter.cs @@ -1,11 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -namespace System +namespace IKVM.CoreLib { internal static class HexConverter @@ -170,7 +171,7 @@ public static void EncodeToUtf16(ReadOnlySpan bytes, Span chars, Cas /// /// Delgate instance for encoding a string. /// - static readonly unsafe SpanAction EncodeToUtf16Action = (Span chars, (nint ptr, int len, Casing cas) args) => + static readonly unsafe SpanAction EncodeToUtf16Action = (chars, args) => { EncodeToUtf16(new ReadOnlySpan((void*)args.ptr, args.len), chars, args.cas); }; diff --git a/src/IKVM.CoreLib/IkvmReflection/IkvmReflectionExtensions.cs b/src/IKVM.CoreLib/IkvmReflection/IkvmReflectionExtensions.cs new file mode 100644 index 0000000000..57fcc97302 --- /dev/null +++ b/src/IKVM.CoreLib/IkvmReflection/IkvmReflectionExtensions.cs @@ -0,0 +1,26 @@ +using IKVM.Reflection; + +namespace IKVM.CoreLib.IkvmReflection +{ + + public static class IkvmReflectionExtensions + { + + /// + /// Gets the parameters types of the specified method or constructor. + /// + /// + /// + public static Type[] GetParameterTypes(this MethodBase method) + { + var p = method.GetParameters(); + var a = p.Length > 0 ? new Type[p.Length] : []; + for (int i = 0; i < p.Length; i++) + a[i] = p[i].ParameterType; + + return a; + } + + } + +} diff --git a/src/IKVM.CoreLib/Modules/ModuleDescriptor.cs b/src/IKVM.CoreLib/Modules/ModuleDescriptor.cs index fb1e98ee8b..218f03d83a 100644 --- a/src/IKVM.CoreLib/Modules/ModuleDescriptor.cs +++ b/src/IKVM.CoreLib/Modules/ModuleDescriptor.cs @@ -7,7 +7,7 @@ using IKVM.ByteCode; using IKVM.ByteCode.Decoding; -using IKVM.CoreLib.System; +using IKVM.CoreLib.Collections; namespace IKVM.CoreLib.Modules { diff --git a/src/IKVM.CoreLib/Modules/ModuleExports.cs b/src/IKVM.CoreLib/Modules/ModuleExports.cs index af2bf814d1..961e1a7f2f 100644 --- a/src/IKVM.CoreLib/Modules/ModuleExports.cs +++ b/src/IKVM.CoreLib/Modules/ModuleExports.cs @@ -3,7 +3,7 @@ using System.Linq; using IKVM.ByteCode; -using IKVM.CoreLib.System; +using IKVM.CoreLib.Collections; namespace IKVM.CoreLib.Modules { diff --git a/src/IKVM.CoreLib/Modules/ModuleOpens.cs b/src/IKVM.CoreLib/Modules/ModuleOpens.cs index df44632043..3591b07f06 100644 --- a/src/IKVM.CoreLib/Modules/ModuleOpens.cs +++ b/src/IKVM.CoreLib/Modules/ModuleOpens.cs @@ -3,7 +3,7 @@ using System.Linq; using IKVM.ByteCode; -using IKVM.CoreLib.System; +using IKVM.CoreLib.Collections; namespace IKVM.CoreLib.Modules { diff --git a/src/IKVM.CoreLib/Modules/ModuleProvides.cs b/src/IKVM.CoreLib/Modules/ModuleProvides.cs index 212eb329ab..0ecad66caa 100644 --- a/src/IKVM.CoreLib/Modules/ModuleProvides.cs +++ b/src/IKVM.CoreLib/Modules/ModuleProvides.cs @@ -2,7 +2,7 @@ using System.Collections.Immutable; using System.Linq; -using IKVM.CoreLib.System; +using IKVM.CoreLib.Collections; namespace IKVM.CoreLib.Modules { diff --git a/src/IKVM.CoreLib/Modules/ModuleVersion.cs b/src/IKVM.CoreLib/Modules/ModuleVersion.cs index d729f13386..9d024a267e 100644 --- a/src/IKVM.CoreLib/Modules/ModuleVersion.cs +++ b/src/IKVM.CoreLib/Modules/ModuleVersion.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Runtime.CompilerServices; -using IKVM.CoreLib.System; +using IKVM.CoreLib.Collections; namespace IKVM.CoreLib.Modules { diff --git a/src/IKVM.CoreLib/Reflection/AssemblyNameEqualityComparer.cs b/src/IKVM.CoreLib/Reflection/AssemblyNameEqualityComparer.cs new file mode 100644 index 0000000000..fe1a16f44e --- /dev/null +++ b/src/IKVM.CoreLib/Reflection/AssemblyNameEqualityComparer.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace IKVM.CoreLib.Reflection +{ + + class AssemblyNameEqualityComparer : IEqualityComparer + { + + /// + /// Gets the default instance of this comparer. + /// + public static readonly AssemblyNameEqualityComparer Instance = new(); + + /// + /// Initializes a new instance. + /// + public AssemblyNameEqualityComparer() + { + + } + + + /// + public bool Equals(AssemblyName? x, AssemblyName? y) + { + // this expects non-null AssemblyName + if (x == null || y == null) + return false; + + if (ReferenceEquals(x, y)) + return true; + + if (x.Name != null && y.Name != null) + { + if (string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) != 0) + return false; + } + else if (!(x.Name == null && y.Name == null)) + { + return false; + } + + if (x.Version != null && y.Version != null) + { + if (x.Version != y.Version) + return false; + } + else if (!(x.Version == null && y.Version == null)) + { + return false; + } + + if (x.CultureInfo != null && y.CultureInfo != null) + { + if (!x.CultureInfo.Equals(y.CultureInfo)) + return false; + } + else if (!(x.CultureInfo == null && y.CultureInfo == null)) + { + return false; + } + + var xArray = x.GetPublicKeyToken(); + var yArray = y.GetPublicKeyToken(); + if (!IsSameKeyToken(xArray, yArray)) + return false; + + return true; + } + + /// + public int GetHashCode(AssemblyName obj) + { + int hashcode = 0; + + if (obj.Name != null) + hashcode ^= obj.Name.GetHashCode(); + + if (obj.Version != null) + hashcode ^= obj.Version.GetHashCode(); + + if (obj.CultureInfo != null) + hashcode ^= obj.CultureInfo.GetHashCode(); + + var objArray = obj.GetPublicKeyToken(); + if (objArray != null) + { + // distinguishing no PKToken from "PKToken = null" which is an array of length=0 + hashcode ^= objArray.Length.GetHashCode() + 1; + if (objArray.Length > 0) + hashcode ^= BitConverter.ToUInt64(objArray, 0).GetHashCode(); + } + + return hashcode; + } + + static bool IsSameKeyToken(byte[]? reqKeyToken, byte[]? curKeyToken) + { + bool isSame = false; + + if (reqKeyToken == null && curKeyToken == null) + { + // Both Key Tokens are not set, treat them as same. + isSame = true; + } + else if (reqKeyToken != null && curKeyToken != null) + { + // Both KeyTokens are set. + if (reqKeyToken.Length == curKeyToken.Length) + { + isSame = true; + for (int i = 0; i < reqKeyToken.Length; i++) + { + if (reqKeyToken[i] != curKeyToken[i]) + { + isSame = false; + break; + } + } + } + } + + return isSame; + } + + } + +} diff --git a/src/IKVM.CoreLib/Reflection/ReflectionExtensions.cs b/src/IKVM.CoreLib/Reflection/ReflectionExtensions.cs new file mode 100644 index 0000000000..4539596760 --- /dev/null +++ b/src/IKVM.CoreLib/Reflection/ReflectionExtensions.cs @@ -0,0 +1,838 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; +using System.Reflection.Metadata.Ecma335; + +namespace IKVM.CoreLib.Reflection +{ + + static class ReflectionExtensions + { + + static readonly ParameterExpression _constructorInfoParameter = Expression.Parameter(typeof(ConstructorInfo), "p"); + static readonly ParameterExpression _methodInfoParameter = Expression.Parameter(typeof(MethodInfo), "p"); + static readonly ParameterExpression _fieldInfoParameter = Expression.Parameter(typeof(FieldInfo), "p"); + + static readonly ParameterExpression _assemblyBuilderRuntimeAssemblyParameter = Expression.Parameter(typeof(AssemblyBuilder), "p"); + static readonly ParameterExpression _propertyBuilderParameter = Expression.Parameter(typeof(PropertyBuilder), "p"); + static readonly ParameterExpression _eventBuilderParameter = Expression.Parameter(typeof(EventBuilder), "p"); + static readonly ParameterExpression _parameterBuilderParameter = Expression.Parameter(typeof(ParameterBuilder), "p"); + + static readonly Type _methodBuilderInstantiationType = typeof(TypeBuilder).Assembly.GetType("System.Reflection.Emit.MethodBuilderInstantiation", true)!; + static readonly Type _constructorOnTypeBuilderInstantiationType = typeof(TypeBuilder).Assembly.GetType("System.Reflection.Emit.ConstructorOnTypeBuilderInstantiation", true)!; + static readonly Type _methodOnTypeBuilderInstantiationType = typeof(TypeBuilder).Assembly.GetType("System.Reflection.Emit.MethodOnTypeBuilderInstantiation", true)!; + static readonly Type _fieldOnTypeBuilderInstantiationType = typeof(TypeBuilder).Assembly.GetType("System.Reflection.Emit.FieldOnTypeBuilderInstantiation", true)!; + +#if NET + +#if NET8_0_OR_GREATER + + static readonly Type _assemblyBuilderType = typeof(AssemblyBuilder).Assembly.GetType("System.Reflection.Emit.RuntimeAssemblyBuilder", true)!; + static readonly Type _propertyBuilderType = typeof(PropertyBuilder).Assembly.GetType("System.Reflection.Emit.RuntimePropertyBuilder", true)!; + static readonly Type _eventBuilderType = typeof(EventBuilder).Assembly.GetType("System.Reflection.Emit.RuntimeEventBuilder", true)!; + static readonly Type _parameterBuilderType = typeof(ParameterBuilder).Assembly.GetType("System.Reflection.Emit.RuntimeParameterBuilder", true)!; + + static readonly Func _getConstructorOnTypeBuilderInstantiationConstructorFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_constructorInfoParameter, _constructorOnTypeBuilderInstantiationType), + _constructorOnTypeBuilderInstantiationType.GetField("_ctor", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _constructorInfoParameter) + .Compile(); + + static readonly Func _getMethodOnTypeBuilderInstantiationMethodFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_methodInfoParameter, _methodOnTypeBuilderInstantiationType), + _methodOnTypeBuilderInstantiationType.GetField("_method", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _methodInfoParameter) + .Compile(); + + static readonly Func _getFieldOnTypeBuilderInstantiationFieldFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_fieldInfoParameter, _fieldOnTypeBuilderInstantiationType), + _fieldOnTypeBuilderInstantiationType.GetField("_field", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _fieldInfoParameter) + .Compile(); + +#else + + static readonly Type _assemblyBuilderType = typeof(AssemblyBuilder).Assembly.GetType("System.Reflection.Emit.AssemblyBuilder", true)!; + static readonly Type _propertyBuilderType = typeof(PropertyBuilder).Assembly.GetType("System.Reflection.Emit.PropertyBuilder", true)!; + static readonly Type _eventBuilderType = typeof(EventBuilder).Assembly.GetType("System.Reflection.Emit.EventBuilder", true)!; + static readonly Type _parameterBuilderType = typeof(ParameterBuilder).Assembly.GetType("System.Reflection.Emit.ParameterBuilder", true)!; + + static readonly Func _getConstructorOnTypeBuilderInstantiationConstructorFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_constructorInfoParameter, _constructorOnTypeBuilderInstantiationType), + _constructorOnTypeBuilderInstantiationType.GetField("m_ctor", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _constructorInfoParameter) + .Compile(); + + static readonly Func _getMethodOnTypeBuilderInstantiationMethodFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_methodInfoParameter, _methodOnTypeBuilderInstantiationType), + _methodOnTypeBuilderInstantiationType.GetField("m_method", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _methodInfoParameter) + .Compile(); + + static readonly Func _getFieldOnTypeBuilderInstantiationFieldFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_fieldInfoParameter, _fieldOnTypeBuilderInstantiationType), + _fieldOnTypeBuilderInstantiationType.GetField("m_field", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _fieldInfoParameter) + .Compile(); + +#endif + + static readonly Func _getAssemblyBuilderRuntimeAssemblyFunc = Expression.Lambda>( + Expression.Property( + Expression.ConvertChecked(_assemblyBuilderRuntimeAssemblyParameter, _assemblyBuilderType), + _assemblyBuilderType.GetProperty("InternalAssembly", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _assemblyBuilderRuntimeAssemblyParameter) + .Compile(); + + static readonly Func _getPropertyMetadataTokenFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_propertyBuilderParameter, _propertyBuilderType), + _propertyBuilderType.GetField("m_tkProperty", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _propertyBuilderParameter) + .Compile(); + + static readonly Func _getEventMetadataTokenFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_eventBuilderParameter, _eventBuilderType), + _eventBuilderType.GetField("m_evToken", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _eventBuilderParameter) + .Compile(); + + static readonly Func _getParameterMethodBuilderFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_parameterBuilderParameter, _parameterBuilderType), + _parameterBuilderType.GetField("_methodBuilder", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _parameterBuilderParameter) + .Compile(); + + static readonly Func _getParameterMetadataTokenFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_parameterBuilderParameter, _parameterBuilderType), + _parameterBuilderType.GetField("_token", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _parameterBuilderParameter) + .Compile(); + +#else + + static readonly Type _assemblyBuilderType = typeof(AssemblyBuilder).Assembly.GetType("System.Reflection.Emit.AssemblyBuilder", true)!; + static readonly Type _eventBuilderType = typeof(EventBuilder).Assembly.GetType("System.Reflection.Emit.EventBuilder", true)!; + static readonly Type _parameterBuilderType = typeof(ParameterBuilder).Assembly.GetType("System.Reflection.Emit.ParameterBuilder", true)!; + + static readonly Func _getConstructorOnTypeBuilderInstantiationConstructorFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_constructorInfoParameter, _constructorOnTypeBuilderInstantiationType), + _constructorOnTypeBuilderInstantiationType.GetField("m_ctor", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _constructorInfoParameter) + .Compile(); + + static readonly Func _getMethodOnTypeBuilderInstantiationMethodFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_methodInfoParameter, _methodOnTypeBuilderInstantiationType), + _methodOnTypeBuilderInstantiationType.GetField("m_method", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _methodInfoParameter) + .Compile(); + + static readonly Func _getFieldOnTypeBuilderInstantiationFieldFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_fieldInfoParameter, _fieldOnTypeBuilderInstantiationType), + _fieldOnTypeBuilderInstantiationType.GetField("m_field", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _fieldInfoParameter) + .Compile(); + + static readonly Func _getAssemblyBuilderRuntimeAssemblyFunc = Expression.Lambda>( + Expression.Call( + Expression.ConvertChecked(_assemblyBuilderRuntimeAssemblyParameter, _assemblyBuilderType), + _assemblyBuilderType.GetMethod("GetNativeHandle", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _assemblyBuilderRuntimeAssemblyParameter) + .Compile(); + + static readonly Func _getParameterMethodBuilderFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_parameterBuilderParameter, _parameterBuilderType), + _parameterBuilderType.GetField("m_methodBuilder", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _parameterBuilderParameter) + .Compile(); + +#endif + + static readonly Func _getEventModuleBuilderFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_eventBuilderParameter, _eventBuilderType), + _eventBuilderType.GetField("m_module", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _eventBuilderParameter) + .Compile(); + + static readonly Func _getEventTypeBuilderFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_eventBuilderParameter, _eventBuilderType), + _eventBuilderType.GetField("m_type", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _eventBuilderParameter) + .Compile(); + + static readonly Func _getEventNameFunc = Expression.Lambda>( + Expression.Field( + Expression.ConvertChecked(_eventBuilderParameter, _eventBuilderType), + _eventBuilderType.GetField("m_name", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + _eventBuilderParameter) + .Compile(); + + static readonly Func _getEventAttributesFunc = Expression.Lambda>( + Expression.ConvertChecked( + Expression.Field( + Expression.ConvertChecked(_eventBuilderParameter, _eventBuilderType), + _eventBuilderType.GetField("m_attributes", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException()), + typeof(EventAttributes)), + _eventBuilderParameter) + .Compile(); + + /// + /// Gets the type. + /// + public static Type MethodBuilderInstantiationType => _methodBuilderInstantiationType; + + /// + /// Gets the type. + /// + public static Type MethodOnTypeBuilderInstantiationType => _methodOnTypeBuilderInstantiationType; + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + public static int GetMetadataTokenSafe(this Module module) + { + var t = module.MetadataToken; + if (t == 0) + throw new InvalidOperationException(); + + return t; + } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this Module module) + { + return MetadataTokens.GetRowNumber(MetadataTokens.EntityHandle(module.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + public static int GetMetadataTokenSafe(this MemberInfo member) + { + return member switch + { + Type t => t.GetMetadataTokenSafe(), + MethodBase m => m.GetMetadataTokenSafe(), + FieldInfo f => f.GetMetadataTokenSafe(), + PropertyInfo p => p.GetMetadataTokenSafe(), + EventInfo e => e.GetMetadataTokenSafe(), + _ => throw new InvalidOperationException(), + }; + } + +// /// +// /// Gets the metadata token for the specified . +// /// +// /// +// /// +// public static int GetMetadataTokenSafe(this Type type) +// { +//#if NETFRAMEWORK +// if (type is TypeBuilder b) +// { +// var t = b.TypeToken.Token; +// if (t == 0) +// throw new InvalidOperationException(); + +// return t; +// } +//#endif + +// return type.GetMetadataToken(); +// } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this Type type) + { + return MetadataTokens.GetRowNumber(MetadataTokens.TypeDefinitionHandle(type.GetMetadataTokenSafe())); + } + +// /// +// /// Gets the metadata token for the specified . +// /// +// /// +// /// +// public static int GetMetadataTokenSafe(this FieldInfo field) +// { +//#if NETFRAMEWORK +// if (field is FieldBuilder b) +// { +// var t = b.GetToken().Token; +// if (t == 0) +// throw new InvalidOperationException(); + +// return t; +// } +//#endif + +//#if NET8_0_OR_GREATER || NETFRAMEWORK +// // field is instance of FieldOnTypeBuilderInstantiation +// if (_fieldOnTypeBuilderInstantiationType.IsInstanceOfType(field)) +// { +// var f = _getFieldOnTypeBuilderInstantiationFieldFunc(field); +// return f.GetMetadataTokenSafe(); +// } +//#endif + +// return field.GetMetadataToken(); +// } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this FieldInfo field) + { + return MetadataTokens.GetRowNumber(MetadataTokens.FieldDefinitionHandle(field.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + /// + public static int GetMetadataTokenSafe(this MethodBase method) + { + return method switch + { + ConstructorInfo c => c.GetMetadataTokenSafe(), + MethodInfo m => m.GetMetadataTokenSafe(), + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this MethodBase method) + { + return method switch + { + ConstructorInfo c => c.GetMetadataTokenRowNumberSafe(), + MethodInfo m => m.GetMetadataTokenRowNumberSafe(), + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// +// public static int GetMetadataTokenSafe(this ConstructorInfo ctor) +// { +//#if NETFRAMEWORK +// if (ctor is ConstructorBuilder b) +// { +// var t = b.GetToken().Token; +// if (t == 0) +// throw new InvalidOperationException(); + +// return t; +// } +//#endif + +//#if NET8_0_OR_GREATER || NETFRAMEWORK +// // ctor is instance of ConstructorOnTypeBuilderInstantiation +// if (_constructorOnTypeBuilderInstantiationType.IsInstanceOfType(ctor)) +// { +// var c = _getConstructorOnTypeBuilderInstantiationConstructorFunc(ctor); +// return c.GetMetadataTokenSafe(); +// } +//#endif + +// return ctor.GetMetadataToken(); +// } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this ConstructorInfo ctor) + { + return MetadataTokens.GetRowNumber(MetadataTokens.MethodDefinitionHandle(ctor.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// +// public static int GetMetadataTokenSafe(this MethodInfo method) +// { +//#if NETFRAMEWORK +// if (method is MethodBuilder b) +// { +// var t = b.GetToken().Token; +// if (t == 0) +// throw new InvalidOperationException(); + +// return t; +// } +//#endif + +//#if NET8_0_OR_GREATER || NETFRAMEWORK +// // method is instance of MethodOnTypeBuilderInstantiation +// if (_methodOnTypeBuilderInstantiationType.IsInstanceOfType(method)) +// { +// var m = _getMethodOnTypeBuilderInstantiationMethodFunc(method); +// return m.GetMetadataTokenSafe(); +// } +//#endif + +//#if NET6_0 +// // method is instance of MethodOnTypeBuilderInstantiation +// if (_methodOnTypeBuilderInstantiationType.IsInstanceOfType(method)) +// { +// var m = _getMethodOnTypeBuilderInstantiationMethodFunc(method); +// return m.GetMetadataTokenSafe(); +// } +//#endif + +// return method.GetMetadataToken(); +// } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this MethodInfo method) + { + return MetadataTokens.GetRowNumber(MetadataTokens.MethodDefinitionHandle(method.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + /// +// public static int GetMetadataTokenSafe(this PropertyInfo property) +// { +// if (property is PropertyBuilder b) +// { +//#if NETFRAMEWORK +// var t = b.PropertyToken.Token; +//#else +// var t = _getPropertyMetadataTokenFunc(b); +//#endif +// if (t == 0) +// throw new InvalidOperationException(); + +// return t; +// } + +// return property.GetMetadataToken(); +// } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this PropertyInfo property) + { + return MetadataTokens.GetRowNumber(MetadataTokens.PropertyDefinitionHandle(property.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + /// + //public static int GetMetadataTokenSafe(this EventInfo @event) + //{ + // return @event.GetMetadataToken(); + //} + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this EventInfo @event) + { + return MetadataTokens.GetRowNumber(MetadataTokens.EventDefinitionHandle(@event.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + /// + /// + public static int GetMetadataToken(this EventBuilder @event) + { +#if NETFRAMEWORK + return @event.GetEventToken().Token; +#else + return _getEventMetadataTokenFunc(@event); +#endif + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + /// + /// + public static int GetMetadataTokenSafe(this EventBuilder @event) + { + var t = @event.GetMetadataToken(); + if (t == 0) + throw new InvalidOperationException(); + + return t; + } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this EventBuilder @event) + { + return MetadataTokens.GetRowNumber(MetadataTokens.EventDefinitionHandle(@event.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + /// + public static int GetMetadataTokenSafe(this ParameterInfo parameter) + { + var t = parameter.MetadataToken; + if (t == 0) + throw new InvalidOperationException(); + + return t; + } + + /// + /// Gets the metadata row number for the specified . + /// + /// + /// + public static int GetMetadataTokenRowNumberSafe(this ParameterInfo parameter) + { + return MetadataTokens.GetRowNumber(MetadataTokens.ParameterHandle(parameter.GetMetadataTokenSafe())); + } + + /// + /// Gets the metadata token for the specified . + /// + /// + /// + /// + /// + public static int GetMetadataToken(this ParameterBuilder parameter) + { +#if NETFRAMEWORK + return parameter.GetToken().Token; +#else + return _getParameterMetadataTokenFunc(parameter); +#endif + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static Assembly GetRuntimeAssembly(this AssemblyBuilder assembly) + { + return _getAssemblyBuilderRuntimeAssemblyFunc(assembly); + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static ModuleBuilder GetModuleBuilder(this ParameterBuilder parameter) + { + return (ModuleBuilder)_getParameterMethodBuilderFunc(parameter).Module; + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static TypeBuilder GetTypeBuilder(this ParameterBuilder parameter) + { + return (TypeBuilder?)_getParameterMethodBuilderFunc(parameter).DeclaringType ?? throw new InvalidOperationException(); + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static ModuleBuilder GetModuleBuilder(this FieldBuilder field) + { + return (ModuleBuilder)field.Module; + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static TypeBuilder GetTypeBuilder(this FieldBuilder field) + { + return (TypeBuilder?)field.DeclaringType ?? throw new InvalidOperationException(); + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static ModuleBuilder GetModuleBuilder(this PropertyBuilder property) + { + return (ModuleBuilder)property.Module; + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static TypeBuilder GetTypeBuilder(this PropertyBuilder property) + { + return (TypeBuilder)property.DeclaringType!; + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static ModuleBuilder GetModuleBuilder(this EventBuilder @event) + { + return _getEventModuleBuilderFunc(@event); + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static TypeBuilder GetTypeBuilder(this EventBuilder @event) + { + return _getEventTypeBuilderFunc(@event); + } + + /// + /// Gets the name associated with a . + /// + /// + /// + public static string GetEventName(this EventBuilder @event) + { + return _getEventNameFunc(@event); + } + + /// + /// Gets the attributes associated with a . + /// + /// + /// + public static EventAttributes GetEventAttributes(this EventBuilder @event) + { + return _getEventAttributesFunc(@event); + } + + /// + /// Gets the associated with a . + /// + /// + /// + public static MethodBuilder GetMethodBuilder(this ParameterBuilder parameter) + { + return _getParameterMethodBuilderFunc(parameter); + } + + /// + /// Returns true if the is a SZArray. + /// + /// + /// + public static bool IsSZArray(this Type type) + { +#if NET + return type.IsSZArray; +#else + return type.IsArray && type.Name.EndsWith("[]"); +#endif + } + + /// + /// Gets the parameters types of the specified method or constructor. + /// + /// + /// + public static Type[] GetParameterTypes(this MethodBase method) + { + var p = method.GetParameters(); + var a = new Type[p.Length]; + for (int i = 0; i < p.Length; i++) + a[i] = p[i].ParameterType; + + return a; + } + + /// + /// Implements GetcustomAttributeData with support for examining inheritence. + /// + /// + public static IEnumerable GetCustomAttributesData(this Type type, bool inherit) + { + foreach (var i in type.GetCustomAttributesData()) + yield return i; + + if (inherit) + for (var baseType = type.BaseType; baseType != null; baseType = baseType.BaseType) + foreach (var cad in baseType.GetCustomAttributesData()) + if (cad.AttributeType.GetCustomAttribute()?.Inherited ?? false) + yield return cad; + } + + /// + /// Implements GetcustomAttributeData with support for examining inheritence. + /// + /// + public static IEnumerable GetInheritedCustomAttributesData(this Type type) + { + for (var baseType = type.BaseType; baseType != null; baseType = baseType.BaseType) + foreach (var cad in baseType.GetCustomAttributesData()) + if (cad.AttributeType.GetCustomAttribute()?.Inherited ?? false) + yield return cad; + } + + /// + /// Implements GetcustomAttributeData with support for examining inheritence. + /// + /// + /// + public static IEnumerable GetCustomAttributesData(this MethodInfo method, bool inherit) + { + foreach (var i in method.GetCustomAttributesData()) + yield return i; + + if (inherit) + for (var baseMethod = method.GetBaseDefinition(); baseMethod != null; baseMethod = baseMethod.GetBaseDefinition()) + foreach (var cad in baseMethod.GetCustomAttributesData()) + if (cad.AttributeType.GetCustomAttribute()?.Inherited ?? false) + yield return cad; + } + + /// + /// Implements GetcustomAttributeData with support for examining inheritence. + /// + /// + /// + public static IEnumerable GetInheritedCustomAttributesData(this MethodInfo method) + { + for (var baseMethod = method.GetBaseDefinition(); baseMethod != null; baseMethod = baseMethod.GetBaseDefinition()) + foreach (var cad in baseMethod.GetCustomAttributesData()) + if (cad.AttributeType.GetCustomAttribute()?.Inherited ?? false) + yield return cad; + } + + /// + /// Implements GetcustomAttributeData with support for examining inheritence. + /// + /// + /// + public static IEnumerable GetCustomAttributesData(this MemberInfo member, bool inherit) + { + if (member is Type type) + return GetCustomAttributesData(type, inherit); + + if (member is MethodInfo method) + return GetCustomAttributesData(method, inherit); + + return member.GetCustomAttributesData(); + } + + /// + /// Gets the interfaces that are directly declared on the specified type. The method is imperfect, as + /// GetInterfaceMap provides no way to discover interfaces on a type that have no implementation methods. + /// + /// + /// + public static IReadOnlyList GetDeclaredInterfaces(this Type type) + { + var b = new List(); + + foreach (var iface in type.GetInterfaces()) + if (IsInterfaceDirectlyImplementedOnType(type, iface)) + b.Add(iface); + + return b; + } + + /// + /// Returns true if the is directly implemented by . + /// + /// + /// + /// + static bool IsInterfaceDirectlyImplementedOnType(Type type, Type interfaceType) + { + var map = type.GetInterfaceMap(interfaceType); + + // if any of the target methods are declared on this type, the interface is implemented by this type + foreach (var method in map.TargetMethods) + if (method.DeclaringType == type) + return true; + + return false; + } + + } + +} \ No newline at end of file diff --git a/src/IKVM.CoreLib/Reflection/TypeListEqualityComparer.cs b/src/IKVM.CoreLib/Reflection/TypeListEqualityComparer.cs new file mode 100644 index 0000000000..c7349923bf --- /dev/null +++ b/src/IKVM.CoreLib/Reflection/TypeListEqualityComparer.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace IKVM.CoreLib.Symbols +{ + + /// + /// Compares two array instances for equality. + /// + class TypeListEqualityComparer : IEqualityComparer + { + + public static readonly TypeListEqualityComparer Instance = new(); + + public bool Equals(Type[]? x, Type[]? y) + { + if (x == y) + return true; + + if (x == null || y == null) + return false; + + if (x.Length != y.Length) + return false; + + for (int i = 0; i < x.Length; i++) + if (x[i] != y[i]) + return false; + + return true; + } + + public int GetHashCode(Type[] obj) + { + int result = 17; + + for (int i = 0; i < obj.Length; i++) + { + unchecked + { + result = result * 41 + obj[i].GetHashCode(); + } + } + + return result; + } + + } + +} diff --git a/src/IKVM.CoreLib/Runtime/DependentHandle.cs b/src/IKVM.CoreLib/Runtime/DependentHandle.cs new file mode 100644 index 0000000000..9e59664dbb --- /dev/null +++ b/src/IKVM.CoreLib/Runtime/DependentHandle.cs @@ -0,0 +1,193 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime; + +namespace IKVM.CoreLib.Runtime +{ + + /// + /// Private type-safe implementation of DependentHandle. On .NET Core this serves as a wrapper for the built-in + /// type. On Framework this uses reflection. + /// + /// + /// + struct DependentHandle : IDisposable + where TTarget : class? + where TDependent : class? + { + +#if NETFRAMEWORK + + static readonly Type DependentHandleType = typeof(object).Assembly.GetType("System.Runtime.CompilerServices.DependentHandle") ?? throw new Exception(); + static readonly ConstructorInfo DependentHandleCtor = DependentHandleType.GetConstructor([typeof(object), typeof(object)]) ?? throw new Exception(); + static readonly PropertyInfo IsAllocatedProperty = DependentHandleType.GetProperty("IsAllocated") ?? throw new Exception(); + static readonly MethodInfo GetPrimaryMethod = DependentHandleType.GetMethod("GetPrimary") ?? throw new Exception(); + static readonly MethodInfo GetPrimaryAndSecondaryMethod = DependentHandleType.GetMethod("GetPrimaryAndSecondary") ?? throw new Exception(); + static readonly MethodInfo FreeMethod = DependentHandleType.GetMethod("Free") ?? throw new Exception(); + + static readonly ParameterExpression ThisExpr = Expression.Parameter(typeof(object)); + + static readonly Func GetIsAllocatedFunc = Expression.Lambda>( + Expression.Property( + Expression.ConvertChecked(ThisExpr, DependentHandleType), + IsAllocatedProperty), + ThisExpr) + .Compile(); + + static readonly Func GetPrimaryFunc = Expression.Lambda>( + Expression.Call( + Expression.ConvertChecked(ThisExpr, DependentHandleType), + GetPrimaryMethod), + ThisExpr) + .Compile(); + + delegate void GetPrimaryAndSecondaryDelegate(object self, out object primary, out object secondary); + + static readonly ParameterExpression Ref1 = Expression.Parameter(typeof(object).MakeByRefType()); + static readonly ParameterExpression Ref2 = Expression.Parameter(typeof(object).MakeByRefType()); + + static readonly GetPrimaryAndSecondaryDelegate GetPrimaryAndSecondaryFunc = Expression.Lambda( + Expression.Call( + Expression.ConvertChecked(ThisExpr, DependentHandleType), + GetPrimaryAndSecondaryMethod, + Ref1, + Ref2), + ThisExpr, + Ref1, + Ref2) + .Compile(); + + static readonly Action FreeFunc = Expression.Lambda>( + Expression.Call( + Expression.ConvertChecked(ThisExpr, DependentHandleType), + FreeMethod), + ThisExpr) + .Compile(); + +#endif + +#if NET + DependentHandle _hnd; +#else + object _hnd; +#endif + + /// + /// Initializes a new instance. + /// + /// + /// + public DependentHandle(TTarget? target, TDependent? dependent) + { +#if NET + _hnd = new DependentHandle(target, dependent); +#else + _hnd = DependentHandleCtor.Invoke([target, dependent]); +#endif + } + + /// + /// Gets a value indicating whether this instance was constructed and has not yet been disposed. + /// + public readonly bool IsAllocated + { + get + { +#if NET + return _hnd.IsAllocated; +#else + return GetIsAllocatedFunc(_hnd); +#endif + } + } + + /// + /// Gets or sets the target object instance for the current handle. + /// + public TTarget? Target + { + readonly get + { +#if NET + return (TTarget?)_hnd.Target; +#else + return (TTarget?)GetPrimaryFunc(_hnd); +#endif + } + set + { +#if NET + _hnd.Target = value; +#else + GetPrimaryAndSecondaryFunc(_hnd, out var primary, out var secondary); + FreeFunc(_hnd); + _hnd = DependentHandleCtor.Invoke([value, secondary]); + GC.KeepAlive(primary); + GC.KeepAlive(secondary); +#endif + } + } + + /// + /// Gets or sets the dependent object instance for the current handle. + + + /// + public TDependent? Dependent + { + readonly get + { +#if NET + return (TDependent?)_hnd.Dependent; +#else + GetPrimaryAndSecondaryFunc(_hnd, out var primary, out var secondary); + return (TDependent?)secondary; +#endif + } + set + { +#if NET + _hnd.Dependent = value; +#else + GetPrimaryAndSecondaryFunc(_hnd, out var primary, out var secondary); + FreeFunc(_hnd); + _hnd = DependentHandleCtor.Invoke([primary, value]); + GC.KeepAlive(primary); + GC.KeepAlive(secondary); +#endif + } + } + + /// + /// Gets the values of both Target and Dependent (if available) as an atomic operation. + /// + public readonly (TTarget? Target, TDependent? Dependent) TargetAndDependent + { + get + { +#if NET + var (Target, Dependent) = _hnd.TargetAndDependent; + return ((TTarget?)Target, (TDependent?)Dependent); +#else + GetPrimaryAndSecondaryFunc(_hnd, out var primary, out var secondary); + return ((TTarget?)primary, (TDependent?)secondary); +#endif + } + } + + /// + public void Dispose() + { +#if NET + if (_hnd.IsAllocated) + _hnd.Dispose(); +#else + if (_hnd is not null) + FreeFunc(_hnd); +#endif + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/ArrayTypeSymbol.cs b/src/IKVM.CoreLib/Symbols/ArrayTypeSymbol.cs new file mode 100644 index 0000000000..1c1f7f8c22 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/ArrayTypeSymbol.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; +using System.Text; + +namespace IKVM.CoreLib.Symbols +{ + + class ArrayTypeSymbol : HasElementSymbol + { + + readonly int _rank; + readonly ImmutableArray _sizes; + readonly ImmutableArray _lowerBounds; + + string? _nameSuffix; + ImmutableArray _interfaces; + ImmutableArray _methods; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + /// + public ArrayTypeSymbol(SymbolContext context, TypeSymbol elementType, int rank, ImmutableArray sizes, ImmutableArray lowerBounds) : + base(context, elementType) + { + _rank = rank; + _sizes = sizes; + _lowerBounds = lowerBounds; + } + + /// + protected override string NameSuffix => _nameSuffix ??= ComputeNameSuffix(); + + /// + /// Computes the value for . + /// + /// + string ComputeNameSuffix() + { + if (_rank == 1) + { + return "[*]"; + } + else + { + var b = new ValueStringBuilder(stackalloc char[1 + (_rank - 1) + 1]); + b.Append('['); + b.Append(',', _rank - 1); + b.Append(']'); + return b.ToString(); + } + } + + /// + public sealed override TypeAttributes Attributes => TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Serializable; + + /// + public sealed override bool IsArray => true; + + /// + public sealed override TypeSymbol? BaseType => Context.ResolveCoreType("System.Array"); + + /// + public sealed override int GetArrayRank() + { + return _rank; + } + + /// + internal sealed override ImmutableArray GetDeclaredInterfaces() => ImmutableArray.Empty; + + /// + internal sealed override ImmutableArray GetDeclaredMethods() + { + if (_methods.IsDefault) + { + var int32 = Context.ResolveCoreType("System.Int32"); + + var ctor1Args = ImmutableArray.CreateBuilder(_rank); + var ctor2Args = ImmutableArray.CreateBuilder(_rank * 2); + for (int i = 0; i < _rank; i++) + { + ctor1Args.Add(int32); + ctor2Args.Add(int32); + ctor2Args.Add(int32); + } + + // get and set args start at the same length + var argBuilder = ImmutableArray.CreateBuilder(_rank); + for (int i = 0; i < _rank; i++) + argBuilder.Add(int32); + + var args = argBuilder.DrainToImmutable(); + var getArgs = args; + var setArgs = args.Add(GetElementType()!); // set args takes a value + + ImmutableInterlocked.InterlockedInitialize(ref _methods, + [ + new SyntheticConstructorSymbol(Context, Module, this, MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, ctor1Args.DrainToImmutable()), + new SyntheticConstructorSymbol(Context, Module, this, MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, ctor2Args.DrainToImmutable()), + new SyntheticMethodSymbol(Context, Module, this, "Set", MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, null, setArgs), + new SyntheticMethodSymbol(Context, Module, this, "Address", MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, GetElementType()!.MakeByRefType(), getArgs), + new SyntheticMethodSymbol(Context, Module, this, "Get", MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, GetElementType(), getArgs), + ]); + } + + return _methods; + } + + /// + internal sealed override MethodImplementationMapping GetMethodImplementations() + { + return MethodImplementationMapping.CreateEmpty(this); + } + + /// + internal sealed override ImmutableArray GetDeclaredCustomAttributes() => []; + + /// + internal sealed override TypeSymbol Specialize(GenericContext context) + { + if (ContainsGenericParameters == false) + return this; + + var elementType = GetElementType() ?? throw new InvalidOperationException(); + return elementType.Specialize(context).MakeArrayType(GetArrayRank()); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/AssemblyIdentity.cs b/src/IKVM.CoreLib/Symbols/AssemblyIdentity.cs new file mode 100644 index 0000000000..847be2426e --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/AssemblyIdentity.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; + +namespace IKVM.CoreLib.Symbols +{ + + /// + /// Represents an identity of an assembly as defined by CLI metadata specification. + /// + public class AssemblyIdentity + { + + /// + /// Determines whether two instances are equal. + /// + /// The operand appearing on the left side of the operator. + /// The operand appearing on the right side of the operator. + public static bool operator ==(AssemblyIdentity? left, AssemblyIdentity? right) + { + return EqualityComparer.Default.Equals(left!, right!); + } + + /// + /// Determines whether two instances are not equal. + /// + /// The operand appearing on the left side of the operator. + /// The operand appearing on the right side of the operator. + public static bool operator !=(AssemblyIdentity? left, AssemblyIdentity? right) + { + return !(left == right); + } + + /// + /// Returns true (false) if specified assembly identities are (not) equal + /// regardless of unification, retargeting or other assembly binding policies. + /// Returns null if these policies must be consulted to determine name equivalence. + /// + public static bool? MemberwiseEqual(AssemblyIdentity x, AssemblyIdentity y) + { + if (ReferenceEquals(x, y)) + return true; + + if (!AssemblyIdentityComparer.SimpleNameComparer.Equals(x._name, y._name)) + return false; + + if (x._version.Equals(y._version) && EqualIgnoringNameAndVersion(x, y)) + return true; + + return null; + } + + /// + /// Returns true if the components of the assembly names, other than name and version, are equal. + /// + /// + /// + /// + public static bool EqualIgnoringNameAndVersion(AssemblyIdentity x, AssemblyIdentity y) + { + return x.ContentType == y.ContentType && AssemblyIdentityComparer.CultureComparer.Equals(x.CultureName, y.CultureName) && KeysEqual(x, y); + } + + /// + /// Returns true if the public keys of both identities are equal. + /// + /// + /// + /// + public static bool KeysEqual(AssemblyIdentity x, AssemblyIdentity y) + { + var xToken = x._publicKeyToken; + var yToken = y._publicKeyToken; + + // weak names or both strong names with initialized PKT - compare tokens: + if (!xToken.IsDefault && !yToken.IsDefault) + return xToken.SequenceEqual(yToken); + + // both are strong names with uninitialized PKT - compare full keys: + if (xToken.IsDefault && yToken.IsDefault) + return x._publicKey.SequenceEqual(y._publicKey); + + // one of the strong names doesn't have PK, other doesn't have PTK initialized. + if (xToken.IsDefault) + return x.PublicKeyToken.SequenceEqual(yToken); + else + return xToken.SequenceEqual(y.PublicKeyToken); + } + + /// + /// Initializes the publickey and publickeytoken values. + /// + /// + /// + /// + /// + static void InitializeKey(ImmutableArray publicKeyOrToken, bool hasPublicKey, out ImmutableArray publicKey, out ImmutableArray publicKeyToken) + { + if (hasPublicKey) + { + publicKey = publicKeyOrToken; + publicKeyToken = default; + } + else + { + publicKey = ImmutableArray.Empty; + publicKeyToken = publicKeyOrToken.IsDefault ? ImmutableArray.Empty : publicKeyOrToken; + } + } + + /// + /// Calculates the public key token from the given public key. + /// + /// + /// + static ImmutableArray CalculatePublicKeyToken(ImmutableArray publicKey) + { + var hash = SHA1.Create().ComputeHash(publicKey.ToArray()); + + // SHA1 hash is always 160 bits: + Debug.Assert(hash.Length == 20); + + // PublicKeyToken is the low 64 bits of the SHA-1 hash of the public key. + int l = hash.Length - 1; + var result = ImmutableArray.CreateBuilder(8); + for (int i = 0; i < 8; i++) + result.Add(hash[l - i]); + + return result.DrainToImmutable(); + } + + /// + /// Parses a span of characters into a assembly name. + /// + /// A span containing the characters representing the assembly name to parse. + /// Parsed type name. + /// Provided assembly name was invalid. + public static AssemblyIdentity Parse(ReadOnlySpan assemblyName) => TryParse(assemblyName, out AssemblyIdentity? result) ? result! : throw new ArgumentException("Invalid assembly name.", nameof(assemblyName)); + + /// + /// Tries to parse a span of characters into an assembly name. + /// + /// A span containing the characters representing the assembly name to parse. + /// Contains the result when parsing succeeds. + /// true if assembly name was converted successfully, otherwise, false. + public static bool TryParse(ReadOnlySpan assemblyName, [NotNullWhen(true)] out AssemblyIdentity? result) + { + AssemblyIdentityParser.AssemblyIdentityParts parts = default; + if (!assemblyName.IsEmpty && AssemblyIdentityParser.TryParse(assemblyName, ref parts)) + { + result = new(parts._name, parts._version, parts._cultureName, parts._publicKeyOrToken?.ToImmutableArray() ?? [], parts._publicKeyOrToken != null); + return true; + } + + result = null; + return false; + } + + readonly string _name; + readonly Version _version; + readonly string? _cultureName; + readonly AssemblyContentType _contentType; + readonly ProcessorArchitecture _processorArchitecture; + + string? _fullName; + ImmutableArray _publicKey; + ImmutableArray _publicKeyToken; + + int _hashCode = 0; + + /// + /// Initializes a new instance of the class. + /// + /// The simple name of the assembly. + /// The version of the assembly. + /// The name of the culture associated with the assembly. + /// The public key or its token. + /// is null. + public AssemblyIdentity(string name, Version? version = null, string? cultureName = null, ImmutableArray publicKeyOrToken = default, bool hasPublicKey = false, AssemblyContentType contentType = default, ProcessorArchitecture processorArchitecture = ProcessorArchitecture.None) + { + _name = name ?? throw new ArgumentNullException(nameof(name)); + _version = version ?? new Version(0, 0, 0, 0); + _cultureName = cultureName; + _contentType = contentType; + _processorArchitecture = processorArchitecture; + InitializeKey(publicKeyOrToken, hasPublicKey, out _publicKey, out _publicKeyToken); + } + + /// + /// Gets the simple name of the assembly. + /// + public string Name => _name; + + /// + /// Gets the version of the assembly. + /// + public Version? Version => _version; + + /// + /// Gets the name of the culture associated with the assembly. + /// + /// + /// Do not create a instance from this string unless + /// you know the string has originated from a trustworthy source. + /// + public string? CultureName => _cultureName; + + /// + /// Returns true if a public key is available. + /// + public bool HasPublicKey => _publicKey.Length > 0; + + /// + /// Gets the public key or the public key token of the assembly. + /// + public ImmutableArray PublicKey => _publicKey; + + /// + /// Low 8 bytes of SHA1 hash of the public key, or empty. + /// + public ImmutableArray PublicKeyToken + { + get + { + if (_publicKeyToken.IsDefault) + ImmutableInterlocked.InterlockedCompareExchange(ref _publicKeyToken, CalculatePublicKeyToken(_publicKey), default); + + return _publicKeyToken; + } + } + + /// + /// Gets the full name of the assembly, also known as the display name. + /// + /// In contrary to it does not validate public key token neither computes it based on the provided public key. + public string FullName => _fullName ??= AssemblyNameFormatter.ComputeDisplayName(Name, Version, CultureName, PublicKeyToken, Flags, ContentType, PublicKey); + + /// + /// Gets the . + /// + public AssemblyNameFlags Flags => HasPublicKey ? AssemblyNameFlags.PublicKey : AssemblyNameFlags.None; + + /// + /// Specifies the binding model for how this object will be treated in comparisons. + /// + public AssemblyContentType ContentType => _contentType; + + /// + /// Gets the processor architecture of the assembly name. + /// + public ProcessorArchitecture ProcessorArchitecture => _processorArchitecture; + + /// + /// True if the assembly identity has a strong name, ie. either a full public key or a token. + /// + public bool IsStrongName => HasPublicKey || _publicKeyToken.Length > 0; + + /// + /// Determines whether the specified instance is equal to the current instance. + /// + /// The object to be compared with the current instance. + public bool Equals(AssemblyIdentity? obj) + { + return !ReferenceEquals(obj, null) && (_hashCode == 0 || obj._hashCode == 0 || _hashCode == obj._hashCode) && MemberwiseEqual(this, obj) == true; + } + + /// + /// Determines whether the specified instance is equal to the current instance. + /// + /// The object to be compared with the current instance. + public override bool Equals(object? obj) + { + return Equals(obj as AssemblyIdentity); + } + + /// + /// Returns the hash code for the current instance. + /// + /// + public override int GetHashCode() + { + if (_hashCode == 0) + { + // Do not include PK/PKT in the hash - collisions on PK/PKT are rare (assembly identities differ only in PKT/PK) + // and we can't calculate hash of PKT if only PK is available + _hashCode = + HashCode.Combine(AssemblyIdentityComparer.SimpleNameComparer.GetHashCode(_name), + HashCode.Combine(_version?.GetHashCode(), GetHashCodeIgnoringNameAndVersion())); + } + + return _hashCode; + } + + /// + /// Gets the hashcode for this instance, without considering the name and version. + /// + /// + int GetHashCodeIgnoringNameAndVersion() + { + return HashCode.Combine((int)_contentType, AssemblyIdentityComparer.CultureComparer.GetHashCode(_cultureName ?? "")); + } + + /// + /// Initializes a new instance of the class based on the stored information. + /// + /// + /// Do not create an instance with string unless + /// you know the string has originated from a trustworthy source. + /// + public AssemblyName ToAssemblyName() + { + AssemblyName assemblyName = new(); + assemblyName.Name = Name; + assemblyName.CultureName = CultureName; + assemblyName.Version = Version; + assemblyName.Flags = Flags; + assemblyName.ContentType = ContentType; +#pragma warning disable SYSLIB0037 // Type or member is obsolete + assemblyName.ProcessorArchitecture = ProcessorArchitecture; +#pragma warning restore SYSLIB0037 // Type or member is obsolete + + if (HasPublicKey) + assemblyName.SetPublicKey(PublicKey.ToArray()); + else if (PublicKeyToken.IsDefaultOrEmpty == false) + assemblyName.SetPublicKeyToken(PublicKeyToken.ToArray()); + + return assemblyName; + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/AssemblyIdentityComparer.cs b/src/IKVM.CoreLib/Symbols/AssemblyIdentityComparer.cs new file mode 100644 index 0000000000..a4727d08cf --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/AssemblyIdentityComparer.cs @@ -0,0 +1,23 @@ +using System; + +namespace IKVM.CoreLib.Symbols +{ + + class AssemblyIdentityComparer + { + + public static AssemblyIdentityComparer Default { get; } = new AssemblyIdentityComparer(); + + public static StringComparer SimpleNameComparer + { + get { return StringComparer.OrdinalIgnoreCase; } + } + + public static StringComparer CultureComparer + { + get { return StringComparer.OrdinalIgnoreCase; } + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/AssemblyIdentityParts.cs b/src/IKVM.CoreLib/Symbols/AssemblyIdentityParts.cs new file mode 100644 index 0000000000..995719a0cf --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/AssemblyIdentityParts.cs @@ -0,0 +1,490 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; + +using IKVM.CoreLib.Text; + +namespace IKVM.CoreLib.Symbols +{ + + /// + /// Parses an assembly name. + /// + internal ref partial struct AssemblyIdentityParser + { + + public readonly struct AssemblyIdentityParts + { + + public AssemblyIdentityParts(string name, Version? version, string? cultureName, AssemblyNameFlags flags, byte[]? publicKeyOrToken) + { + _name = name; + _version = version; + _cultureName = cultureName; + _flags = flags; + _publicKeyOrToken = publicKeyOrToken; + } + + public readonly string _name; + public readonly Version? _version; + public readonly string? _cultureName; + public readonly AssemblyNameFlags _flags; + public readonly byte[]? _publicKeyOrToken; + + } + + /// + /// Token categories for the lexer. + /// + private enum Token + { + Equals = 1, + Comma = 2, + String = 3, + End = 4, + } + + private enum AttributeKind + { + Version = 1, + Culture = 2, + PublicKeyOrToken = 4, + ProcessorArchitecture = 8, + Retargetable = 16, + ContentType = 32 + } + + private readonly ReadOnlySpan _input; + private int _index; + + private AssemblyIdentityParser(ReadOnlySpan input) + { + if (input.Length == 0) + throw new ArgumentException(nameof(input)); + + _input = input; + _index = 0; + } + + internal static bool TryParse(ReadOnlySpan name, ref AssemblyIdentityParts parts) + { + AssemblyIdentityParser parser = new(name); + return parser.TryParse(ref parts); + } + + private static bool TryRecordNewSeen(scoped ref AttributeKind seenAttributes, AttributeKind newAttribute) + { + if ((seenAttributes & newAttribute) != 0) + { + return false; + } + seenAttributes |= newAttribute; + return true; + } + + private bool TryParse(ref AssemblyIdentityParts result) + { + // Name must come first. + if (!TryGetNextToken(out string name, out Token token) || token != Token.String || string.IsNullOrEmpty(name)) + return false; + + Version? version = null; + string? cultureName = null; + byte[]? pkt = null; + AssemblyNameFlags flags = 0; + + AttributeKind alreadySeen = default; + if (!TryGetNextToken(out _, out token)) + return false; + + while (token != Token.End) + { + if (token != Token.Comma) + return false; + + if (!TryGetNextToken(out string attributeName, out token) || token != Token.String) + return false; + + if (!TryGetNextToken(out _, out token) || token != Token.Equals) + return false; + + if (!TryGetNextToken(out string attributeValue, out token) || token != Token.String) + return false; + + if (attributeName == string.Empty) + return false; + + if (IsAttribute(attributeName, "Version")) + { + if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Version)) + { + return false; + } + if (!TryParseVersion(attributeValue, ref version)) + { + return false; + } + } + else if (IsAttribute(attributeName, "Culture")) + { + if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Culture)) + { + return false; + } + if (!TryParseCulture(attributeValue, out cultureName)) + { + return false; + } + } + else if (IsAttribute(attributeName, "PublicKeyToken")) + { + if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken)) + { + return false; + } + if (!TryParsePKT(attributeValue, isToken: true, out pkt)) + { + return false; + } + } + else if (IsAttribute(attributeName, "PublicKey")) + { + if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken)) + { + return false; + } + if (!TryParsePKT(attributeValue, isToken: false, out pkt)) + { + return false; + } + flags |= AssemblyNameFlags.PublicKey; + } + else if (IsAttribute(attributeName, "ProcessorArchitecture")) + { + if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ProcessorArchitecture)) + { + return false; + } + if (!TryParseProcessorArchitecture(attributeValue, out ProcessorArchitecture arch)) + { + return false; + } + flags |= (AssemblyNameFlags)(((int)arch) << 4); + } + else if (IsAttribute(attributeName, "Retargetable")) + { + if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Retargetable)) + { + return false; + } + + if (attributeValue.Equals("Yes", StringComparison.OrdinalIgnoreCase)) + { + flags |= AssemblyNameFlags.Retargetable; + } + else if (attributeValue.Equals("No", StringComparison.OrdinalIgnoreCase)) + { + // nothing to do + } + else + { + return false; + } + } + else if (IsAttribute(attributeName, "ContentType")) + { + if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ContentType)) + { + return false; + } + + if (attributeValue.Equals("WindowsRuntime", StringComparison.OrdinalIgnoreCase)) + { + flags |= (AssemblyNameFlags)(((int)AssemblyContentType.WindowsRuntime) << 9); + } + else + { + return false; + } + } + else + { + // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it. + } + + if (!TryGetNextToken(out _, out token)) + { + return false; + } + } + + result = new AssemblyIdentityParts(name, version, cultureName, flags, pkt); + return true; + } + + private static bool IsAttribute(string candidate, string attributeKind) + => candidate.Equals(attributeKind, StringComparison.OrdinalIgnoreCase); + + private static bool TryParseVersion(string attributeValue, ref Version? version) + { +#if NET8_0_OR_GREATER + ReadOnlySpan attributeValueSpan = attributeValue; + Span parts = stackalloc Range[5]; + parts = parts.Slice(0, attributeValueSpan.Split(parts, '.')); +#else + string[] parts = attributeValue.Split('.'); +#endif + if (parts.Length is < 2 or > 4) + { + return false; + } + + Span versionNumbers = [ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue]; + for (int i = 0; i < parts.Length; i++) + { + if (!ushort.TryParse( +#if NET8_0_OR_GREATER + attributeValueSpan[parts[i]], +#else + parts[i], +#endif + NumberStyles.None, NumberFormatInfo.InvariantInfo, out versionNumbers[i])) + { + return false; + } + } + + if (versionNumbers[0] == ushort.MaxValue || + versionNumbers[1] == ushort.MaxValue) + { + return false; + } + + version = + versionNumbers[2] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1]) : + versionNumbers[3] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2]) : + new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]); + + return true; + } + + private static bool TryParseCulture(string attributeValue, out string? result) + { + if (attributeValue.Equals("Neutral", StringComparison.OrdinalIgnoreCase)) + { + result = ""; + return true; + } + + result = attributeValue; + return true; + } + + private static bool TryParsePKT(string attributeValue, bool isToken, out byte[]? result) + { + if (attributeValue.Equals("null", StringComparison.OrdinalIgnoreCase) || attributeValue == string.Empty) + { + result = Array.Empty(); + return true; + } + + if (attributeValue.Length % 2 != 0 || (isToken && attributeValue.Length != 8 * 2)) + { + result = null; + return false; + } + + byte[] pkt = new byte[attributeValue.Length / 2]; + if (!HexConverter.TryDecodeFromUtf16(attributeValue.AsSpan(), pkt, out int _)) + { + result = null; + return false; + } + + result = pkt; + return true; + } + + private static bool TryParseProcessorArchitecture(string attributeValue, out ProcessorArchitecture result) + { + result = attributeValue switch + { + _ when attributeValue.Equals("msil", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.MSIL, + _ when attributeValue.Equals("x86", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.X86, + _ when attributeValue.Equals("ia64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.IA64, + _ when attributeValue.Equals("amd64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Amd64, + _ when attributeValue.Equals("arm", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Arm, + _ when attributeValue.Equals("msil", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.MSIL, + _ => ProcessorArchitecture.None + }; + return result != ProcessorArchitecture.None; + } + + private static bool IsWhiteSpace(char ch) + => ch is '\n' or '\r' or ' ' or '\t'; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryGetNextChar(out char ch) + { + if (_index < _input.Length) + { + ch = _input[_index++]; + if (ch == '\0') + { + return false; + } + } + else + { + ch = '\0'; + } + + return true; + } + + // + // Return the next token in assembly name. If the result is Token.String, + // sets "tokenString" to the tokenized string. + // + private bool TryGetNextToken(out string tokenString, out Token token) + { + tokenString = string.Empty; + char c; + + while (true) + { + if (!TryGetNextChar(out c)) + { + token = default; + return false; + } + + switch (c) + { + case ',': + { + token = Token.Comma; + return true; + } + case '=': + { + token = Token.Equals; + return true; + } + case '\0': + { + token = Token.End; + return true; + } + } + + if (!IsWhiteSpace(c)) + { + break; + } + } + + using ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[64]); + + char quoteChar = '\0'; + if (c is '\'' or '\"') + { + quoteChar = c; + if (!TryGetNextChar(out c)) + { + token = default; + return false; + } + } + + for (; ; ) + { + if (c == 0) + { + if (quoteChar != 0) + { + // EOS and unclosed quotes is an error + token = default; + return false; + } + // Reached end of input and therefore of string + break; + } + + if (quoteChar != 0 && c == quoteChar) + break; // Terminate: Found closing quote of quoted string. + + if (quoteChar == 0 && (c is ',' or '=')) + { + _index--; + break; // Terminate: Found start of a new ',' or '=' token. + } + + if (quoteChar == 0 && (c is '\'' or '\"')) + { + token = default; + return false; + } + + if (c is '\\') + { + if (!TryGetNextChar(out c)) + { + token = default; + return false; + } + + switch (c) + { + case '\\': + case ',': + case '=': + case '\'': + case '"': + sb.Append(c); + break; + case 't': + sb.Append('\t'); + break; + case 'r': + sb.Append('\r'); + break; + case 'n': + sb.Append('\n'); + break; + default: + token = default; + return false; + } + } + else + { + sb.Append(c); + } + + if (!TryGetNextChar(out c)) + { + token = default; + return false; + } + } + + + int length = sb.Length; + if (quoteChar == 0) + { + while (length > 0 && IsWhiteSpace(sb[length - 1])) + length--; + } + + tokenString = sb.AsSpan(0, length).ToString(); + token = Token.String; + return true; + } + } +} \ No newline at end of file diff --git a/src/IKVM.CoreLib/Symbols/AssemblyNameFormatter.cs b/src/IKVM.CoreLib/Symbols/AssemblyNameFormatter.cs new file mode 100644 index 0000000000..7c2ecd4b29 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/AssemblyNameFormatter.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Reflection; +using System.Text; + +using IKVM.CoreLib.Text; + +namespace IKVM.CoreLib.Symbols +{ + + static class AssemblyNameFormatter + { + + public static string ComputeDisplayName(string name, Version? version, string? cultureName, ImmutableArray pkt, AssemblyNameFlags flags, AssemblyContentType contentType, ImmutableArray pk) + { + const int PUBLIC_KEY_TOKEN_LEN = 8; + + Debug.Assert(name.Length != 0); + + var vsb = new ValueStringBuilder(stackalloc char[256]); + vsb.AppendQuoted(name); + + if (version != null) + { + ushort major = (ushort)version.Major; + if (major != ushort.MaxValue) + { + vsb.Append(", Version="); + vsb.AppendSpanFormattable(major); + + ushort minor = (ushort)version.Minor; + if (minor != ushort.MaxValue) + { + vsb.Append('.'); + vsb.AppendSpanFormattable(minor); + + ushort build = (ushort)version.Build; + if (build != ushort.MaxValue) + { + vsb.Append('.'); + vsb.AppendSpanFormattable(build); + + ushort revision = (ushort)version.Revision; + if (revision != ushort.MaxValue) + { + vsb.Append('.'); + vsb.AppendSpanFormattable(revision); + } + } + } + } + } + + if (cultureName != null) + { + if (cultureName.Length == 0) + cultureName = "neutral"; + vsb.Append(", Culture="); + vsb.AppendQuoted(cultureName); + } + + var keyOrToken = pkt.IsDefaultOrEmpty == false ? pkt : pk; + if (keyOrToken != null) + { + if (pkt != null) + { + if (pkt.Length > PUBLIC_KEY_TOKEN_LEN) + throw new ArgumentException(); + + vsb.Append(", PublicKeyToken="); + } + else + { + vsb.Append(", PublicKey="); + } + + if (keyOrToken.Length == 0) + { + vsb.Append("null"); + } + else + { + HexConverter.EncodeToUtf16(keyOrToken.AsSpan(), vsb.AppendSpan(keyOrToken.Length * 2), HexConverter.Casing.Lower); + } + } + + if (0 != (flags & AssemblyNameFlags.Retargetable)) + vsb.Append(", Retargetable=Yes"); + + if (contentType == AssemblyContentType.WindowsRuntime) + vsb.Append(", ContentType=WindowsRuntime"); + + return vsb.ToString(); + } + + static void AppendQuoted(this ref ValueStringBuilder vsb, string s) + { + bool needsQuoting = false; + const char quoteChar = '\"'; + + // App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one + // by some algorithm. Rather than guess at it, we use double quotes consistently. + ReadOnlySpan span = s.AsSpan(); + if (s.Length != span.Trim().Length || span.IndexOfAny('\"', '\'') >= 0) + needsQuoting = true; + + if (needsQuoting) + vsb.Append(quoteChar); + + for (int i = 0; i < s.Length; i++) + { + switch (s[i]) + { + case '\\': + case ',': + case '=': + case '\'': + case '"': + vsb.Append('\\'); + break; + case '\t': + vsb.Append("\\t"); + continue; + case '\r': + vsb.Append("\\r"); + continue; + case '\n': + vsb.Append("\\n"); + continue; + } + + vsb.Append(s[i]); + } + + if (needsQuoting) + vsb.Append(quoteChar); + } + + static void AppendSpanFormattable(this ref ValueStringBuilder vsb, ushort value) + { + vsb.Append(value.ToString()); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/AssemblySymbol.cs b/src/IKVM.CoreLib/Symbols/AssemblySymbol.cs new file mode 100644 index 0000000000..b0aa4a1c67 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/AssemblySymbol.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +using IKVM.CoreLib.Collections; + +namespace IKVM.CoreLib.Symbols +{ + + public abstract class AssemblySymbol : Symbol, ICustomAttributeProviderInternal + { + + CustomAttributeImpl _customAttributes; + + /// + /// Initializes a new instance. + /// + /// + public AssemblySymbol(SymbolContext context) : + base(context) + { + _customAttributes = new CustomAttributeImpl(context, this); + } + + /// + /// Gets an for this assembly. + /// + /// + public abstract AssemblyIdentity Identity { get; } + + /// + /// Gets the display name of the assembly. + /// + public string FullName => Identity.FullName; + + /// + /// Gets a string representing the version of the common language runtime (CLR) saved in the file containing the manifest. + /// + public abstract string ImageRuntimeVersion { get; } + + /// + /// Gets the full path or UNC location of the loaded file that contains the manifest. + /// + public abstract string Location { get; } + + /// + /// Gets the module that contains the manifest for the current assembly. + /// + public abstract ModuleSymbol ManifestModule { get; } + + /// + /// Gets the entry point of this assembly. + /// + public abstract MethodSymbol? EntryPoint { get; } + + /// + /// Gets a collection of the types defined in this assembly. + /// + public IEnumerable DefinedTypes => GetTypes(); + + /// + /// Gets a collection of the public types defined in this assembly that are visible outside the assembly. + /// + public IEnumerable ExportedTypes => GetExportedTypes(); + + /// + /// Gets a collection that contains the modules in this assembly. + /// + public ImmutableArray Modules => GetModules(); + + /// + /// Returns true if the symbol is missing. + /// + public abstract bool IsMissing { get; } + + /// + /// Gets the public types defined in this assembly that are visible outside the assembly. + /// + /// + public IEnumerable GetExportedTypes() + { + foreach (var type in GetTypes()) + if (IsVisibleOutsideAssembly(type)) + yield return type; + } + + /// + /// Returns true if the specified type is visible outside the assembly. + /// + /// + /// + bool IsVisibleOutsideAssembly(TypeSymbol type) + { + var visibility = type.Attributes & TypeAttributes.VisibilityMask; + if (visibility == TypeAttributes.Public) + return true; + + if (visibility == TypeAttributes.NestedPublic) + return IsVisibleOutsideAssembly(type.DeclaringType!); + + return false; + } + + /// + /// Gets the specified module in this assembly. + /// + /// + /// + public ModuleSymbol? GetModule(string name) + { + if (name is null) + throw new ArgumentNullException(nameof(name)); + + return GetModules() + .Where(i => i.Name == name) + .SingleOrDefaultOrThrow(() => new AmbiguousMatchException()); + } + + /// + /// Gets all the modules that are part of this assembly. + /// + /// + public abstract ImmutableArray GetModules(); + + /// + /// Gets the objects for all the assemblies referenced by this assembly. + /// + /// + public abstract ImmutableArray GetReferencedAssemblies(); + + /// + /// Gets the Type object with the specified name in the assembly instance. + /// + /// + /// + public TypeSymbol? GetType(string name) => GetType(name, false); + + /// + /// Gets the object with the specified name in the assembly instance and optionally throws an exception if the type is not found. + /// + /// + /// + /// + public TypeSymbol? GetType(string name, bool throwOnError) + { + foreach (var module in GetModules()) + if (module.GetType(name, false) is TypeSymbol type) + return type; + + if (throwOnError) + throw new TypeLoadException(); + + return null; + } + + /// + /// Gets all types defined in this assembly. + /// + /// + public IEnumerable GetTypes() + { + foreach (var module in GetModules()) + foreach (var type in module.GetTypes()) + yield return type; + } + + /// + /// Loads the specified manifest resource from this assembly. + /// + /// + /// + public abstract ManifestResourceInfo? GetManifestResourceInfo(string resourceName); + + /// + /// Loads the specified manifest resource from this assembly. + /// + /// + /// + public abstract Stream? GetManifestResourceStream(string name); + + /// + /// Loads the specified manifest resource, scoped by the namespace of the specified type, from this assembly. + /// + /// + /// + /// + public Stream? GetManifestResourceStream(TypeSymbol type, string name) + { + using var sb = new ValueStringBuilder(stackalloc char[256]); + + if (type == null) + { + if (name == null) + throw new ArgumentNullException(nameof(type)); + } + else + { + var ns = type.Namespace; + if (ns != null) + { + sb.Append(ns); + + if (name != null) + sb.Append(Type.Delimiter); + } + } + + if (name != null) + sb.Append(name); + + return GetManifestResourceStream(sb.ToString()); + } + + /// + internal abstract ImmutableArray GetDeclaredCustomAttributes(); + + /// + ImmutableArray ICustomAttributeProviderInternal.GetDeclaredCustomAttributes() => GetDeclaredCustomAttributes(); + + /// + ICustomAttributeProviderInternal? ICustomAttributeProviderInternal.GetInheritedCustomAttributeProvider() => null; + + /// + public IEnumerable GetCustomAttributes(bool inherit = false) => _customAttributes.GetCustomAttributes(inherit); + + /// + public IEnumerable GetCustomAttributes(TypeSymbol attributeType, bool inherit = false) => _customAttributes.GetCustomAttributes(attributeType, inherit); + + /// + public CustomAttribute? GetCustomAttribute(TypeSymbol attributeType, bool inherit = false) => _customAttributes.GetCustomAttribute(attributeType, inherit); + + /// + public bool IsDefined(TypeSymbol attributeType, bool inherit = false) => _customAttributes.IsDefined(attributeType, inherit); + + /// + public override string ToString() => FullName ?? ""; + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/ByRefTypeSymbol.cs b/src/IKVM.CoreLib/Symbols/ByRefTypeSymbol.cs new file mode 100644 index 0000000000..864c28007c --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/ByRefTypeSymbol.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; + +namespace IKVM.CoreLib.Symbols +{ + + class ByRefTypeSymbol : HasElementSymbol + { + + /// + /// Initializes a new instance. + /// + /// + /// + public ByRefTypeSymbol(SymbolContext context, TypeSymbol elementType) : + base(context, elementType) + { + + } + + /// + protected sealed override string NameSuffix => "&"; + + /// + public sealed override TypeAttributes Attributes => TypeAttributes.Public; + + /// + public sealed override TypeSymbol? BaseType => null; + + /// + public sealed override bool IsByRef => true; + + /// + public sealed override int GetArrayRank() + { + throw new NotSupportedException(); + } + + /// + internal sealed override ImmutableArray GetDeclaredInterfaces() + { + return ImmutableArray.Empty; + } + + /// + internal sealed override ImmutableArray GetDeclaredMethods() + { + return ImmutableArray.Empty; + } + + internal override MethodImplementationMapping GetMethodImplementations() + { + return MethodImplementationMapping.CreateEmpty(this); + } + + /// + internal sealed override ImmutableArray GetDeclaredCustomAttributes() + { + return ImmutableArray.Empty; + } + + /// + internal sealed override TypeSymbol Specialize(GenericContext context) + { + if (ContainsGenericParameters == false) + return this; + + var elementType = GetElementType() ?? throw new InvalidOperationException(); + return elementType.Specialize(context).MakeByRefType(); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/CustomAttribute.cs b/src/IKVM.CoreLib/Symbols/CustomAttribute.cs new file mode 100644 index 0000000000..a78b7d7670 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/CustomAttribute.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Immutable; +using System.Text; + +namespace IKVM.CoreLib.Symbols +{ + + public readonly record struct CustomAttribute( + TypeSymbol AttributeType, + MethodSymbol Constructor, + ImmutableArray ConstructorArguments, + ImmutableArray NamedArguments) + { + + /// + /// Initializes an instance of the interface given the constructor for the custom attribute and the arguments to the constructor. + /// + /// + /// + /// + public static CustomAttribute Create(MethodSymbol ctor, ImmutableArray constructorArgs) + { + return new CustomAttribute( + ctor.DeclaringType ?? throw new InvalidOperationException(), + ctor, + PackTypedArgs(ctor.ParameterTypes, constructorArgs), + ImmutableArray.Empty); + } + + /// + /// Initializes an instance of the interface given the constructor for the custom attribute, the arguments to the constructor, and a set of named field/value pairs. + /// + /// + /// + /// + /// + public static CustomAttribute Create(MethodSymbol ctor, ImmutableArray constructorArgs, ImmutableArray namedFields, ImmutableArray fieldValues) + { + return new CustomAttribute( + ctor.DeclaringType ?? throw new InvalidOperationException(), + ctor, + PackTypedArgs(ctor.ParameterTypes, constructorArgs), + PackNamedArgs([], [], namedFields, fieldValues)); + } + + /// + /// Initializes an instance of the interface given the constructor for the custom attribute, the arguments to the constructor, and a set of named property or value pairs. + /// + /// + /// + /// + /// + public static CustomAttribute Create(MethodSymbol ctor, ImmutableArray constructorArgs, ImmutableArray namedProperties, ImmutableArray propertyValues) + { + return new CustomAttribute( + ctor.DeclaringType ?? throw new InvalidOperationException(), + ctor, + PackTypedArgs(ctor.ParameterTypes, constructorArgs), + PackNamedArgs(namedProperties, propertyValues, [], [])); + } + + /// + /// Initializes an instance of the interface given the constructor for the custom attribute, the arguments to the constructor, a set of named property or value pairs, and a set of named field or value pairs. + /// + /// + /// + /// + /// + /// + /// + public static CustomAttribute Create(MethodSymbol ctor, ImmutableArray constructorArgs, ImmutableArray namedProperties, ImmutableArray propertyValues, ImmutableArray namedFields, ImmutableArray fieldValues) + { + return new CustomAttribute( + ctor.DeclaringType ?? throw new InvalidOperationException(), + ctor, + PackTypedArgs(ctor.ParameterTypes, constructorArgs), + PackNamedArgs(namedProperties, propertyValues, namedFields, fieldValues)); + } + + /// + /// Packs the types as typed arguments. + /// + /// + /// + /// + /// + /// + static ImmutableArray PackTypedArgs(ImmutableArray types, ImmutableArray values) + { + if (types.IsDefault) + throw new ArgumentNullException(nameof(types)); + if (values.IsDefault) + throw new ArgumentNullException(nameof(values)); + if (types.Length != values.Length) + throw new ArgumentException(); + + var a = ImmutableArray.CreateBuilder(types.Length); + for (int i = 0; i < types.Length; i++) + a.Add(PackTypedArg(types[i], values[i])); + + return a.DrainToImmutable(); + } + + /// + /// Packs the type as a typed argument. + /// + /// + /// + /// + static CustomAttributeTypedArgument PackTypedArg(TypeSymbol type, object? value) + { + return new CustomAttributeTypedArgument(type, value); + } + + /// + /// Packages the members and args as a named argument. + /// + /// + /// + /// + static ImmutableArray PackNamedArgs(ImmutableArray namedProperties, ImmutableArray propertyValues, ImmutableArray namedFields, ImmutableArray fieldValues) + { + var a = ImmutableArray.CreateBuilder(namedProperties.Length + namedFields.Length); + for (int i = 0; i < namedProperties.Length; i++) + a.Add(PackNamedArg(namedProperties[i], propertyValues[i])); + for (int i = 0; i < namedFields.Length; i++) + a.Add(PackNamedArg(namedFields[i], fieldValues[i])); + + return a.DrainToImmutable(); + } + + /// + /// Packs the property and arg as a named argument. + /// + /// + /// + /// + static CustomAttributeNamedArgument PackNamedArg(PropertySymbol property, object? v) + { + return new CustomAttributeNamedArgument(property, PackTypedArg(property.PropertyType, v)); + } + + /// + /// Packs the field and arg as a named argument. + /// + /// + /// + /// + static CustomAttributeNamedArgument PackNamedArg(FieldSymbol field, object? v) + { + return new CustomAttributeNamedArgument(field, PackTypedArg(field.FieldType, v)); + } + + /// + public override string ToString() + { + using var vsb = new ValueStringBuilder(stackalloc char[256]); + + vsb.Append('['); + vsb.Append(Constructor.DeclaringType!.FullName); + vsb.Append('('); + + var first = true; + + var constructorArguments = ConstructorArguments; + var constructorArgumentsCount = constructorArguments.Length; + for (int i = 0; i < constructorArgumentsCount; i++) + { + if (!first) + vsb.Append(", "); + + vsb.Append(constructorArguments[i].ToString()); + first = false; + } + + var namedArguments = NamedArguments; + var namedArgumentsCount = namedArguments.Length; + for (int i = 0; i < namedArgumentsCount; i++) + { + if (!first) + vsb.Append(", "); + + vsb.Append(namedArguments[i].ToString()); + first = false; + } + + vsb.Append(")]"); + + return vsb.ToString(); + } + + } + + +} diff --git a/src/IKVM.CoreLib/Symbols/CustomAttributeImpl.cs b/src/IKVM.CoreLib/Symbols/CustomAttributeImpl.cs new file mode 100644 index 0000000000..13bdee1111 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/CustomAttributeImpl.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; + +using IKVM.CoreLib.Collections; + +namespace IKVM.CoreLib.Symbols +{ + + /// + /// Provides implementations in support of . + /// + struct CustomAttributeImpl + { + + readonly SymbolContext _context; + readonly ICustomAttributeProviderInternal _provider; + + TypeSymbol? _attributeUsageAttributeType; + ImmutableArray _declaredCustomAttributes; + ImmutableArray _declaredAndInheritedCustomAttributes; + + /// + /// Initializes a new instance. + /// + public CustomAttributeImpl(SymbolContext context, ICustomAttributeProviderInternal provider) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); + } + + /// + /// Returns the custom attributes applied to this member. + /// + /// + /// + public ImmutableArray GetCustomAttributes(bool inherit) + { + if (inherit == false) + { + if (_declaredCustomAttributes.IsDefault) + ImmutableInterlocked.InterlockedInitialize(ref _declaredCustomAttributes, _provider.GetDeclaredCustomAttributes()); + + return _declaredCustomAttributes; + } + else + { + if (_declaredAndInheritedCustomAttributes.IsDefault) + ImmutableInterlocked.InterlockedInitialize(ref _declaredAndInheritedCustomAttributes, ComputeDeclaredAndInheritedCustomAttributes()); + + return _declaredAndInheritedCustomAttributes; + } + } + + /// + /// Computes the custom attributes that are applied to this member, including those which are inherited. + /// + /// + ImmutableArray ComputeDeclaredAndInheritedCustomAttributes() + { + var list = _provider.GetDeclaredCustomAttributes(); + + // move through the inherited custom attribute providers + for (var provider = _provider.GetInheritedCustomAttributeProvider(); provider != null; provider = provider.GetInheritedCustomAttributeProvider()) + foreach (var customAttribute in provider.GetDeclaredCustomAttributes()) + if (IsInheritable(customAttribute)) + list = list.Add(customAttribute); + + return list; + } + + /// + /// Returns true if the specified is inherited. + /// + /// + /// + /// + bool IsInheritable(CustomAttribute customAttribute) + { + _attributeUsageAttributeType ??= _context.ResolveCoreType(typeof(AttributeUsageAttribute).FullName!); + if (_attributeUsageAttributeType == null) + throw new InvalidOperationException("Could not find core type System.AttributeUsageAttribute."); + + // AttributeUsageAttribute is inheritable; this prevents recursion + if (customAttribute.AttributeType == _attributeUsageAttributeType) + return true; + + // attribute usage should decorate the attribute type, either here or directly + var attributeUsageAttribute = GetNearestInheritedCustomAttribute(customAttribute.AttributeType, _attributeUsageAttributeType); + if (attributeUsageAttribute == null) + throw new InvalidOperationException(); + + // return whether the Inherited property is set + return GetInheritedValue(attributeUsageAttribute.Value); + } + + /// + /// Gets the nearest custom attribute of the specified type. + /// + /// + /// + /// + readonly CustomAttribute? GetNearestInheritedCustomAttribute(ICustomAttributeProviderInternal provider, TypeSymbol attributeType) + { + foreach (var customAttribute in provider.GetDeclaredCustomAttributes()) + if (customAttribute.AttributeType == attributeType) + return customAttribute; + + for (ICustomAttributeProviderInternal? baseProvider = provider.GetInheritedCustomAttributeProvider(); baseProvider != null; baseProvider = baseProvider.GetInheritedCustomAttributeProvider()) + foreach (var customAttribute in provider.GetDeclaredCustomAttributes()) + if (customAttribute.AttributeType == attributeType) + return customAttribute; + + return null; + } + + /// + /// Gets the boolean value of the property. + /// + /// + /// + readonly bool GetInheritedValue(CustomAttribute attributeUsageAttribute) + { + foreach (var i in attributeUsageAttribute.NamedArguments) + if (i.MemberInfo is PropertySymbol property && property.Name == nameof(AttributeUsageAttribute.Inherited) && (bool?)i.TypedValue.Value == true) + return true; + + return false; + } + + /// + /// Returns the custom attributes applied to this member. + /// + /// + /// + /// + public ImmutableArray GetCustomAttributes(TypeSymbol attributeType, bool inherit) + { + return GetCustomAttributes(inherit).Where(i => i.AttributeType == attributeType).ToImmutableArray(); + + } + + /// + /// Retrieves a custom attribute of a specified type applied to this member. + /// + /// + /// + /// + public CustomAttribute? GetCustomAttribute(TypeSymbol attributeType, bool inherit) + { + return GetCustomAttributes(attributeType, inherit).Select(static i => new CustomAttribute?(i)).SingleOrDefaultOrThrow(static () => new AmbiguousMatchException()); + } + + /// + /// Determines whether any custom attributes of a specified type are applied to an assembly, module, type member, or method parameter. + /// + /// + /// + /// + public bool IsDefined(TypeSymbol attributeType, bool inherit) + { + return GetCustomAttributes(attributeType, inherit).Any(); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/CustomAttributeNamedArgument.cs b/src/IKVM.CoreLib/Symbols/CustomAttributeNamedArgument.cs new file mode 100644 index 0000000000..8985458255 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/CustomAttributeNamedArgument.cs @@ -0,0 +1,20 @@ +namespace IKVM.CoreLib.Symbols +{ + + public readonly record struct CustomAttributeNamedArgument(MemberSymbol MemberInfo, CustomAttributeTypedArgument TypedValue) + { + + /// + /// Gets the type of the argument. + /// + internal TypeSymbol ArgumentType => MemberInfo is FieldSymbol fi ? fi.FieldType : ((PropertySymbol)MemberInfo).PropertyType; + + /// + public override string? ToString() + { + return $"{MemberInfo.Name} = {TypedValue.ToString(ArgumentType != ArgumentType.Context.ResolveCoreType("System.Object"))}"; + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/CustomAttributeSymbol.cs b/src/IKVM.CoreLib/Symbols/CustomAttributeSymbol.cs deleted file mode 100644 index dbdab0f3cb..0000000000 --- a/src/IKVM.CoreLib/Symbols/CustomAttributeSymbol.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Immutable; - -namespace IKVM.CoreLib.Symbols -{ - - readonly record struct CustomAttributeSymbol( - ITypeSymbol AttributeType, - IConstructorSymbol Constructor, - ImmutableArray ConstructorArguments, - ImmutableArray NamedArguments); - -} diff --git a/src/IKVM.CoreLib/Symbols/CustomAttributeSymbolNamedArgument.cs b/src/IKVM.CoreLib/Symbols/CustomAttributeSymbolNamedArgument.cs deleted file mode 100644 index c01598e24c..0000000000 --- a/src/IKVM.CoreLib/Symbols/CustomAttributeSymbolNamedArgument.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace IKVM.CoreLib.Symbols -{ - - readonly record struct CustomAttributeSymbolNamedArgument( - bool IsField, - IMemberSymbol MemberInfo, - string MemberName, - CustomAttributeSymbolTypedArgument TypedValue); - -} diff --git a/src/IKVM.CoreLib/Symbols/CustomAttributeSymbolTypedArgument.cs b/src/IKVM.CoreLib/Symbols/CustomAttributeSymbolTypedArgument.cs deleted file mode 100644 index 0f56df5a32..0000000000 --- a/src/IKVM.CoreLib/Symbols/CustomAttributeSymbolTypedArgument.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace IKVM.CoreLib.Symbols -{ - - readonly record struct CustomAttributeSymbolTypedArgument( - ITypeSymbol ArgumentType, - object? Value); - -} diff --git a/src/IKVM.CoreLib/Symbols/CustomAttributeTypedArgument.cs b/src/IKVM.CoreLib/Symbols/CustomAttributeTypedArgument.cs new file mode 100644 index 0000000000..4d1bf97f17 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/CustomAttributeTypedArgument.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Text; + +namespace IKVM.CoreLib.Symbols +{ + + public readonly record struct CustomAttributeTypedArgument(TypeSymbol ArgumentType, object? Value) + { + + public override string ToString() => ToString(false); + + /// + /// Returns a string reprsentation of this typed argument. + /// + /// + /// + internal string ToString(bool typed) + { + if (ArgumentType is null) + return base.ToString()!; + + if (ArgumentType.IsEnum) + return typed ? $"{Value}" : $"({ArgumentType.FullName}){Value}"; + + if (Value is null) + return typed ? "null" : $"({ArgumentType.Name})null"; + + if (ArgumentType == ArgumentType.Context.ResolveCoreType("System.String")) + return $"\"{Value}\""; + + if (ArgumentType == ArgumentType.Context.ResolveCoreType("System.Char")) + return $"'{Value}'"; + + if (ArgumentType == ArgumentType.Context.ResolveCoreType("System.Type")) + return $"typeof({((TypeSymbol)Value!).FullName})"; + + if (ArgumentType.IsArray) + { + var array = (IReadOnlyList)Value!; + var elementType = ArgumentType.GetElementType()!; + + using var result = new ValueStringBuilder(stackalloc char[256]); + result.Append("new "); + result.Append(elementType.IsEnum ? elementType.FullName : elementType.Name); + result.Append('['); + var count = array.Count; + result.Append(count.ToString()); + result.Append("] { "); + + for (int i = 0; i < count; i++) + { + if (i != 0) + result.Append(", "); + + result.Append(array[i].ToString(elementType != ArgumentType.Context.ResolveCoreType("System.Object"))); + } + + result.Append(" }"); + + return result.ToString(); + } + + return typed ? $"{Value}" : $"({ArgumentType.Name}){Value}"; + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefaultBinder.cs b/src/IKVM.CoreLib/Symbols/DefaultBinder.cs new file mode 100644 index 0000000000..4e5177dfa2 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefaultBinder.cs @@ -0,0 +1,635 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Reflection; + +namespace IKVM.CoreLib.Symbols +{ + + /// + /// Provides methods to select various symbols based on requirements. + /// + internal class DefaultBinder + { + + readonly SymbolContext _context; + + TypeSymbol? _lazyObjectType; + TypeSymbol? _lazyIntPtrType; + TypeSymbol? _lazyUIntPtrType; + + /// + /// Initializes a new instance. + /// + /// + /// + public DefaultBinder(SymbolContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + /// + /// Gets the symbol for . + /// + TypeSymbol ObjectType => _lazyObjectType ??= _context.ResolveCoreType("System.Object"); + + /// + /// Gets the symbol for . + /// + TypeSymbol IntPtrType => _lazyIntPtrType ??= _context.ResolveCoreType("System.IntPtr"); + + /// + /// Gets the symbol for . + /// + TypeSymbol UIntPtrType => _lazyUIntPtrType ??= _context.ResolveCoreType("System.UIntPtr"); + + /// + /// Given a set of methods that match the base criteria, select a method based upon an array of parameter types. This + /// method should return null if no method matches the criteria. + /// + public MethodSymbol? SelectMethod(IReadOnlyList match, BindingFlags bindingFlags, TypeSymbolSelectorList types, ImmutableArray modifiers) + { + // we don't automatically jump out on exact match + if (match == null || match.Count == 0) + throw new ArgumentException("Unexpected empty array.", nameof(match)); + + var candidates = new List(match); + + // find all the methods that can be described by the types parameter + // remove all of them that cannot + int curIdx = 0; + for (var i = 0; i < candidates.Count; i++) + { + var par = candidates[i].Parameters; + if (par.Length != types.Indexes.Length) + continue; + + int j; + for (j = 0; j < types.Indexes.Length; j++) + if (types.Indexes[j].Match(_context, par[j].ParameterType) == false) + break; + + if (j == types.Indexes.Length) + candidates[curIdx++] = candidates[i]; + } + + if (curIdx == 0) + return null; + if (curIdx == 1) + return candidates[0]; + + // walk all of the methods looking the most specific method to invoke + int currentMin = 0; + var ambig = false; + + var paramOrder = types.Indexes.Length > 0 ? stackalloc int[types.Indexes.Length] : Array.Empty(); + for (var i = 0; i < types.Indexes.Length; i++) + paramOrder[i] = i; + + for (var i = 1; i < curIdx; i++) + { + int newMin = FindMostSpecificMethod(candidates[currentMin], paramOrder, null, candidates[i], paramOrder, null, types); + if (newMin == 0) + ambig = true; + else + { + if (newMin == 2) + { + ambig = false; + currentMin = i; + } + } + } + + var bestMatch = candidates[currentMin]; + if (ambig) + throw new AmbiguousMatchException($"Ambiguous match found for '{bestMatch.DeclaringType} {bestMatch}'."); + + return bestMatch; + } + + /// + /// Selects the property that matches the base criteria. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PropertySymbol? SelectProperty(BindingFlags bindingAttr, IReadOnlyList match, TypeSymbol? returnType, TypeSymbolSelectorList indexes, ImmutableArray modifiers) + { + // if indexes is present every element must be non-null + if (indexes.Indexes.IsDefault == false) + foreach (var index in indexes.Indexes) + throw new ArgumentNullException(nameof(index)); + + if (match == null || match.Count == 0) + throw new ArgumentException(nameof(match)); + + var candidates = new List(match); + + int i, j = 0; + + // Find all the properties that can be described by type indexes parameter + int curIdx = 0; + for (i = 0; i < candidates.Count; i++) + { + if (indexes.Indexes.IsDefault == false) + { + var par = candidates[i].GetIndexParameters(); + if (par.Length != indexes.Indexes.Length) + continue; + + for (j = 0; j < indexes.Indexes.Length; j++) + if (indexes.Indexes[j].Match(_context, par[j].ParameterType) == false) + break; + } + + if (indexes.Indexes.IsDefault || j == indexes.Indexes.Length) + { + if (returnType != null) + { + if (candidates[i].PropertyType.IsPrimitive) + { + if (CanChangePrimitive(returnType, candidates[i].PropertyType) == false) + continue; + } + else + { + if (candidates[i].PropertyType.IsAssignableFrom(returnType) == false) + continue; + } + } + + candidates[curIdx++] = candidates[i]; + } + } + + if (curIdx == 0) + return null; + + if (curIdx == 1) + return candidates[0]; + + int currentMin = 0; + var ambig = false; + + var paramOrder = indexes.Indexes.IsDefault == false && indexes.Indexes.Length > 0 ? stackalloc int[indexes.Indexes.Length] : Array.Empty(); + for (i = 0; i < paramOrder.Length; i++) + paramOrder[i] = i; + + for (i = 1; i < curIdx; i++) + { + int newMin = FindMostSpecificType(candidates[currentMin].PropertyType, candidates[i].PropertyType, returnType); + if (newMin == 0 && indexes.Indexes.IsDefault == false) + newMin = FindMostSpecific(candidates[currentMin].GetIndexParameters(), paramOrder, null, candidates[i].GetIndexParameters(), paramOrder, null, indexes); + + if (newMin == 0) + { + newMin = FindMostSpecificProperty(candidates[currentMin], candidates[i]); + if (newMin == 0) + ambig = true; + } + + if (newMin == 2) + { + ambig = false; + currentMin = i; + } + } + + var bestMatch = candidates[currentMin]; + if (ambig) + throw new AmbiguousMatchException(bestMatch.ToString()); + + return bestMatch; + } + + /// + /// Returns any exact bindings that may exist. + /// + /// + /// + /// + /// + public MethodSymbol? ExactBinding(IReadOnlyList match, ImmutableArray types) + { + if (match is null) + throw new ArgumentNullException(nameof(match)); + + var aExactMatches = new MethodSymbol[match.Count]; + int cExactMatches = 0; + + for (int i = 0; i < match.Count; i++) + { + var par = match[i].Parameters; + if (par.Length == 0) + continue; + + int j; + for (j = 0; j < types.Length; j++) + { + var pCls = par[j].ParameterType; + + // If the classes exactly match continue + if (!pCls.Equals(types[j])) + break; + } + + if (j < types.Length) + continue; + + // Add the exact match to the array of exact matches. + aExactMatches[cExactMatches] = match[i]; + cExactMatches++; + } + + if (cExactMatches == 0) + return null; + + if (cExactMatches == 1) + return aExactMatches[0]; + + return FindMostDerivedNewSlotMeth(aExactMatches, cExactMatches); + } + + /// + /// Returns any exact bindings that may exist. + /// + /// + /// + /// + /// + /// + /// + public PropertySymbol? ExactPropertyBinding(IReadOnlyList match, TypeSymbol? returnType, ImmutableArray types) + { + if (match == null) + throw new ArgumentNullException(nameof(match)); + + PropertySymbol? bestMatch = null; + + for (int i = 0; i < match.Count; i++) + { + var parameter = match[i].GetIndexParameters(); + + int j; + for (j = 0; j < types.Length; j++) + { + var parameterType = parameter[j].ParameterType; + + // If the classes exactly match continue + if (parameterType != types![j]) + break; + } + + if (j < types.Length) + continue; + + if (returnType != null && returnType != match[i].PropertyType) + continue; + + if (bestMatch != null) + throw new AmbiguousMatchException(bestMatch.ToString()); + + bestMatch = match[i]; + } + + return bestMatch; + } + + int FindMostSpecific(ImmutableArray p1, ReadOnlySpan paramOrder1, TypeSymbol? paramArrayType1, + ImmutableArray p2, ReadOnlySpan paramOrder2, TypeSymbol? paramArrayType2, + TypeSymbolSelectorList types) + { + // a method using params is always less specific than one not using params + if (paramArrayType1 != null && paramArrayType2 == null) + return 2; + if (paramArrayType2 != null && paramArrayType1 == null) + return 1; + + // now either p1 and p2 both use params or neither does. + var p1Less = false; + var p2Less = false; + + for (int i = 0; i < types.Indexes.Length; i++) + { + TypeSymbol c1, c2; + + // If a param array is present, then either + // the user re-ordered the parameters in which case + // the argument to the param array is either an array + // in which case the params is conceptually ignored and so paramArrayType1 == null + // or the argument to the param array is a single element + // in which case paramOrder[i] == p1.Length - 1 for that element + // or the user did not re-order the parameters in which case + // the paramOrder array could contain indexes larger than p.Length - 1 (see VSW 577286) + // so any index >= p.Length - 1 is being put in the param array + + if (paramArrayType1 != null && paramOrder1[i] >= p1.Length - 1) + c1 = paramArrayType1; + else + c1 = p1[paramOrder1[i]].ParameterType; + + if (paramArrayType2 != null && paramOrder2[i] >= p2.Length - 1) + c2 = paramArrayType2; + else + c2 = p2[paramOrder2[i]].ParameterType; + + if (c1 == c2) + continue; + + switch (FindMostSpecificType(c1, c2, types.Indexes[i])) + { + case 0: return 0; + case 1: p1Less = true; break; + case 2: p2Less = true; break; + } + } + + // Two way p1Less and p2Less can be equal. All the arguments are the + // same they both equal false, otherwise there were things that both + // were the most specific type on.... + if (p1Less == p2Less) + { + return 0; + } + else + { + return p1Less ? 1 : 2; + } + } + + /// + /// Finds which type is the most specific. + /// + /// + /// + /// + /// + int FindMostSpecificType(TypeSymbol c1, TypeSymbol c2, TypeSymbolSelector t) + { + return t.FindMostSpecific(_context, c1, c2); + } + + int FindMostSpecificMethod(MethodSymbol m1, ReadOnlySpan paramOrder1, TypeSymbol? paramArrayType1, + MethodSymbol m2, ReadOnlySpan paramOrder2, TypeSymbol? paramArrayType2, + TypeSymbolSelectorList types) + { + // Find the most specific method based on the parameters. + int res = FindMostSpecific(m1.Parameters, paramOrder1, paramArrayType1, + m2.Parameters, paramOrder2, paramArrayType2, types); + + // If the match was not ambiguous then return the result. + if (res != 0) + return res; + + // Check to see if the methods have the exact same name and signature. + if (CompareMethodSig(m1, m2)) + { + // Determine the depth of the declaring types for both methods. + var hierarchyDepth1 = GetHierarchyDepth(m1.DeclaringType!); + var hierarchyDepth2 = GetHierarchyDepth(m2.DeclaringType!); + + // the most derived method is the most specific one + if (hierarchyDepth1 == hierarchyDepth2) + return 0; + + if (hierarchyDepth1 < hierarchyDepth2) + return 2; + + return 1; + } + + // The match is ambiguous. + return 0; + } + + int FindMostSpecificField(FieldSymbol cur1, FieldSymbol cur2) + { + // Check to see if the fields have the same name. + if (cur1.Name == cur2.Name) + { + int hierarchyDepth1 = GetHierarchyDepth(cur1.DeclaringType!); + int hierarchyDepth2 = GetHierarchyDepth(cur2.DeclaringType!); + + if (hierarchyDepth1 == hierarchyDepth2) + { + Debug.Assert(cur1.IsStatic != cur2.IsStatic, "hierarchyDepth1 == hierarchyDepth2"); + return 0; + } + else if (hierarchyDepth1 < hierarchyDepth2) + return 2; + else + return 1; + } + + // The match is ambiguous. + return 0; + } + + int FindMostSpecificProperty(PropertySymbol cur1, PropertySymbol cur2) + { + // Check to see if the fields have the same name. + if (cur1.Name == cur2.Name) + { + int hierarchyDepth1 = GetHierarchyDepth(cur1.DeclaringType!); + int hierarchyDepth2 = GetHierarchyDepth(cur2.DeclaringType!); + + if (hierarchyDepth1 == hierarchyDepth2) + { + return 0; + } + else if (hierarchyDepth1 < hierarchyDepth2) + return 2; + else + return 1; + } + + // The match is ambiguous. + return 0; + } + + /// + /// Returns true if the two methods have the exact same signature. + /// + /// + /// + /// + public static bool CompareMethodSig(MethodSymbol m1, MethodSymbol m2) + { + var params1 = m1.Parameters; + var params2 = m2.Parameters; + + if (params1.Length != params2.Length) + return false; + + for (int i = 0; i < params1.Length; i++) + if (params1[i].ParameterType != params2[i].ParameterType) + return false; + + return true; + } + + /// + /// Gets the depth of the type within the type hierarchy. + /// + /// + /// + int GetHierarchyDepth(TypeSymbol type) + { + int depth = 0; + + for (var cType = (TypeSymbol?)type; cType != null; cType = cType.BaseType) + depth++; + + return depth; + } + + internal MethodSymbol? FindMostDerivedNewSlotMeth(ReadOnlySpan match, int cMatches) + { + int deepestHierarchy = 0; + MethodSymbol? methWithDeepestHierarchy = null; + + for (int i = 0; i < cMatches; i++) + { + // Calculate the depth of the hierarchy of the declaring type of the current method. + int currentHierarchyDepth = GetHierarchyDepth(match[i].DeclaringType!); + + // The two methods have the same name, signature, and hierarchy depth. + // This can only happen if at least one is vararg or generic. + if (currentHierarchyDepth == deepestHierarchy) + throw new AmbiguousMatchException(methWithDeepestHierarchy!.ToString()); + + // Check to see if this method is on the most derived class. + if (currentHierarchyDepth > deepestHierarchy) + { + deepestHierarchy = currentHierarchyDepth; + methWithDeepestHierarchy = match[i]; + } + } + + return methWithDeepestHierarchy; + } + + // This method will create the mapping between the Parameters and the underlying + // data based upon the names array. The names array is stored in the same order + // as the values and maps to the parameters of the method. We store the mapping + // from the parameters to the names in the paramOrder array. All parameters that + // don't have matching names are then stored in the array in order. + bool CreateParamOrder(int[] paramOrder, ImmutableArray pars, string[] names) + { + var used = new bool[pars.Length]; + + // Mark which parameters have not been found in the names list + for (var i = 0; i < pars.Length; i++) + paramOrder[i] = -1; + + // Find the parameters with names. + for (var i = 0; i < names.Length; i++) + { + int j; + for (j = 0; j < pars.Length; j++) + { + if (names[i].Equals(pars[j].Name)) + { + paramOrder[j] = i; + used[i] = true; + break; + } + } + + // This is an error condition. The name was not found. This method must not match what we sent. + if (j == pars.Length) + return false; + } + + // Now we fill in the holes with the parameters that are unused. + int pos = 0; + for (int i = 0; i < pars.Length; i++) + { + if (paramOrder[i] == -1) + { + for (; pos < pars.Length; pos++) + { + if (!used[pos]) + { + paramOrder[i] = pos; + pos++; + break; + } + } + } + } + + return true; + } + + /// + /// Returns true if the given source primitive type can be converted to the given target primitive type. + /// + /// + /// + /// + internal bool CanChangePrimitive(TypeSymbol source, TypeSymbol target) + { + if ((source == IntPtrType && target == IntPtrType) || (source == UIntPtrType && target == UIntPtrType)) + return true; + + var widerCodes = PrimitiveConversions[(int)source.TypeCode]; + var targetCode = (Primitives)(1 << (int)target.TypeCode); + + return (widerCodes & targetCode) != 0; + } + + static ReadOnlySpan PrimitiveConversions => + [ + /* Empty */ 0, // not primitive + /* Object */ 0, // not primitive + /* DBNull */ 0, // not primitive + /* Boolean */ Primitives.Boolean, + /* Char */ Primitives.Char | Primitives.UInt16 | Primitives.UInt32 | Primitives.Int32 | Primitives.UInt64 | Primitives.Int64 | Primitives.Single | Primitives.Double, + /* SByte */ Primitives.SByte | Primitives.Int16 | Primitives.Int32 | Primitives.Int64 | Primitives.Single | Primitives.Double, + /* Byte */ Primitives.Byte | Primitives.Char | Primitives.UInt16 | Primitives.Int16 | Primitives.UInt32 | Primitives.Int32 | Primitives.UInt64 | Primitives.Int64 | Primitives.Single | Primitives.Double, + /* Int16 */ Primitives.Int16 | Primitives.Int32 | Primitives.Int64 | Primitives.Single | Primitives.Double, + /* UInt16 */ Primitives.UInt16 | Primitives.UInt32 | Primitives.Int32 | Primitives.UInt64 | Primitives.Int64 | Primitives.Single | Primitives.Double, + /* Int32 */ Primitives.Int32 | Primitives.Int64 | Primitives.Single | Primitives.Double, + /* UInt32 */ Primitives.UInt32 | Primitives.UInt64 | Primitives.Int64 | Primitives.Single | Primitives.Double, + /* Int64 */ Primitives.Int64 | Primitives.Single | Primitives.Double, + /* UInt64 */ Primitives.UInt64 | Primitives.Single | Primitives.Double, + /* Single */ Primitives.Single | Primitives.Double, + /* Double */ Primitives.Double, + /* Decimal */ Primitives.Decimal, + /* DateTime */ Primitives.DateTime, + /* [Unused] */ 0, + /* String */ Primitives.String, + ]; + + [Flags] + enum Primitives + { + Boolean = 1 << TypeCode.Boolean, + Char = 1 << TypeCode.Char, + SByte = 1 << TypeCode.SByte, + Byte = 1 << TypeCode.Byte, + Int16 = 1 << TypeCode.Int16, + UInt16 = 1 << TypeCode.UInt16, + Int32 = 1 << TypeCode.Int32, + UInt32 = 1 << TypeCode.UInt32, + Int64 = 1 << TypeCode.Int64, + UInt64 = 1 << TypeCode.UInt64, + Single = 1 << TypeCode.Single, + Double = 1 << TypeCode.Double, + Decimal = 1 << TypeCode.Decimal, + DateTime = 1 << TypeCode.DateTime, + String = 1 << TypeCode.String, + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionAssemblySymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionAssemblySymbol.cs new file mode 100644 index 0000000000..209137202d --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionAssemblySymbol.cs @@ -0,0 +1,25 @@ +using System; + +namespace IKVM.CoreLib.Symbols +{ + + /// + /// Represents an assembly definition. + /// + public abstract class DefinitionAssemblySymbol : AssemblySymbol + { + + /// + /// Initializes a new instance. + /// + /// + /// + protected DefinitionAssemblySymbol(SymbolContext context) : + base(context) + { + + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionEventSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionEventSymbol.cs new file mode 100644 index 0000000000..42b7124f2a --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionEventSymbol.cs @@ -0,0 +1,19 @@ +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionEventSymbol : EventSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionEventSymbol(SymbolContext context) : + base(context) + { + + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionFieldSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionFieldSymbol.cs new file mode 100644 index 0000000000..3591440e3a --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionFieldSymbol.cs @@ -0,0 +1,19 @@ +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionFieldSymbol : FieldSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionFieldSymbol(SymbolContext context) : + base(context) + { + + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionGenericMethodParameterTypeSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionGenericMethodParameterTypeSymbol.cs new file mode 100644 index 0000000000..fa3d2003dc --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionGenericMethodParameterTypeSymbol.cs @@ -0,0 +1,24 @@ +using System.Linq; + +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionGenericMethodParameterTypeSymbol : GenericMethodParameterTypeSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionGenericMethodParameterTypeSymbol(SymbolContext context) : + base(context) + { + + } + + /// + public sealed override bool ContainsMissingType => GenericParameterConstraints.Any(i => i.ContainsMissingType); + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionGenericTypeParameterTypeSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionGenericTypeParameterTypeSymbol.cs new file mode 100644 index 0000000000..40141c91b3 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionGenericTypeParameterTypeSymbol.cs @@ -0,0 +1,19 @@ +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionGenericTypeParameterTypeSymbol : GenericTypeParameterTypeSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionGenericTypeParameterTypeSymbol(SymbolContext context) : + base(context) + { + + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionMethodSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionMethodSymbol.cs new file mode 100644 index 0000000000..e23825c2ee --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionMethodSymbol.cs @@ -0,0 +1,33 @@ +using System; + +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionMethodSymbol : MethodSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionMethodSymbol(SymbolContext context) : + base(context) + { + + } + + /// + public sealed override bool IsGenericMethodDefinition => GenericParameters.IsEmpty == false; + + /// + public sealed override bool IsConstructedGenericMethod => false; + + /// + public sealed override MethodSymbol? BaseDefinition => throw new NotImplementedException(); + + /// + public sealed override MethodSymbol? GenericMethodDefinition => IsGenericMethodDefinition ? this : throw new InvalidOperationException(); + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionModuleSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionModuleSymbol.cs new file mode 100644 index 0000000000..8b10b39f2f --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionModuleSymbol.cs @@ -0,0 +1,19 @@ +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionModuleSymbol : ModuleSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionModuleSymbol(SymbolContext context) : + base(context) + { + + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionParameterSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionParameterSymbol.cs new file mode 100644 index 0000000000..0490f06bb2 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionParameterSymbol.cs @@ -0,0 +1,24 @@ +using System; + +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionParameterSymbol : ParameterSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionParameterSymbol(SymbolContext context) : + base(context) + { + + } + + /// + public sealed override bool ContainsMissing => throw new NotImplementedException(); + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionPropertySymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionPropertySymbol.cs new file mode 100644 index 0000000000..ba98d8f74e --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionPropertySymbol.cs @@ -0,0 +1,19 @@ +namespace IKVM.CoreLib.Symbols +{ + + public abstract class DefinitionPropertySymbol : PropertySymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionPropertySymbol(SymbolContext context) : + base(context) + { + + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/DefinitionTypeSymbol.cs b/src/IKVM.CoreLib/Symbols/DefinitionTypeSymbol.cs new file mode 100644 index 0000000000..a38318bf11 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/DefinitionTypeSymbol.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Immutable; + +namespace IKVM.CoreLib.Symbols +{ + + /// + /// Describes a type definition. + /// + public abstract class DefinitionTypeSymbol : TypeSymbol + { + + /// + /// Initializes a new instance. + /// + /// + protected DefinitionTypeSymbol(SymbolContext context) : + base(context) + { + + } + + /// + public sealed override MethodSymbol? DeclaringMethod => null; + + /// + public sealed override bool IsTypeDefinition => true; + + /// + public sealed override bool IsArray => false; + + /// + public sealed override bool IsByRef => false; + + /// + public sealed override bool IsConstructedGenericType => false; + + /// + public sealed override bool IsFunctionPointer => false; + + /// + public sealed override int GenericParameterPosition => throw new InvalidOperationException(); + + /// + public sealed override bool HasElementType => false; + + /// + public sealed override bool IsGenericTypeParameter => false; + + /// + public sealed override bool IsGenericMethodParameter => false; + + /// + public sealed override bool IsPointer => false; + + /// + public sealed override bool IsSZArray => false; + + /// + public sealed override bool IsUnmanagedFunctionPointer => false; + + /// + public sealed override TypeSymbol? GetElementType() => null; + + /// + public sealed override TypeSymbol GenericTypeDefinition => throw new InvalidOperationException(); + + /// + public sealed override ImmutableArray GenericParameterConstraints => throw new NotSupportedException(); + + /// + public sealed override TypeCode TypeCode => TypeSymbolExtensions.GetTypeCode(this); + + /// + public sealed override bool ContainsGenericParameters => GenericParameters.Length > 0; + + /// + public sealed override bool IsGenericTypeDefinition => ContainsGenericParameters; + + /// + public sealed override bool IsPrimitive => TypeCode is TypeCode.Boolean or TypeCode.Byte or TypeCode.SByte or TypeCode.Int16 or TypeCode.UInt16 or TypeCode.Int32 or TypeCode.UInt32 or TypeCode.Int64 or TypeCode.UInt64 or TypeCode.Char or TypeCode.Double or TypeCode.Single || this == Context.ResolveCoreType("System.IntPtr") || this == Context.ResolveCoreType("System.UIntPtr"); + + /// + public sealed override bool IsEnum => BaseType != null && BaseType == Context.ResolveCoreType("System.Enum"); + + /// + public sealed override int GetArrayRank() => throw new ArgumentException("Must be an array type."); + + /// + public sealed override string? GetEnumName(object value) + { + if (!IsEnum) + throw new ArgumentException(); + if (value == null) + throw new ArgumentNullException(); + + try + { + value = Convert.ChangeType(value, TypeSymbolExtensions.GetSystemType(GetEnumUnderlyingType())); + } + catch (FormatException) + { + throw new ArgumentException(); + } + catch (OverflowException) + { + return null; + } + catch (InvalidCastException) + { + return null; + } + + foreach (var field in GetDeclaredFields()) + if (field.IsLiteral && field.GetRawConstantValue() is { } v && v.Equals(value)) + return field.Name; + + return null; + } + + /// + public sealed override ImmutableArray GetEnumNames() + { + if (!IsEnum) + throw new ArgumentException(); + + var names = ImmutableArray.CreateBuilder(); + foreach (var field in GetDeclaredFields()) + if (field.IsLiteral) + names.Add(field.Name); + + return names.ToImmutable(); + } + + /// + public sealed override TypeSymbol GetEnumUnderlyingType() + { + if (!IsEnum) + throw new ArgumentException(); + + foreach (var field in GetDeclaredFields()) + if (!field.IsStatic) + return field.FieldType; + + throw new InvalidOperationException(); + } + + /// + public sealed override bool IsEnumDefined(object value) + { + if (value is string s) + return GetEnumNames().IndexOf(s) != -1; + if (IsEnum == false) + throw new ArgumentException(); + if (value == null) + throw new ArgumentNullException(); + if (value.GetType() != TypeSymbolExtensions.GetSystemType(GetEnumUnderlyingType())) + throw new ArgumentException(); + + foreach (var field in GetDeclaredFields()) + if (field.IsLiteral && field.GetRawConstantValue() is { } v && v.Equals(value)) + return true; + + return false; + } + + /// + internal sealed override TypeSymbol Specialize(GenericContext genericContext) + { + if (ContainsGenericParameters == false) + return this; + + var args = GenericParameters; + for (int i = 0; i < args.Length; i++) + if (args[i].ContainsGenericParameters) + args = args.SetItem(i, args[i].Specialize(genericContext)); + + return MakeGenericType(args); + + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/AssemblySymbolBuilder.cs b/src/IKVM.CoreLib/Symbols/Emit/AssemblySymbolBuilder.cs new file mode 100644 index 0000000000..e98c586608 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/AssemblySymbolBuilder.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading; + +namespace IKVM.CoreLib.Symbols.Emit +{ + + public sealed class AssemblySymbolBuilder : DefinitionAssemblySymbol, ICustomAttributeBuilder + { + + AssemblyIdentity _identity; + + ModuleSymbolBuilder? _manifestModule; + ImmutableArray.Builder _modules = ImmutableArray.CreateBuilder(); + ImmutableArray _modulesCache; + ImmutableArray.Builder _win32Icons = ImmutableArray.CreateBuilder(); + ImmutableArray.Builder _manifestResources = ImmutableArray.CreateBuilder(); + ImmutableArray.Builder _resources = ImmutableArray.CreateBuilder(); + + ImmutableArray.Builder _typeForwarders = ImmutableArray.CreateBuilder(); + MethodSymbolBuilder? _entryPoint; + PEFileKinds _fileKind; + ImmutableArray<(string Name, string FileName)>.Builder _resourceFiles = ImmutableArray.CreateBuilder<(string, string)>(); + (string? product, string? productVersion, string? company, string? copyright, string? trademark)? _versionResource; + ImmutableArray.Builder _referencedAssemblies = ImmutableArray.CreateBuilder(); + ImmutableArray.Builder _customAttributes = ImmutableArray.CreateBuilder(); + + bool _frozen; + object? _writer; + + /// + /// Initializes a new instance. + /// + /// + internal AssemblySymbolBuilder(SymbolContext context, AssemblyIdentity identity) : + base(context) + { + _identity = identity ?? throw new ArgumentNullException(nameof(identity)); + } + + /// + public override AssemblyIdentity Identity => _identity; + + /// + public override string ImageRuntimeVersion => throw new NotSupportedException(); + + /// + public override string Location => throw new NotSupportedException(); + + /// + public override ModuleSymbol ManifestModule => _manifestModule ?? throw new InvalidOperationException(); + + /// + public override MethodSymbol? EntryPoint => _entryPoint; + + /// + public override bool IsMissing => false; + + /// + public override ManifestResourceInfo? GetManifestResourceInfo(string resourceName) + { + var manifestModule = (ModuleSymbolBuilder)ManifestModule; + foreach (var i in manifestModule.GetManifestResources()) + if (i.Name == resourceName) + return new ManifestResourceInfo(global::System.Reflection.ResourceLocation.Embedded, null, null); + + return null; + } + + /// + public override Stream GetManifestResourceStream(string name) + { + var manifestModule = (ModuleSymbolBuilder)ManifestModule; + foreach (var i in manifestModule.GetManifestResources()) + if (i.Name == name) + return new MemoryStream(i.Data.ToArray()); + + throw new FileNotFoundException(); + } + + /// + public override ImmutableArray GetModules() + { + if (_modulesCache == default) + ImmutableInterlocked.InterlockedInitialize(ref _modulesCache, _modules.ToImmutable().CastArray()); + + return _modulesCache; + } + + /// + public override ImmutableArray GetReferencedAssemblies() + { + return _referencedAssemblies.ToImmutable(); + } + + /// + internal override ImmutableArray GetDeclaredCustomAttributes() + { + return _customAttributes.ToImmutable(); + } + + /// + /// Freezes the type builder. + /// + internal void Freeze() + { + lock (this) + _frozen = true; + } + + /// + /// Throws an exception if the builder is frozen. + /// + void ThrowIfFrozen() + { + lock (this) + if (_frozen) + throw new InvalidOperationException("AssemblySymbolBuilder is frozen."); + } + + /// + /// Defines a named module in this assembly. + /// + /// + /// + /// + public ModuleSymbolBuilder DefineModule(string name, string fileName) + { + ThrowIfFrozen(); + var b = new ModuleSymbolBuilder(Context, this, name, fileName); + _modules.Add(b); + _modulesCache = default; + _manifestModule ??= b; + return b; + } + + /// + /// Sets a Win32 icon on the generated assembly. + /// + /// + public void DefineIconResource(byte[] bytes) + { + ThrowIfFrozen(); + _win32Icons.Add(bytes); + } + + /// + /// Sets a manifest resource on the generated assembly. + /// + /// + public void DefineManifestResource(byte[] bytes) + { + ThrowIfFrozen(); + _manifestResources.Add(bytes); + } + + /// + /// Sets a Win32 version info resource on the generated assembly. + /// + public void DefineVersionInfoResource() + { + ThrowIfFrozen(); + _versionResource = (null, null, null, null, null); + } + + /// + /// Sets a Win32 version info resource on the generated assembly. + /// + public void DefineVersionInfoResource(string product, string productVersion, string company, string copyright, string trademark) + { + ThrowIfFrozen(); + _versionResource = (product, productVersion, company, copyright, trademark); + } + + /// + /// Sets the entry point for this assembly, assuming that a console application is being built. + /// + /// + public void SetEntryPoint(MethodSymbolBuilder entryMethod) + { + SetEntryPoint(entryMethod, PEFileKinds.Dll); + } + + /// + /// Sets the entry point for this assembly and defines the type of the portable executable (PE file) being built. + /// + /// + /// + public void SetEntryPoint(MethodSymbolBuilder entryMethod, PEFileKinds fileKind) + { + ThrowIfFrozen(); + _entryPoint = entryMethod; + _fileKind = fileKind; + } + + /// + /// Adds a forwarded type to this assembly. + /// + /// + public void AddTypeForwarder(TypeSymbol type) + { + ThrowIfFrozen(); + _typeForwarders.Add(type); + } + + /// + /// Adds an external resource file to the assembly. + /// + /// + /// + public void AddResourceFile(string name, string fileName) + { + ThrowIfFrozen(); + _resourceFiles.Add((name, fileName)); + } + + /// + public void SetCustomAttribute(CustomAttribute attribute) + { + ThrowIfFrozen(); + _customAttributes.Add(attribute); + } + + /// + /// Sets the assembly version. + /// + /// + /// + public void SetAssemblyVersion(Version version) + { + ThrowIfFrozen(); + + _identity = new AssemblyIdentity( + _identity.Name, + version, + _identity.CultureName, + _identity.HasPublicKey ? _identity.PublicKey : _identity.PublicKeyToken, + _identity.HasPublicKey, + _identity.ContentType, + _identity.ProcessorArchitecture); + } + + /// + /// Sets the assembly culture. + /// + /// + /// + public void SetAssemblyCulture(string cultureName) + { + ThrowIfFrozen(); + + _identity = new AssemblyIdentity( + _identity.Name, + _identity.Version, + cultureName, + _identity.HasPublicKey ? _identity.PublicKey : _identity.PublicKeyToken, + _identity.HasPublicKey, + _identity.ContentType, + _identity.ProcessorArchitecture); + } + + /// + /// Gets the writer object associated with this builder. + /// + /// + /// + /// + internal TWriter Writer(Func create) + { + if (_writer is null) + Interlocked.CompareExchange(ref _writer, create(this), null); + + return (TWriter)(_writer ?? throw new InvalidOperationException()); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/EventSymbolBuilder.cs b/src/IKVM.CoreLib/Symbols/Emit/EventSymbolBuilder.cs new file mode 100644 index 0000000000..e090ffad89 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/EventSymbolBuilder.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; +using System.Threading; + +namespace IKVM.CoreLib.Symbols.Emit +{ + + public sealed class EventSymbolBuilder : DefinitionEventSymbol, ICustomAttributeBuilder + { + + readonly TypeSymbol _declaringType; + readonly string _name; + readonly EventAttributes _attributes; + readonly TypeSymbol _eventType; + + MethodSymbolBuilder? _addMethod; + MethodSymbolBuilder? _removeMethod; + MethodSymbolBuilder? _raiseMethod; + readonly ImmutableArray.Builder _otherMethods = ImmutableArray.CreateBuilder(); + readonly ImmutableArray.Builder _customAttributes = ImmutableArray.CreateBuilder(); + + bool _frozen; + object? _writer; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + /// + internal EventSymbolBuilder(SymbolContext context, TypeSymbolBuilder declaringType, string name, EventAttributes attributes, TypeSymbol eventType) : + base(context) + { + _declaringType = declaringType ?? throw new ArgumentNullException(nameof(declaringType)); + _name = name ?? throw new ArgumentNullException(nameof(name)); + _attributes = attributes; + _eventType = eventType ?? throw new ArgumentNullException(nameof(eventType)); + } + + /// + public sealed override TypeSymbol? DeclaringType => _declaringType; + + /// + public sealed override EventAttributes Attributes => _attributes; + + /// + public sealed override TypeSymbol? EventHandlerType => _eventType; + + /// + public sealed override string Name => _name; + + /// + public sealed override bool IsMissing => false; + + /// + public sealed override MethodSymbol? AddMethod => _addMethod; + + /// + public sealed override MethodSymbol? RaiseMethod => _raiseMethod; + + /// + public sealed override MethodSymbol? RemoveMethod => _removeMethod; + + /// + public sealed override ImmutableArray OtherMethods => _otherMethods.ToImmutable().CastArray(); + + /// + internal sealed override ImmutableArray GetDeclaredCustomAttributes() => _customAttributes.ToImmutable(); + + /// + /// Freezes the type builder. + /// + internal void Freeze() + { + lock (this) + _frozen = true; + } + + /// + /// Throws an exception if the builder is frozen. + /// + void ThrowIfFrozen() + { + lock (this) + if (_frozen) + throw new InvalidOperationException("EventSymbolBuilder is frozen."); + } + + /// + /// Sets the method used to subscribe to this event. + /// + /// + public void SetAddOnMethod(MethodSymbolBuilder method) + { + ThrowIfFrozen(); + _addMethod = method; + } + + /// + /// Sets the method used to unsubscribe to this event. + /// + /// + public void SetRemoveOnMethod(MethodSymbolBuilder method) + { + ThrowIfFrozen(); + _removeMethod = method; + } + + /// + /// Sets the method used to raise this event. + /// + /// + public void SetRaiseMethod(MethodSymbolBuilder method) + { + ThrowIfFrozen(); + _raiseMethod = method; + } + + /// + /// Adds one of the "other" methods associated with this event. "Other" methods are methods other than the "on" and "raise" methods associated with an event. This function can be called many times to add as many "other" methods. + /// + /// + public void AddOtherMethod(MethodSymbolBuilder method) + { + ThrowIfFrozen(); + _otherMethods.Add(method); + } + + /// + public void SetCustomAttribute(CustomAttribute attribute) + { + ThrowIfFrozen(); + _customAttributes.Add(attribute); + } + + /// + /// Gets the writer object associated with this builder. + /// + /// + /// + /// + internal TWriter Writer(Func create) + { + if (_writer is null) + Interlocked.CompareExchange(ref _writer, create(this), null); + + return (TWriter)(_writer ?? throw new InvalidOperationException()); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/FieldSymbolBuilder.cs b/src/IKVM.CoreLib/Symbols/Emit/FieldSymbolBuilder.cs new file mode 100644 index 0000000000..001e9d2209 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/FieldSymbolBuilder.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; +using System.Threading; + +namespace IKVM.CoreLib.Symbols.Emit +{ + + public sealed class FieldSymbolBuilder : DefinitionFieldSymbol, ICustomAttributeBuilder + { + + readonly ModuleSymbol _declaringModule; + readonly TypeSymbolBuilder? _declaringType; + readonly string _name; + readonly FieldAttributes _attributes; + readonly TypeSymbol _fieldType; + readonly ImmutableArray _requiredCustomModifiers; + readonly ImmutableArray _optionalCustomModifiers; + object? _constantValue; + int? _offset; + readonly ImmutableArray.Builder _customAttributes = ImmutableArray.CreateBuilder(); + + bool _frozen; + object? _writer; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal FieldSymbolBuilder(SymbolContext context, ModuleSymbol declaringModule, TypeSymbolBuilder? declaringType, string name, FieldAttributes attributes, TypeSymbol fieldType, ImmutableArray requiredCustomModifiers, ImmutableArray optionalCustomModifiers) : + base(context) + { + _declaringModule = declaringModule ?? throw new ArgumentNullException(nameof(declaringModule)); + _declaringType = declaringType; + _name = name ?? throw new ArgumentNullException(nameof(name)); + _attributes = attributes; + _fieldType = fieldType ?? throw new ArgumentNullException(nameof(fieldType)); + _requiredCustomModifiers = requiredCustomModifiers; + _optionalCustomModifiers = optionalCustomModifiers; + } + + /// + public sealed override bool IsMissing => false; + + /// + public sealed override ModuleSymbol Module => _declaringModule; + + /// + public sealed override TypeSymbol? DeclaringType => _declaringType; + + /// + public sealed override FieldAttributes Attributes => _attributes; + + /// + public sealed override string Name => _name; + + /// + public sealed override TypeSymbol FieldType => _fieldType; + + /// + public sealed override object? GetRawConstantValue() + { + return _constantValue; + } + + /// + /// Gets the defined field offset. + /// + public int? Offset => _offset; + + /// + public sealed override ImmutableArray GetOptionalCustomModifiers() + { + return _optionalCustomModifiers; + } + + /// + public sealed override ImmutableArray GetRequiredCustomModifiers() + { + return _requiredCustomModifiers; + } + + /// + internal sealed override ImmutableArray GetDeclaredCustomAttributes() + { + return _customAttributes.ToImmutable(); + } + + /// + /// Freezes the type builder. + /// + internal void Freeze() + { + lock (this) + _frozen = true; + } + + /// + /// Throws an exception if the builder is frozen. + /// + void ThrowIfFrozen() + { + lock (this) + if (_frozen) + throw new InvalidOperationException("FieldSymbolBuilder is frozen."); + } + + /// + /// Sets the default value of this field. + /// + /// + public void SetConstant(object? defaultValue) + { + ThrowIfFrozen(); + _constantValue = default; + } + + /// + /// Specifies the field layout. + /// + /// + public void SetOffset(int iOffset) + { + ThrowIfFrozen(); + _offset = iOffset; + } + + /// + public void SetCustomAttribute(CustomAttribute attribute) + { + ThrowIfFrozen(); + _customAttributes.Add(attribute); + } + + /// + /// Gets the writer object associated with this builder. + /// + /// + /// + /// + internal TWriter Writer(Func create) + { + if (_writer is null) + Interlocked.CompareExchange(ref _writer, create(this), null); + + return (TWriter)(_writer ?? throw new InvalidOperationException()); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/GenericMethodParameterTypeSymbolBuilder.cs b/src/IKVM.CoreLib/Symbols/Emit/GenericMethodParameterTypeSymbolBuilder.cs new file mode 100644 index 0000000000..a803389a6a --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/GenericMethodParameterTypeSymbolBuilder.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; + +namespace IKVM.CoreLib.Symbols.Emit +{ + + public sealed class GenericMethodParameterTypeSymbolBuilder : DefinitionGenericMethodParameterTypeSymbol, ICustomAttributeBuilder + { + + readonly MethodSymbolBuilder _declaringMethod; + readonly string _name; + GenericParameterAttributes _attributes; + readonly int _position; + readonly ImmutableArray.Builder _customAttributes = ImmutableArray.CreateBuilder(); + TypeSymbol? _baseTypeConstraint; + ImmutableArray _interfaceConstraints = []; + ImmutableArray _constraints; + + bool _frozen; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + internal GenericMethodParameterTypeSymbolBuilder(SymbolContext context, MethodSymbolBuilder declaringMethod, string name, GenericParameterAttributes attributes, int position) : + base(context) + { + _declaringMethod = declaringMethod ?? throw new ArgumentNullException(nameof(declaringMethod)); + _name = name ?? throw new ArgumentNullException(nameof(name)); + _attributes = attributes; + _position = position; + } + + /// + public sealed override MethodSymbol? DeclaringMethod => _declaringMethod; + + /// + public sealed override string Name => _name; + + /// + public sealed override string? Namespace => ""; + + /// + public sealed override GenericParameterAttributes GenericParameterAttributes => _attributes; + + /// + public sealed override int GenericParameterPosition => _position; + + /// + public sealed override ImmutableArray GenericParameterConstraints + { + get + { + if (_constraints.IsDefault) + { + var n = _baseTypeConstraint != null ? 1 : 0; + var l = ImmutableArray.CreateBuilder(n + _interfaceConstraints.Length); + if (_baseTypeConstraint != null) + l.Add(_baseTypeConstraint); + + foreach (var i in _interfaceConstraints) + l.Add(i); + + ImmutableInterlocked.InterlockedInitialize(ref _constraints, l.ToImmutable()); + } + + return _constraints; + } + } + + /// + public sealed override ImmutableArray GetOptionalCustomModifiers() => []; + + /// + public sealed override ImmutableArray GetRequiredCustomModifiers() => []; + + /// + internal sealed override ImmutableArray GetDeclaredCustomAttributes() => _customAttributes.ToImmutable(); + + /// + /// Freezes the type builder. + /// + internal void Freeze() + { + lock (this) + _frozen = true; + } + + /// + /// Throws an exception if the builder is frozen. + /// + void ThrowIfFrozen() + { + lock (this) + if (_frozen) + throw new InvalidOperationException("GenericMethodParameterTypeSymbolBuilder is frozen."); + } + + /// + /// Sets the variance characteristics and special constraints of the generic parameter, such as the parameterless constructor constraint. + /// + /// + public void SetGenericParameterAttributes(GenericParameterAttributes genericParameterAttributes) + { + ThrowIfFrozen(); + _attributes = genericParameterAttributes; + } + + /// + /// Sets the base type that a type must inherit in order to be substituted for the type parameter. + /// + /// + public void SetBaseTypeConstraint(TypeSymbol? baseTypeConstraint) + { + ThrowIfFrozen(); + _baseTypeConstraint = baseTypeConstraint; + _constraints = default; + } + + /// + /// Sets the interfaces a type must implement in order to be substituted for the type parameter. + /// + /// + public void SetInterfaceConstraints(ImmutableArray interfaceConstraints) + { + ThrowIfFrozen(); + _interfaceConstraints = interfaceConstraints; + _constraints = default; + } + + /// + public void SetCustomAttribute(CustomAttribute attribute) + { + ThrowIfFrozen(); + _customAttributes.Add(attribute); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/GenericTypeParameterTypeSymbolBuilder.cs b/src/IKVM.CoreLib/Symbols/Emit/GenericTypeParameterTypeSymbolBuilder.cs new file mode 100644 index 0000000000..c612d326df --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/GenericTypeParameterTypeSymbolBuilder.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; + +namespace IKVM.CoreLib.Symbols.Emit +{ + + public sealed class GenericTypeParameterTypeSymbolBuilder : DefinitionGenericTypeParameterTypeSymbol, ICustomAttributeBuilder + { + + readonly TypeSymbol _declaringType; + readonly string _name; + GenericParameterAttributes _attributes; + readonly int _position; + readonly ImmutableArray.Builder _customAttributes = ImmutableArray.CreateBuilder(); + TypeSymbol? _baseTypeConstraint; + ImmutableArray _interfaceConstraints = []; + ImmutableArray _constraints; + + bool _frozen; + + /// + /// Initializes a new instance. + /// + /// + /// + /// + internal GenericTypeParameterTypeSymbolBuilder(SymbolContext context, TypeSymbolBuilder declaringType, string name, GenericParameterAttributes attributes, int position) : + base(context) + { + _declaringType = declaringType ?? throw new ArgumentNullException(nameof(declaringType)); + _name = name ?? throw new ArgumentNullException(nameof(name)); + _attributes = attributes; + _position = position; + } + + /// + public sealed override TypeSymbol? DeclaringType => _declaringType; + + /// + public sealed override string Name => _name; + + /// + public sealed override string? Namespace => ""; + + /// + public sealed override GenericParameterAttributes GenericParameterAttributes => _attributes; + + /// + public sealed override int GenericParameterPosition => _position; + + /// + public sealed override ImmutableArray GenericParameterConstraints + { + get + { + if (_constraints.IsDefault) + { + var n = _baseTypeConstraint != null ? 1 : 0; + var l = ImmutableArray.CreateBuilder(n + _interfaceConstraints.Length); + if (_baseTypeConstraint != null) + l.Add(_baseTypeConstraint); + + foreach (var i in _interfaceConstraints) + l.Add(i); + + ImmutableInterlocked.InterlockedInitialize(ref _constraints, l.ToImmutable()); + } + + return _constraints; + } + } + + /// + public sealed override ImmutableArray GetOptionalCustomModifiers() => []; + + /// + public sealed override ImmutableArray GetRequiredCustomModifiers() => []; + + /// + internal override ImmutableArray GetDeclaredCustomAttributes() => _customAttributes.ToImmutable(); + + /// + /// Freezes the type builder. + /// + internal void Freeze() + { + lock (this) + _frozen = true; + } + + /// + /// Throws an exception if the builder is frozen. + /// + void ThrowIfFrozen() + { + lock (this) + if (_frozen) + throw new InvalidOperationException("GenericTypeParameterTypeSymbolBuilder is frozen."); + } + + /// + /// Sets the variance characteristics and special constraints of the generic parameter, such as the parameterless constructor constraint. + /// + /// + public void SetGenericParameterAttributes(GenericParameterAttributes genericParameterAttributes) + { + ThrowIfFrozen(); + _attributes = genericParameterAttributes; + } + + /// + /// Sets the base type that a type must inherit in order to be substituted for the type parameter. + /// + /// + public void SetBaseTypeConstraint(TypeSymbol? baseTypeConstraint) + { + ThrowIfFrozen(); + _baseTypeConstraint = baseTypeConstraint; + _constraints = default; + } + + /// + /// Sets the interfaces a type must implement in order to be substituted for the type parameter. + /// + /// + public void SetInterfaceConstraints(ImmutableArray interfaceConstraints) + { + ThrowIfFrozen(); + _interfaceConstraints = interfaceConstraints; + _constraints = default; + } + + /// + public void SetCustomAttribute(CustomAttribute attribute) + { + ThrowIfFrozen(); + _customAttributes.Add(attribute); + } + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/ICustomAttributeBuilder.cs b/src/IKVM.CoreLib/Symbols/Emit/ICustomAttributeBuilder.cs new file mode 100644 index 0000000000..210591f31e --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/ICustomAttributeBuilder.cs @@ -0,0 +1,15 @@ +namespace IKVM.CoreLib.Symbols.Emit +{ + + interface ICustomAttributeBuilder + { + + /// + /// Sets a custom attribute. + /// + /// + void SetCustomAttribute(CustomAttribute attribute); + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/IILGeneratorWriter.cs b/src/IKVM.CoreLib/Symbols/Emit/IILGeneratorWriter.cs new file mode 100644 index 0000000000..319a8f8d82 --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/IILGeneratorWriter.cs @@ -0,0 +1,240 @@ +using System.Collections.Immutable; + +namespace IKVM.CoreLib.Symbols.Emit +{ + + /// + /// Provides an interface to emit ECMA335 IL. + /// + interface IILGeneratorWriter + { + + readonly record struct LocalBuilderRef(int Index); + + readonly record struct LabelRef(int Index); + + /// + /// Begins a lexical scope. + /// + void BeginScope(); + + /// + /// Specifies the namespace to be used in evaluating locals and watches for the current active lexical scope. + /// + /// + void UsingNamespace(string usingNamespace); + + /// + /// Ends a lexical scope. + /// + void EndScope(); + + /// + /// Marks a sequence point in the Microsoft intermediate language (MSIL) stream. + /// + /// + /// + /// + /// + /// + void MarkSequencePoint(SourceDocument document, int startLine, int startColumn, int endLine, int endColumn); + + /// + /// Declares a local variable of the specified type, optionally pinning the object referred to by the variable. + /// + /// + /// + /// + LocalBuilderRef DeclareLocal(TypeSymbol localType, bool pinned); + + /// + /// Declares a local variable of the specified type. + /// + /// + /// + LocalBuilderRef DeclareLocal(TypeSymbol localType); + + /// + /// Declares a new label. + /// + /// + LabelRef DefineLabel(); + + /// + /// Begins an exception block for a non-filtered exception. + /// + /// + LabelRef BeginExceptionBlock(); + + /// + /// Marks the Microsoft intermediate language (MSIL) stream's current position with the given label. + /// + /// + void MarkLabel(LabelRef label); + + /// + /// Begins an exception block for a filtered exception. + /// + void BeginExceptFilterBlock(); + + /// + /// Begins a catch block. + /// + /// + void BeginCatchBlock(TypeSymbol? exceptionType); + + /// + /// Begins an exception fault block in the Microsoft intermediate language (MSIL) stream. + /// + void BeginFaultBlock(); + + /// + /// Begins a finally block in the Microsoft intermediate language (MSIL) instruction stream. + /// + void BeginFinallyBlock(); + + /// + /// Ends an exception block. + /// + void EndExceptionBlock(); + + /// + /// Emits an instruction to throw an exception. + /// + /// + void ThrowException(TypeSymbol exceptionType); + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the index of the given local variable. + /// + /// + /// + void Emit(OpCodeValue opcode, LocalBuilderRef arg); + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the metadata token for the given type. + /// + /// + /// + void Emit(OpCodeValue opcode, TypeSymbol arg); + + /// + /// Puts the specified instruction and metadata token for the specified field onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, FieldSymbol arg); + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the metadata token for the given method. + /// + /// + /// + void Emit(OpCodeValue opcode, MethodSymbol arg); + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the metadata token for the given string. + /// + /// + /// + void Emit(OpCodeValue opcode, string str); + + /// + /// Puts the specified instruction and numerical argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, float arg); + + /// + /// Puts the specified instruction and character argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, sbyte arg); + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream and leaves space to include a label when fixes are done. + /// + /// + /// + void Emit(OpCodeValue opcode, ImmutableArray labels); + + /// + /// Puts the specified instruction and numerical argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, long arg); + + /// + /// Puts the specified instruction and numerical argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, int arg); + + /// + /// Puts the specified instruction and numerical argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, short arg); + + /// + /// Puts the specified instruction and numerical argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, double arg); + + /// + /// Puts the specified instruction and character argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + void Emit(OpCodeValue opcode, byte arg); + + /// + /// Puts the specified instruction onto the stream of instructions. + /// + /// + void Emit(OpCodeValue opcode); + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream and leaves space to include a label when fixes are done. + /// + /// + /// + void Emit(OpCodeValue opcode, LabelRef label); + + /// + /// Puts a call or callvirt instruction onto the Microsoft intermediate language (MSIL) stream to call a varargs method. + /// + /// + /// + /// + void EmitCall(OpCodeValue opcode, MethodSymbol methodInfo, ImmutableArray optionalParameterTypes); + + /// + /// Puts a Calli instruction onto the Microsoft intermediate language (MSIL) stream, specifying an unmanaged calling convention for the indirect call. + /// + /// + /// + /// + /// + void EmitCalli(OpCodeValue opcode, global::System.Runtime.InteropServices.CallingConvention unmanagedCallConv, TypeSymbol? returnType, ImmutableArray parameterTypes); + + /// + /// Puts a Calli instruction onto the Microsoft intermediate language (MSIL) stream, specifying a managed calling convention for the indirect call. + /// + /// + /// + /// + /// + /// + void EmitCalli(OpCodeValue opcode, global::System.Reflection.CallingConventions callingConvention, TypeSymbol? returnType, ImmutableArray parameterTypes, ImmutableArray optionalParameterTypes); + + } + +} diff --git a/src/IKVM.CoreLib/Symbols/Emit/ILGenerator.cs b/src/IKVM.CoreLib/Symbols/Emit/ILGenerator.cs new file mode 100644 index 0000000000..6b550438aa --- /dev/null +++ b/src/IKVM.CoreLib/Symbols/Emit/ILGenerator.cs @@ -0,0 +1,759 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; + +using IKVM.CoreLib.Collections; + +namespace IKVM.CoreLib.Symbols.Emit +{ + + public class ILGenerator + { + + const int BlockSize = 64; + + /// + /// Decscribes the IL node. + /// + enum NodeKind : byte + { + + OpCode, + OpCode_Local, + OpCode_Type, + OpCode_Method, + OpCode_Field, + OpCode_Float, + OpCode_String, + OpCode_SByte, + OpCode_Byte, + OpCode_Short, + OpCode_Double, + OpCode_Integer, + OpCode_Long, + OpCode_Label, + OpCode_ManyLabel, + Call, + Calli, + CalliManaged, + BeginScope, + EndScope, + UsingNamespace, + DeclareLocal, + SequencePoint, + Label, + BeginExceptionBlock, + BeginCatchBlock, + BeginFaultBlock, + BeginFinallyBlock, + BeginFilterBlock, + EndExceptionBlock, + + } + + struct Node + { + + public NodeKind Kind; + public OpCodeValue OpCode; + public object? ObjectArg; + public long NumberArg0; + public long NumberArg1; + + /// + /// Initializes a new instance. + /// + public Node(NodeKind kind, OpCodeValue opcode, object? objectArg, long numberArg0, long numberArg1) + { + Kind = kind; + OpCode = opcode; + ObjectArg = objectArg; + NumberArg0 = numberArg0; + NumberArg1 = numberArg1; + } + + } + +#if NET8_0_OR_GREATER + + [global::System.Runtime.CompilerServices.InlineArray(BlockSize)] + struct ILInlineNodeArray + { + + public Node Item; + + } + +#endif + + /// + /// IL stream is a series of double-linked blocks. Head always points to the first in the sequence. + /// + class ILBlock + { + + public ILBlock Head; + public ILBlock Prev; + public ILBlock Next; +#if NET8_0_OR_GREATER + public ILInlineNodeArray Data; +#else + public Node[] Data; +#endif + public int Size = 0; + + /// + /// Initializes a head block. + /// + public ILBlock() + { + Head = this; + Prev = this; + Next = this; +#if NET8_0_OR_GREATER + Data = new ILInlineNodeArray(); +#else + Data = new Node[BlockSize]; +#endif + } + + /// + /// Initializes a new tail block. + /// + /// + public ILBlock(ILBlock prev) : this() + { + Head = prev.Head; + Prev = prev; + Prev.Next = this; + } + + } + + /// + /// Describes an IL stream, which is a pointer to the latest tail block, and to which s can be appended. + /// + struct NodeStream : IEnumerable + { + + ILBlock Tail; + int Size; + + /// + /// Initializes a new instance. + /// + public NodeStream() + { + Tail = new ILBlock(); + Size = 0; + } + + /// + /// Appends a new node to the stream. + /// + /// + public void Append(Node node) + { + // append a new tail block if required + if (Tail.Size >= BlockSize) + Tail = new ILBlock(Tail); + + // append onto existing tail + Tail.Data[Tail.Size++] = node; + Size++; + } + + /// + public IEnumerator GetEnumerator() + { + for (var node = Tail.Head; node != node.Next; node = node.Next) + for (int i = 0; i < node.Size; i++) + yield return node.Data[i]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + } + + record class CallNode(MethodSymbol Method, ImmutableArray OptionalParameterTypes); + + record class CalliNode(CallingConvention UnmanagedCallConv, TypeSymbol? ReturnType, ImmutableArray ParameterTypes); + + record class ManagedCalliNode(CallingConventions CallConv, TypeSymbol? ReturnType, ImmutableArray ParameterTypes, ImmutableArray OptionalParameterTypes); + + readonly SymbolContext _context; + NodeStream _stream; + int _iloffset; + int _labelIndex; + int _localIndex; + + /// + /// Initializes a new instance. + /// + /// + public ILGenerator(SymbolContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _stream = new NodeStream(); + } + + /// + /// Gets the current offset, in bytes, in the Microsoft intermediate language (MSIL) stream that is being emitted by the . + /// + public int ILOffset => _iloffset; + + /// + /// Begins a lexical scope. + /// + public void BeginScope() + { + _stream.Append(new Node(NodeKind.BeginScope, 0, null, 0, 0)); + } + + /// + /// Specifies the namespace to be used in evaluating locals and watches for the current active lexical scope. + /// + /// + public void UsingNamespace(string usingNamespace) + { + _stream.Append(new Node(NodeKind.UsingNamespace, 0, usingNamespace, 0, 0)); + } + + /// + /// Ends a lexical scope. + /// + public void EndScope() + { + _stream.Append(new Node(NodeKind.EndScope, 0, null, 0, 0)); + } + + /// + /// Marks a sequence point in the Microsoft intermediate language (MSIL) stream. + /// + /// + /// + /// + /// + /// + public void MarkSequencePoint(SourceDocument document, int startLine, int startColumn, int endLine, int endColumn) + { + if (startLine < 1) + throw new ArgumentOutOfRangeException(nameof(startLine)); + if (endLine < 1) + throw new ArgumentOutOfRangeException(nameof(endLine)); + + _stream.Append(new Node(NodeKind.SequencePoint, 0, document, startLine << 32 | startColumn, endLine << 32 | endColumn)); + } + + /// + /// Declares a local variable of the specified type, optionally pinning the object referred to by the variable. + /// + /// + /// + /// + public LocalBuilder DeclareLocal(TypeSymbol localType, bool pinned) + { + var b = new LocalBuilder(localType, pinned, _localIndex++); + _stream.Append(new Node(NodeKind.DeclareLocal, 0, b, pinned ? 1 : 0, 0)); + return b; + } + + /// + /// Declares a local variable of the specified type. + /// + /// + /// + public LocalBuilder DeclareLocal(TypeSymbol localType) + { + var b = new LocalBuilder(localType, false, _localIndex++); + _stream.Append(new Node(NodeKind.DeclareLocal, 0, b, 0, 0)); + return b; + } + + /// + /// Declares a new label. + /// + /// + public Label DefineLabel() + { + return new Label(_labelIndex++); + } + + /// + /// Begins an exception block for a non-filtered exception. + /// + /// + public Label BeginExceptionBlock() + { + var l = new Label(_labelIndex++); + _stream.Append(new Node(NodeKind.BeginExceptionBlock, 0, null, l.Index, 0)); + return l; + } + + /// + /// Marks the Microsoft intermediate language (MSIL) stream's current position with the given label. + /// + /// + public void MarkLabel(Label loc) + { + _stream.Append(new Node(NodeKind.Label, 0, null, loc.Index, 0)); + } + + /// + /// Begins an exception block for a filtered exception. + /// + public void BeginExceptFilterBlock() + { + _stream.Append(new Node(NodeKind.BeginFilterBlock, 0, null, 0, 0)); + } + + /// + /// Begins a catch block. + /// + /// + public void BeginCatchBlock(TypeSymbol? exceptionType) + { + _stream.Append(new Node(NodeKind.BeginCatchBlock, 0, exceptionType, 0, 0)); + } + + /// + /// Begins an exception fault block in the Microsoft intermediate language (MSIL) stream. + /// + public void BeginFaultBlock() + { + _stream.Append(new Node(NodeKind.BeginFaultBlock, 0, null, 0, 0)); + } + + /// + /// Begins a finally block in the Microsoft intermediate language (MSIL) instruction stream. + /// + public void BeginFinallyBlock() + { + _stream.Append(new Node(NodeKind.BeginFinallyBlock, 0, null, 0, 0)); + } + + /// + /// Ends an exception block. + /// + public void EndExceptionBlock() + { + _stream.Append(new Node(NodeKind.EndExceptionBlock, 0, null, 0, 0)); + } + + /// + /// Emits an instruction to throw an exception. + /// + /// + public void ThrowException(TypeSymbol exceptionType) + { + if (exceptionType is null) + throw new ArgumentNullException(nameof(exceptionType)); + + var exceptionTypeSymbol = _context.ResolveCoreType("System.Exception"); + if (exceptionType.IsSubclassOf(exceptionTypeSymbol) == false && exceptionType != exceptionTypeSymbol) + throw new ArgumentException("Not exception type."); + + var con = exceptionType.GetConstructor([]); + if (con == null) + throw new ArgumentException("No default constructor."); + + Emit(OpCodes.Newobj, con); + Emit(OpCodes.Throw); + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the index of the given local variable. + /// + /// + /// + public void Emit(OpCode opcode, LocalBuilder local) + { + _iloffset += opcode.Size; + _stream.Append(new Node(NodeKind.OpCode_Local, (OpCodeValue)opcode.Value, local, 0, 0)); + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the metadata token for the given type. + /// + /// + /// + public void Emit(OpCode opcode, TypeSymbol cls) + { + _iloffset += opcode.Size; + _stream.Append(new Node(NodeKind.OpCode_Type, (OpCodeValue)opcode.Value, cls, 0, 0)); + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the metadata token for the given string. + /// + /// + /// + public void Emit(OpCode opcode, string str) + { + _iloffset += opcode.Size; + _stream.Append(new Node(NodeKind.OpCode_String, (OpCodeValue)opcode.Value, str, 0, 0)); + } + + /// + /// Puts the specified instruction and numerical argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + public unsafe void Emit(OpCode opcode, float arg) + { + _iloffset += opcode.Size; + _stream.Append(new Node(NodeKind.OpCode_Float, (OpCodeValue)opcode.Value, null, *(int*)&arg, 0)); + } + + /// + /// Puts the specified instruction and character argument onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + public void Emit(OpCode opcode, sbyte arg) + { + _iloffset += opcode.Size; + _stream.Append(new Node(NodeKind.OpCode_SByte, (OpCodeValue)opcode.Value, null, arg, 0)); + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream followed by the metadata token for the given method. + /// + /// + /// + public void Emit(OpCode opcode, MethodSymbol method) + { + _iloffset += opcode.Size; + _stream.Append(new Node(NodeKind.OpCode_Method, (OpCodeValue)opcode.Value, method, 0, 0)); + } + + /// + /// Puts the specified instruction and a signature token onto the Microsoft intermediate language (MSIL) stream of instructions. + /// + /// + /// + public void Emit(OpCode opcode, SignatureHelper signature) + { + _iloffset += opcode.Size; + throw new NotImplementedException(); + } + + /// + /// Puts the specified instruction onto the Microsoft intermediate language (MSIL) stream and leaves space to include a label when fixes are done. + /// + /// + /// + public void Emit(OpCode opcode, ImmutableArray