Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,17 @@ public long GetInternedString(string value)
return address;
}

/// <summary>
/// Attempts to get the address of an already interned string in the virtual managed heap.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="address">When this method returns <c>true</c>, contains the address of the interned string.</param>
/// <returns><c>true</c> if the string was found in the intern pool; otherwise, <c>false</c>.</returns>
public bool TryGetInternedString(string value, out long address)
{
return _internedStrings.TryGetValue(value, out address);
}

private void SetMethodTable(BitVectorSpan objectSpan, ITypeDescriptor type) => objectSpan
.SliceObjectMethodTable(_factory)
.WriteNativeInteger(_factory.ClrMockMemory.MethodTables.GetAddress(type), _factory.Is32Bit);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using AsmResolver.DotNet;
using Echo.Memory;
using Echo.Platforms.AsmResolver.Emulation.Dispatch;
Expand All @@ -26,7 +26,11 @@ public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor me
{
case "FastAllocateString":
return InvokeFastAllocateString(context, arguments);

case "Intern":
return InvokeIntern(context, arguments);
case "IsInterned":
return InvokeIsInterned(context, arguments);

default:
return InvocationResult.Inconclusive();
}
Expand All @@ -43,4 +47,52 @@ private static InvocationResult InvokeFastAllocateString(CilExecutionContext con

return InvocationResult.StepOver(result);
}

private static unsafe InvocationResult InvokeIntern(CilExecutionContext context, IList<BitVector> arguments)
{
var strArgument = arguments[0];
if (!strArgument.IsFullyKnown)
throw new CilEmulatorException("Cannot intern an unknown string.");

var strHandle = strArgument.AsObjectHandle(context.Machine);
if (strHandle.IsNull)
throw new CilEmulatorException("Cannot intern a null string.");

var strData = strHandle.ReadStringData();
if (!strData.IsFullyKnown)
throw new CilEmulatorException("Cannot intern an unknown string.");

var chars = MemoryMarshal.Cast<byte, char>(strData.Bits);
fixed (char* ptr = chars)
{
string str = new(ptr, 0, chars.Length);
long internedAddress = context.Machine.Heap.GetInternedString(str);
return InvocationResult.StepOver(context.Machine.ValueFactory.RentNativeInteger(internedAddress));
}
}

private static unsafe InvocationResult InvokeIsInterned(CilExecutionContext context, IList<BitVector> arguments)
{
var strArgument = arguments[0];
if (!strArgument.IsFullyKnown)
throw new CilEmulatorException("Cannot check intern status of an unknown string.");

var strHandle = strArgument.AsObjectHandle(context.Machine);
if (strHandle.IsNull)
throw new CilEmulatorException("Cannot check intern status of a null string.");

var strData = strHandle.ReadStringData();
if (!strData.IsFullyKnown)
throw new CilEmulatorException("Cannot check intern status of an unknown string.");

var chars = MemoryMarshal.Cast<byte, char>(strData.Bits);
fixed (char* ptr = chars)
{
string str = new(ptr, 0, chars.Length);
if (context.Machine.Heap.TryGetInternedString(str, out long internedAddress))
return InvocationResult.StepOver(context.Machine.ValueFactory.RentNativeInteger(internedAddress));

return InvocationResult.StepOver(null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,92 @@ public void DelegateInvokeOnUnknownBackingMethod()
Assert.Equal(method, _context.Thread.CallStack.Peek().Method);
}

[Fact]
public void InternString()
{
var invoker = StringInvoker.Instance;
var stringType = _fixture.MockModule.CorLibTypeFactory.String.Type.Resolve()!;
var internMethod = stringType.Methods.First(m => m.Name == "Intern");

const string str = "Hello, world!";
long strAddress = _context.Machine.Heap.AllocateString(str);
var strArg = _context.Machine.ValueFactory.RentNativeInteger(strAddress);

var result = invoker.Invoke(_context, internMethod, [strArg]);

Assert.True(result.IsSuccess);
Assert.True(result.Value.IsFullyKnown);

Assert.True(_context.Machine.Heap.TryGetInternedString(str, out long internedAddress));
Assert.Equal(internedAddress, result.Value.AsSpan().ReadNativeInteger(_context.Machine.Is32Bit));
}

[Fact]
public void InternSameStringTwiceReturnsSameAddress()
{
var invoker = StringInvoker.Instance;
var stringType = _fixture.MockModule.CorLibTypeFactory.String.Type.Resolve()!;
var internMethod = stringType.Methods.First(m => m.Name == "Intern");

const string str = "Hello, world!";
long strAddress1 = _context.Machine.Heap.AllocateString(str);
long strAddress2 = _context.Machine.Heap.AllocateString(str);

var result1 = invoker.Invoke(_context, internMethod, [_context.Machine.ValueFactory.RentNativeInteger(strAddress1)]);
var result2 = invoker.Invoke(_context, internMethod, [_context.Machine.ValueFactory.RentNativeInteger(strAddress2)]);

Assert.True(result1.IsSuccess);
Assert.True(result2.IsSuccess);

Assert.True(result1.Value.IsFullyKnown);
Assert.True(result2.Value.IsFullyKnown);

Assert.Equal(result1.Value.AsSpan().ReadNativeInteger(_context.Machine.Is32Bit),
result2.Value.AsSpan().ReadNativeInteger(_context.Machine.Is32Bit));
}

[Fact]
public void IsInternedOnInternedString()
{
var invoker = StringInvoker.Instance;
var stringType = _fixture.MockModule.CorLibTypeFactory.String.Type.Resolve()!;
var internMethod = stringType.Methods.First(m => m.Name == "Intern");
var isInternedMethod = stringType.Methods.First(m => m.Name == "IsInterned");

const string str = "Hello, world!";
long strAddress = _context.Machine.Heap.AllocateString(str);
var internResult = invoker.Invoke(_context, internMethod,
[_context.Machine.ValueFactory.RentNativeInteger(strAddress)]);

long strAddress2 = _context.Machine.Heap.AllocateString(str);
var isInternedResult = invoker.Invoke(_context, isInternedMethod,
[_context.Machine.ValueFactory.RentNativeInteger(strAddress2)]);

Assert.True(internResult.IsSuccess);
Assert.True(internResult.Value?.IsFullyKnown);

Assert.True(isInternedResult.IsSuccess);
Assert.True(isInternedResult.Value?.IsFullyKnown);

Assert.Equal(internResult.Value!.AsSpan().ReadNativeInteger(_context.Machine.Is32Bit),
isInternedResult.Value!.AsSpan().ReadNativeInteger(_context.Machine.Is32Bit));
}

[Fact]
public void IsInternedOnNonInternedString()
{
var invoker = StringInvoker.Instance;
var stringType = _fixture.MockModule.CorLibTypeFactory.String.Type.Resolve()!;
var isInternedMethod = stringType.Methods.First(m => m.Name == "IsInterned");

const string str = "Never interned";
long strAddress = _context.Machine.Heap.AllocateString(str);
var result = invoker.Invoke(_context, isInternedMethod, [_context.Machine.ValueFactory.RentNativeInteger(strAddress)]);

Assert.True(result.IsSuccess);
Assert.Null(result.Value);
}

private sealed class TestDelegateUnknownResolver(IMethodDescriptor method) : ThrowUnknownResolver
{
public override IMethodDescriptor? ResolveDelegateTarget(
Expand Down
Loading