diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs
index 50b7c5a6..cc93fabe 100644
--- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs
+++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilVirtualMachine.cs
@@ -141,7 +141,7 @@ public IObjectAllocator Allocator
{
get;
set;
- } = DefaultAllocators.VirtualHeap;
+ } = DefaultAllocators.String.WithFallback(DefaultAllocators.VirtualHeap);
///
/// Gets the service that is responsible for invoking external functions or methods.
diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Heap/ManagedObjectHeap.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Heap/ManagedObjectHeap.cs
index dc1369fb..dc4dfd2c 100644
--- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Heap/ManagedObjectHeap.cs
+++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Heap/ManagedObjectHeap.cs
@@ -42,6 +42,17 @@ public ManagedObjectHeap(IHeap backingHeap, ValueFactory factory)
///
public AddressRange AddressRange => _backingHeap.AddressRange;
+ ///
+ /// Allocates flat unmanaged memory in the heap (i.e., without any object header).
+ ///
+ /// The size in bytes of the memory region to allocate.
+ /// A value indicating whether the object should be initialized with zeroes.
+ /// The address of the memory that was allocated.
+ public long AllocateFlat(uint size, bool initialize)
+ {
+ return _backingHeap.Allocate(size, initialize);
+ }
+
///
/// Allocates a managed object of the provided type in the heap.
///
@@ -153,7 +164,7 @@ public long AllocateString(BitVector contents)
chunkSpan.SliceStringData(_factory).Write(contents);
// Write null-terminator.
- chunkSpan.Slice(chunkSpan.Count - 16 - 1).U16 = 0;
+ chunkSpan.Slice(chunkSpan.Count - 16).Write((ushort) 0);
return address;
}
diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultAllocators.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultAllocators.cs
index eaf638a8..767656c8 100644
--- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultAllocators.cs
+++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultAllocators.cs
@@ -14,6 +14,11 @@ public static class DefaultAllocators
/// Gets an allocator that allocates objects in the virtualalized heap of the underlying virtual machine.
///
public static VirtualHeapAllocator VirtualHeap => VirtualHeapAllocator.Instance;
+
+ ///
+ /// Gets an allocator that handles System.String constructors.
+ ///
+ public static StringAllocator String => StringAllocator.Instance;
///
/// Chains the first object allocator with the provided object allocator in such a way that if the result of the
diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/StringAllocator.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/StringAllocator.cs
new file mode 100644
index 00000000..a308e198
--- /dev/null
+++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/StringAllocator.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Collections.Generic;
+using AsmResolver.DotNet;
+using AsmResolver.DotNet.Signatures.Types;
+using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
+using Echo.Memory;
+using Echo.Platforms.AsmResolver.Emulation.Dispatch;
+
+namespace Echo.Platforms.AsmResolver.Emulation.Invocation;
+
+///
+/// Provides a shim allocator that handles System.String constructors.
+///
+public class StringAllocator : IObjectAllocator
+{
+ ///
+ /// Gets the singleton instance of the class.
+ ///
+ public static StringAllocator Instance
+ {
+ get;
+ } = new();
+
+ ///
+ public AllocationResult Allocate(CilExecutionContext context, IMethodDescriptor ctor, IList arguments)
+ {
+ if ((!ctor.DeclaringType?.IsTypeOf("System", "String") ?? true) || ctor.Signature is null)
+ return AllocationResult.Inconclusive();
+
+ // TODO: We may want to make this configurable.
+ foreach (var argument in arguments)
+ {
+ if (!argument.IsFullyKnown)
+ return AllocationResult.Inconclusive();
+ }
+
+ // TODO: Add all string constructors.
+ var types = ctor.Signature.ParameterTypes;
+ switch (types.Count)
+ {
+ case 1:
+ return types[0] switch
+ {
+ // .ctor(char[])
+ SzArrayTypeSignature { BaseType.ElementType: ElementType.Char }
+ => ConstructCharArrayString(context, arguments),
+
+ // .ctor(char*)
+ PointerTypeSignature { BaseType.ElementType: ElementType.Char }
+ => ConstructCharPointerString(context, arguments),
+
+ // .ctor(sbyte*)
+ PointerTypeSignature { BaseType.ElementType: ElementType.I1 }
+ => ConstructSBytePointerString(context, arguments),
+
+ // other
+ _ => AllocationResult.Inconclusive()
+ };
+
+ case 2:
+ if (types[0].ElementType == ElementType.Char && types[1].ElementType == ElementType.I4)
+ {
+ // .ctor(char, int32)
+ return ConstructRepeatedCharString(context, arguments);
+ }
+
+ // other
+ return AllocationResult.Inconclusive();
+
+ case 3:
+ return types[0] switch
+ {
+ // .ctor(char[], int32, int32)
+ SzArrayTypeSignature { BaseType.ElementType: ElementType.Char }
+ => ConstructSizedCharArrayString(context, arguments),
+
+ // // .ctor(char*, int32, int32)
+ PointerTypeSignature { BaseType.ElementType: ElementType.Char }
+ => ConstructSizedCharPointerString(context, arguments),
+
+ // // .ctor(sbyte*, int32, int32)
+ PointerTypeSignature { BaseType.ElementType: ElementType.I1 }
+ => ConstructSizedSBytePointerString(context, arguments),
+
+ // other
+ _ => AllocationResult.Inconclusive()
+ };
+ }
+
+ return AllocationResult.Inconclusive();
+ }
+
+ private static AllocationResult ConstructRepeatedCharString(CilExecutionContext context, IList arguments)
+ {
+ char c = (char)arguments[0].AsSpan().U16;
+ int length = arguments[1].AsSpan().I32;
+
+ long result = context.Machine.Heap.AllocateString(new string(c, length));
+
+ return AllocationResult.FullyConstructed(context.Machine.ValueFactory.RentNativeInteger(result));
+ }
+
+ private static AllocationResult ConstructCharArrayString(CilExecutionContext context, IList arguments)
+ {
+ // Get array behind object.
+ var array = arguments[0].AsObjectHandle(context.Machine);
+
+ // Read chars from array.
+ long result = array.Address != 0
+ ? context.Machine.Heap.AllocateString(array.ReadArrayData())
+ : 0;
+
+ return AllocationResult.FullyConstructed(context.Machine.ValueFactory.RentNativeInteger(result));
+ }
+
+ private static AllocationResult ConstructSizedCharArrayString(CilExecutionContext context,
+ IList arguments)
+ {
+ // Get array behind object.
+ var array = arguments[0].AsObjectHandle(context.Machine);
+ int startIndex = arguments[1].AsSpan().I32;
+ int length = arguments[2].AsSpan().I32;
+
+ // Read chars from array.
+ long result = array.Address != 0
+ ? context.Machine.Heap.AllocateString(array.ReadArrayData(startIndex, length))
+ : 0;
+
+ return AllocationResult.FullyConstructed(context.Machine.ValueFactory.RentNativeInteger(result));
+ }
+
+ private static AllocationResult ConstructCharPointerString(CilExecutionContext context, IList arguments)
+ {
+ // Measure bounds.
+ long startAddress = arguments[0].AsSpan().ReadNativeInteger(context.Machine.Is32Bit);
+ long length = GetNullTerminatedStringLength(context, startAddress, sizeof(char));
+
+ // Construct string.
+ return ConstructSizedCharPointerString(context, startAddress, 0, (int)length);
+ }
+
+ private static AllocationResult ConstructSizedCharPointerString(CilExecutionContext context,
+ IList arguments)
+ {
+ // Measure bounds.
+ long startAddress = arguments[0].AsSpan().ReadNativeInteger(context.Machine.Is32Bit);
+ int startIndex = arguments[1].AsSpan().I32;
+ int length = arguments[2].AsSpan().I32;
+
+ // Construct string.
+ return ConstructSizedCharPointerString(context, startAddress, startIndex, length);
+ }
+
+ private static AllocationResult ConstructSizedCharPointerString(
+ CilExecutionContext context,
+ long startAddress,
+ int startIndex,
+ int length)
+ {
+ // Read Unicode bytes.
+ var totalData = new BitVector(length * sizeof(char) * 8, false);
+ context.Machine.Memory.Read(startAddress + startIndex * sizeof(char), totalData);
+
+ // Construct string.
+ long result = context.Machine.Heap.AllocateString(totalData);
+ return AllocationResult.FullyConstructed(context.Machine.ValueFactory.RentNativeInteger(result));
+ }
+
+ private static AllocationResult ConstructSBytePointerString(CilExecutionContext context, IList arguments)
+ {
+ // Measure bounds.
+ long startAddress = arguments[0].AsSpan().ReadNativeInteger(context.Machine.Is32Bit);
+ long length = GetNullTerminatedStringLength(context, startAddress, sizeof(sbyte));
+
+ // Construct string.
+ return ConstructSizedSBytePointerString(context, startAddress, (int)length);
+ }
+
+ private static AllocationResult ConstructSizedSBytePointerString(CilExecutionContext context,
+ IList arguments)
+ {
+ // Measure bounds.
+ long startAddress = arguments[0].AsSpan().ReadNativeInteger(context.Machine.Is32Bit);
+ int startIndex = arguments[1].AsSpan().I32;
+ int length = arguments[2].AsSpan().I32;
+
+ // Construct string.
+ return ConstructSizedSBytePointerString(context, startAddress + startIndex, length);
+ }
+
+ private static AllocationResult ConstructSizedSBytePointerString(CilExecutionContext context, long startAddress,
+ int length)
+ {
+ // Read ASCII bytes.
+ var totalData = new BitVector(length * 8, false);
+ context.Machine.Memory.Read(startAddress, totalData);
+
+ // Convert all ASCII bytes to Unicode bytes.
+ var widened = new BitVector(totalData.Count * 2, false);
+ for (int i = 0; i < totalData.ByteCount; i++)
+ {
+ widened.Bits[i * 2] = totalData.Bits[i];
+ widened.KnownMask[i * 2] = totalData.KnownMask[i];
+ widened.KnownMask[i * 2 + 1] = 0xFF;
+ }
+
+ // Construct string.
+ long result = context.Machine.Heap.AllocateString(widened);
+ return AllocationResult.FullyConstructed(context.Machine.ValueFactory.RentNativeInteger(result));
+ }
+
+ private static long GetNullTerminatedStringLength(CilExecutionContext context, long startAddress, int charSize)
+ {
+ long endAddress = startAddress;
+
+ var singleChar = context.Machine.ValueFactory.BitVectorPool.Rent(charSize * 8, false);
+ try
+ {
+ bool foundNullTerminator = false;
+ while (!foundNullTerminator)
+ {
+ context.Machine.Memory.Read(endAddress, singleChar);
+ switch (singleChar.AsSpan().IsZero.Value)
+ {
+ case TrileanValue.True:
+ // We definitely found a zero.
+ foundNullTerminator = true;
+ break;
+
+ case TrileanValue.False:
+ // We definitely found a non-zero value.
+ endAddress += charSize;
+ break;
+
+ case TrileanValue.Unknown:
+ // We are not sure this is a zero. We cannot continue.
+ throw new CilEmulatorException(
+ $"Attempted to read a null-terminated string at 0x{startAddress:X8} where the final size is uncertain.");
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+ finally
+ {
+ context.Machine.ValueFactory.BitVectorPool.Return(singleChar);
+ }
+
+ return (endAddress - startAddress) / charSize;
+ }
+}
\ No newline at end of file
diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/ObjectHandle.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/ObjectHandle.cs
index bd33d3b6..e5216309 100644
--- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/ObjectHandle.cs
+++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/ObjectHandle.cs
@@ -10,7 +10,6 @@ namespace Echo.Platforms.AsmResolver.Emulation
///
/// Represents an address to an object (including its object header) within a CIL virtual machine.
///
- [DebuggerDisplay("{Address} ({Tag})")]
public readonly struct ObjectHandle : IEquatable
{
///
@@ -51,16 +50,16 @@ public long Address
public StructHandle Contents => new(Machine, Address + Machine.ValueFactory.ObjectHeaderSize);
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- internal string Tag
+ internal object? Tag
{
get
{
if (IsNull)
- return "null";
+ return null;
try
{
- return GetObjectType().FullName;
+ return GetObjectType();
}
catch
{
@@ -204,10 +203,6 @@ public void ReadArrayLength(BitVectorSpan buffer)
/// Occurs when the array has an unknown length.
public BitVector ReadArrayData()
{
- var arrayType = GetObjectType();
- if (arrayType is not SzArrayTypeSignature { BaseType: { } elementType })
- throw new ArgumentException("The object handle does not point to an array type");
-
var length = Machine.ValueFactory.BitVectorPool.Rent(32, false);
try
{
@@ -215,20 +210,38 @@ public BitVector ReadArrayData()
ReadArrayLength(length);
if (!length.AsSpan().IsFullyKnown)
throw new ArgumentException("The array has an unknown length.");
-
- // Determine total space required to store the array data.
- int elementSize = (int) Machine.ValueFactory.GetTypeValueMemoryLayout(elementType).Size;
- var result = new BitVector(length.AsSpan().I32 * elementSize * 8, false);
// Read the data.
- ReadArrayData(result);
-
- return result;
+ return ReadArrayData(0, length.AsSpan().I32);
}
finally
{
Machine.ValueFactory.BitVectorPool.Return(length);
- }
+ }
+ }
+
+ ///
+ /// Interprets the handle as an array reference, and obtains all elements of the array as one continuous buffer
+ /// of bytes.
+ ///
+ /// The index to start reading from.
+ /// The number of elements to read.
+ /// The raw array data.
+ /// Occurs when the array has an unknown length.
+ public BitVector ReadArrayData(int startIndex, int length)
+ {
+ var arrayType = GetObjectType();
+ if (arrayType is not SzArrayTypeSignature { BaseType: { } elementType })
+ throw new ArgumentException("The object handle does not point to an array type");
+
+ // Determine total space required to store the array data.
+ int elementSize = (int) Machine.ValueFactory.GetTypeValueMemoryLayout(elementType).Size;
+ var result = new BitVector(length * elementSize * 8, false);
+
+ // Read the data.
+ ReadArrayData(result, startIndex, elementType);
+
+ return result;
}
///
@@ -242,6 +255,36 @@ public void ReadArrayData(BitVectorSpan buffer)
Machine.Memory.Read(Address + Machine.ValueFactory.ArrayHeaderSize, buffer);
}
+ ///
+ /// Interprets the handle as an array reference, and obtains all elements of the array as one continuous buffer
+ /// of bytes.
+ ///
+ /// The buffer to copy the array data into.
+ /// The start index to read from.
+ /// The raw array data.
+ public void ReadArrayData(BitVectorSpan buffer, int startIndex)
+ {
+ var arrayType = GetObjectType();
+ if (arrayType is not SzArrayTypeSignature { BaseType: { } elementType })
+ throw new ArgumentException("The object handle does not point to an array type");
+
+ ReadArrayData(buffer, startIndex, elementType);
+ }
+
+ ///
+ /// Interprets the handle as an array reference, and obtains all elements of the array as one continuous buffer
+ /// of bytes.
+ ///
+ /// The buffer to copy the array data into.
+ /// The start index to read from.
+ /// The element type to assume.
+ /// The raw array data.
+ public void ReadArrayData(BitVectorSpan buffer, int startIndex, TypeSignature elementType)
+ {
+ int elementSize = (int) Machine.ValueFactory.GetTypeValueMemoryLayout(elementType).Size;
+ Machine.Memory.Read(Address + Machine.ValueFactory.ArrayHeaderSize + startIndex * elementSize, buffer);
+ }
+
///
/// Interprets the handle as an array reference, and writes elements to the array's data as one continuous buffer
/// of bytes.
@@ -257,7 +300,7 @@ public void WriteArrayData(BitVectorSpan buffer)
/// of bytes.
///
/// The buffer to copy the array data from.
- public void WriteArrayData(byte[] buffer)
+ public void WriteArrayData(ReadOnlySpan buffer)
{
Machine.Memory.Write(Address + Machine.ValueFactory.ArrayHeaderSize, buffer);
}
@@ -362,6 +405,6 @@ public override int GetHashCode()
}
///
- public override string ToString() => $"0x{Address.ToString(Machine.Is32Bit ? "X8" : "X16")}";
+ public override string ToString() => $"0x{Address.ToString(Machine.Is32Bit ? "X8" : "X16")} ({Tag})";
}
}
\ No newline at end of file
diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Echo.Platforms.AsmResolver.Tests.csproj b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Echo.Platforms.AsmResolver.Tests.csproj
index 1b4bc732..9795f218 100644
--- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Echo.Platforms.AsmResolver.Tests.csproj
+++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Echo.Platforms.AsmResolver.Tests.csproj
@@ -8,6 +8,8 @@
true
enable
+
+ 12
diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Heap/ManagedObjectHeapTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Heap/ManagedObjectHeapTest.cs
index 93ea0cb6..87bd70d7 100644
--- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Heap/ManagedObjectHeapTest.cs
+++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Heap/ManagedObjectHeapTest.cs
@@ -47,7 +47,7 @@ public unsafe void ValidateObjectSlicesAreCorrect(string value)
{
// Beautiful pointer abuse to read the raw data of the string.
var rawObjectAddress = *(nint*) Unsafe.AsPointer(ref value);
- byte[] data = new byte[sizeof(nint) + sizeof(int) + value.Length * sizeof(char)];
+ byte[] data = new byte[sizeof(nint) + sizeof(int) + (value.Length+1) * sizeof(char)];
Marshal.Copy(rawObjectAddress, data, 0, data.Length);
var objectSpan = new BitVector(data).AsSpan();
diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Invocation/StringAllocatorTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Invocation/StringAllocatorTest.cs
new file mode 100644
index 00000000..7a0a405f
--- /dev/null
+++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Invocation/StringAllocatorTest.cs
@@ -0,0 +1,273 @@
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using AsmResolver.DotNet;
+using AsmResolver.DotNet.Signatures.Types;
+using Echo.Memory;
+using Echo.Platforms.AsmResolver.Emulation;
+using Echo.Platforms.AsmResolver.Emulation.Dispatch;
+using Echo.Platforms.AsmResolver.Emulation.Invocation;
+using Echo.Platforms.AsmResolver.Tests.Mock;
+using Xunit;
+
+namespace Echo.Platforms.AsmResolver.Tests.Emulation.Invocation;
+
+public class StringAllocatorTest : IClassFixture
+{
+ private readonly CilExecutionContext _context;
+ private readonly TypeDefinition _stringType;
+ private readonly CorLibTypeFactory _corlibFactory;
+
+ private readonly StringAllocator _allocator = new();
+
+ public StringAllocatorTest(MockModuleFixture fixture)
+ {
+ var machine = new CilVirtualMachine(fixture.MockModule, false);
+ var thread = machine.CreateThread();
+ _context = new CilExecutionContext(thread, CancellationToken.None);
+ _context.Thread.CallStack.Push(fixture.MockModule.GetOrCreateModuleConstructor());
+
+ _corlibFactory = fixture.MockModule.CorLibTypeFactory;
+ _stringType = _corlibFactory.String.Type.Resolve()!;
+ }
+
+ [Fact]
+ public void UsingCharArrayConstructorNull()
+ {
+ // Locate .ctor(char[])
+ var ctor = _stringType.GetConstructor(_corlibFactory.Char.MakeSzArrayType())!;
+
+ // Allocate using null pointer.
+ var result = _allocator.Allocate(_context, ctor, [_context.Machine.ValueFactory.CreateNull()]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+ Assert.Equal(0, result.Address!.AsObjectHandle(_context.Machine).Address);
+ }
+
+ [Fact]
+ public void UsingCharArrayConstructorNonNull()
+ {
+ const string expectedString = "Hello, world!";
+
+ // Allocate a char[].
+ char[] data = expectedString.ToCharArray();
+ var array = _context.Machine.Heap
+ .AllocateSzArray(_corlibFactory.Char, data.Length, true)
+ .AsObjectHandle(_context.Machine);
+
+ array.WriteArrayData(MemoryMarshal.Cast(data));
+
+ // Allocate string using .ctor(char[])
+ var ctor = _stringType.GetConstructor(_corlibFactory.Char.MakeSzArrayType())!;
+ var result = _allocator.Allocate(_context, ctor, [
+ _context.Machine.ValueFactory.CreateNativeInteger(array.Address)
+ ]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(expectedString, Encoding.Unicode.GetString(resultObject.ReadStringData().Bits));
+ Assert.Equal(data.Length, resultObject.ReadStringLength().AsSpan().I32);
+ }
+
+ [Fact]
+ public void UsingCharArrayConstructorSized()
+ {
+ const string expectedString = "Hello, world!";
+ const int startIndex = 2;
+ const int length = 6;
+
+ // Allocate a char[].
+ char[] data = expectedString.ToCharArray();
+ var array = _context.Machine.Heap
+ .AllocateSzArray(_corlibFactory.Char, data.Length, true)
+ .AsObjectHandle(_context.Machine);
+
+ array.WriteArrayData(MemoryMarshal.Cast(data));
+
+ // Allocate string using .ctor(char[], int32, int32)
+ var ctor = _stringType.GetConstructor(
+ _corlibFactory.Char.MakeSzArrayType(),
+ _corlibFactory.Int32,
+ _corlibFactory.Int32
+ )!;
+ var result = _allocator.Allocate(_context, ctor, [
+ _context.Machine.ValueFactory.CreateNativeInteger(array.Address),
+ new BitVector(startIndex),
+ new BitVector(length)
+ ]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(expectedString[startIndex..(startIndex + length)],
+ Encoding.Unicode.GetString(resultObject.ReadStringData().Bits));
+ Assert.Equal(length, resultObject.ReadStringLength().AsSpan().I32);
+ }
+
+ [Fact]
+ public void UsingCharPointerConstructor()
+ {
+ const string expectedString = "Hello, world!";
+
+ // Allocate some flat memory.
+ char[] data = expectedString.ToCharArray();
+ long address = _context.Machine.Heap.AllocateFlat((uint)(data.Length + 1) * sizeof(char), true);
+ _context.Machine.Memory.Write(address, MemoryMarshal.Cast(data));
+
+ // Allocate string using .ctor(char*)
+ var ctor = _stringType.GetConstructor(_corlibFactory.Char.MakePointerType())!;
+ var result = _allocator.Allocate(_context, ctor, [_context.Machine.ValueFactory.CreateNativeInteger(address)]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(expectedString, Encoding.Unicode.GetString(resultObject.ReadStringData().Bits));
+ Assert.Equal(data.Length, resultObject.ReadStringLength().AsSpan().I32);
+ }
+
+ [Fact]
+ public void UsingCharPointerConstructorSized()
+ {
+ const string expectedString = "Hello, world!";
+ const int startIndex = 2;
+ const int length = 6;
+
+ // Allocate some flat memory.
+ char[] data = expectedString.ToCharArray();
+ long address = _context.Machine.Heap.AllocateFlat((uint)(data.Length + 1) * sizeof(char), true);
+ _context.Machine.Memory.Write(address, MemoryMarshal.Cast(data));
+
+ // Allocate string using .ctor(char*, int32, int32)
+ var ctor = _stringType.GetConstructor(
+ _corlibFactory.Char.MakePointerType(),
+ _corlibFactory.Int32,
+ _corlibFactory.Int32
+ )!;
+ var result = _allocator.Allocate(_context, ctor, [
+ _context.Machine.ValueFactory.CreateNativeInteger(address),
+ new BitVector(startIndex),
+ new BitVector(length)
+ ]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(expectedString[startIndex..(startIndex + length)],
+ Encoding.Unicode.GetString(resultObject.ReadStringData().Bits));
+ Assert.Equal(length, resultObject.ReadStringLength().AsSpan().I32);
+ }
+
+ [Fact]
+ public void UsingCharPointerConstructorWithUnknownBits()
+ {
+ // Allocate some flat memory with unknown bits.
+ long address = _context.Machine.Heap.AllocateFlat(20, false);
+
+ // Attempt to allocate string using .ctor(char*)
+ var ctor = _stringType.GetConstructor(_corlibFactory.Char.MakePointerType())!;
+ Assert.ThrowsAny(() =>
+ {
+ _allocator.Allocate(_context, ctor, [_context.Machine.ValueFactory.CreateNativeInteger(address)]);
+ });
+ }
+
+ [Fact]
+ public void UsingCharPointerConstructorWithUnknownBitsBounded()
+ {
+ // Allocate some flat memory with unknown bits.
+ long address = _context.Machine.Heap.AllocateFlat(20, false);
+ _context.Machine.Memory.Write(address, new BitVector(
+ [0x41, 0x00, 0x41, 0x00, 0x00, 0x00],
+ [0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF]
+ ));
+
+ // Allocate string using .ctor(char*)
+ var ctor = _stringType.GetConstructor(_corlibFactory.Char.MakePointerType())!;
+ var result = _allocator.Allocate(_context, ctor, [_context.Machine.ValueFactory.CreateNativeInteger(address)]);
+
+ // Verify
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(2, resultObject.ReadStringLength().AsSpan().I32);
+ }
+
+ [Fact]
+ public void UsingSBytePointerConstructor()
+ {
+ const string expectedString = "Hello, world!";
+
+ // Allocate some flat memory.
+ byte[] data = Encoding.ASCII.GetBytes(expectedString);
+ long address = _context.Machine.Heap.AllocateFlat((uint)(data.Length + 1), true);
+ _context.Machine.Memory.Write(address, data);
+
+ // Allocate string using .ctor(sbyte*)
+ var ctor = _stringType.GetConstructor(_corlibFactory.SByte.MakePointerType())!;
+ var result = _allocator.Allocate(_context, ctor, [_context.Machine.ValueFactory.CreateNativeInteger(address)]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(expectedString, Encoding.Unicode.GetString(resultObject.ReadStringData().Bits));
+ Assert.Equal(data.Length, resultObject.ReadStringLength().AsSpan().I32);
+ }
+
+ [Fact]
+ public void UsingSbytePointerConstructorSized()
+ {
+ const string expectedString = "Hello, world!";
+ const int startIndex = 2;
+ const int length = 6;
+
+ // Allocate some flat memory.
+ byte[] data = Encoding.ASCII.GetBytes(expectedString);
+ long address = _context.Machine.Heap.AllocateFlat((uint)(data.Length + 1), true);
+ _context.Machine.Memory.Write(address, data);
+
+ // Allocate string using .ctor(sbyte*, int32, int32)
+ var ctor = _stringType.GetConstructor(
+ _corlibFactory.SByte.MakePointerType(),
+ _corlibFactory.Int32,
+ _corlibFactory.Int32
+ )!;
+ var result = _allocator.Allocate(_context, ctor, [
+ _context.Machine.ValueFactory.CreateNativeInteger(address),
+ new BitVector(startIndex),
+ new BitVector(length)
+ ]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(expectedString[startIndex..(startIndex + length)],
+ Encoding.Unicode.GetString(resultObject.ReadStringData().Bits));
+ Assert.Equal(length, resultObject.ReadStringLength().AsSpan().I32);
+ }
+
+ [Fact]
+ public void UsingCharInt32Constructor()
+ {
+ // Allocate string using .ctor(char, int32)
+ var ctor = _stringType.GetConstructor(_corlibFactory.Char, _corlibFactory.Int32)!;
+ var result = _allocator.Allocate(_context, ctor, [
+ new BitVector('A'),
+ new BitVector(30)
+ ]);
+
+ // Verify.
+ Assert.Equal(AllocationResultType.FullyConstructed, result.ResultType);
+
+ var resultObject = result.Address!.AsObjectHandle(_context.Machine);
+ Assert.Equal(new string('A', 30), Encoding.Unicode.GetString(resultObject.ReadStringData().Bits));
+ Assert.Equal(30, resultObject.ReadStringLength().AsSpan().I32);
+ }
+}
\ No newline at end of file