diff --git a/Tests/Kernels/Cosmos.Compiler.Tests.Bcl.System/Kernel.cs b/Tests/Kernels/Cosmos.Compiler.Tests.Bcl.System/Kernel.cs index 08c2426f40..c35949aa67 100644 --- a/Tests/Kernels/Cosmos.Compiler.Tests.Bcl.System/Kernel.cs +++ b/Tests/Kernels/Cosmos.Compiler.Tests.Bcl.System/Kernel.cs @@ -40,6 +40,7 @@ protected override void Run() ConvertTests.Execute(); DateTimeTests.Execute(); TimeSpanTests.Execute(); + ActivatorTests.Execute(); int count = Heap.Collect(); mDebugger.Send("Free"); diff --git a/Tests/Kernels/Cosmos.Compiler.Tests.Bcl.System/System/ActivatorTests.cs b/Tests/Kernels/Cosmos.Compiler.Tests.Bcl.System/System/ActivatorTests.cs new file mode 100644 index 0000000000..2bc1b05d95 --- /dev/null +++ b/Tests/Kernels/Cosmos.Compiler.Tests.Bcl.System/System/ActivatorTests.cs @@ -0,0 +1,100 @@ +using System; +using Cosmos.TestRunner; + +namespace Cosmos.Compiler.Tests.Bcl.System +{ + class ActivatorTests + { + public static unsafe void Execute() + { + // Till this moment (04/09/2024) the constructors, as any other method, are not included on the compilation result if never called, + // so we need to call them before. + Artesa dummy = new Artesa(); + WrappedLogger dummy2 = new WrappedLogger(); + ConsoleLogger dummy3 = new ConsoleLogger(); + + + // Generic method test + ILogger logger = GetLogger(); + // Null checks are just for clarification, a ctor would throw if the object/struct was null. + Assert.IsTrue(logger is not null, "Object incorrectly set."); + Assert.IsTrue(typeof(ConsoleLogger).Equals(logger.GetType()), "Type Incorrectly set"); + logger.Log("Interface method Call works!"); + + // Generic method test (with different type) + logger = GetLogger(); + Assert.IsTrue(logger is not null, "Object incorrectly set."); + Assert.IsTrue(typeof(WrappedLogger).Equals(logger.GetType()), "Type Incorrectly set"); + logger.Log("Interface method call really really works!"); + ((WrappedLogger)logger).Logger.Log("property get works!"); + ((WrappedLogger)logger).Success("Type's specific methods work too."); + + // Struct Test + var artesa = Activator.CreateInstance(typeof(Artesa)); + Assert.IsTrue(artesa is not null, "Struct incorrectly set."); + Assert.IsTrue(typeof(Artesa).Equals(artesa.GetType()), "Type Incorrectly set"); + + // Unboxing + Artesa art = (Artesa)artesa; + Assert.IsTrue(typeof(Artesa).Equals(art.GetType()), "Type Incorrectly set"); + + // Check property + Assert.IsTrue(art.Name is not null, "Property not set"); + + // Test method overrides + Assert.IsTrue(art.ToString() == $"{art.Name}-{art.LastName}", "Property not set"); + } + + public static T GetLogger() where T : ILogger, new() + { + return new T(); + } + } + + struct Artesa + { + public Artesa() + { + Console.WriteLine("LOL"); + Name = "Artesa"; + LastName = "Apple"; + } + public string Name { get; } + public string LastName { get; } + + public override string ToString() + { + return $"{Name}-{LastName}"; + } + } + + class WrappedLogger : ILogger + { + private ILogger logger; + + public WrappedLogger() : this(ActivatorTests.GetLogger()) + { + + } + + public WrappedLogger(ILogger logger) + { + this.logger = logger; + } + + internal ILogger Logger => logger; + + public void Log(string message) => logger.Log(message); + public void Success(string message) => Assert.Succeed(message); + } + + class ConsoleLogger : ILogger + { + public void Log(string message) => Console.WriteLine(message); + } + + interface ILogger + { + void Log(string message); + } +} diff --git a/source/Cosmos.Core_Plugs/System/ActivatorImpl.cs b/source/Cosmos.Core_Plugs/System/ActivatorImpl.cs new file mode 100644 index 0000000000..1b8404d519 --- /dev/null +++ b/source/Cosmos.Core_Plugs/System/ActivatorImpl.cs @@ -0,0 +1,81 @@ +using System; +using System.Runtime.CompilerServices; +using Cosmos.Core; +using IL2CPU.API.Attribs; +using IL2CPU.API; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace Cosmos.Core_Plugs.System +{ + [Plug(typeof(global::System.Activator))] + public static class ActivatorImpl + { + public static T CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>() + { + return (T)Activator.CreateInstance(typeof(T))!; + } + + // nonPublic is ignored since, at the moment, we cannot know if a ctor is public or not + // and we would get a Stack Corruption or Null reference exception if the ctor is not present either way. + public unsafe static object CreateInstance(CosmosRuntimeType ctr, bool nonPublic, bool wrapExceptions) + { + ArgumentNullException.ThrowIfNull(ctr, "Type"); + + // get the Type's VTable entry + var mType = VTablesImpl.mTypes[ctr.mTypeId]; + + // Calculate Object Size + uint dSize = 0; + if (ctr.IsValueType) + { + // For value types this property holds the correct size, so we can avoid the iteration + dSize = mType.Size; + } + else + { + // Calculate Object Actual Size. + var gcType = VTablesImpl.gcTypes[ctr.mTypeId]; + for (int i = 0; i < gcType.GCFieldTypes.Length; i++) + { + dSize += VTablesImpl.GetSize(gcType.GCFieldTypes[i]); + } + } + + // Object Allocation + uint ptr = GCImplementation.AllocNewObject(ObjectUtils.FieldDataOffset + dSize); + + // Set Fields + var vptr = (uint*)ptr; + vptr[0] = ctr.mTypeId; // Type + vptr[1] = ptr; // Address/Handler? + vptr[2] = dSize; // Data Area Size + + object obj = Unsafe.Read(vptr)!; + // We make the wild assumption that the ctor is the first method address, this may change on the future if the VTable is reworked. + var ctoraddress = mType.MethodAddresses[0]; + + try + { + if (ctr.IsValueType) + { + // Struct Ctor Call + var cctor = (delegate*)ctoraddress; + cctor(vptr + 3); // Struct pointer + } + else + { + // Object Ctor Call + var cctor = (delegate*)ctoraddress; + cctor(obj); + } + + return obj; + } + catch (Exception inner) when (wrapExceptions) + { + throw new TargetInvocationException(inner); + } + } + } +}