using System.Buffers.Binary;
using System.Reflection.Metadata.Ecma335;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFESystem.Console;
using Xunit;
using Xunit.Abstractions;
namespace XFESystem.ManagedRuntime.Tests;
public sealed class ManagedApplicationRunnerTests(ITestOutputHelper log)
{
[Fact]
public void RealNet10SampleRunsThroughIlInterpreterAndActualXfeConsoleBinding()
{
using var image = new ManagedImage("XFESystem.ManagedHello.dll");
var terminal = new TerminalTextWriter(new TerminalBuffer(160, 20));
var result = new ManagedApplicationRunner().Execute(image.Bytes, new TerminalManagedConsole(terminal));
log.WriteLine("ManagedSample: " + JsonSerializer.Serialize(result));
log.WriteLine("ManagedSampleSha256: " + Convert.ToHexStringLower(SHA256.HashData(image.Bytes)));
var snapshot = terminal.Snapshot();
var text = Text(snapshot);
Assert.Equal("XFESystem.ManagedHello", result.AssemblyName);
Assert.Equal(0, result.ExitCode);
Assert.True(result.Instructions > 10);
Assert.Equal(7, result.InteropCalls);
Assert.Contains("XFE_MANAGED_XFECONSOLE_OK", text);
Assert.Contains("XFE_MANAGED_APP_OK", text);
Assert.Contains("ANSI RGB: #75C9F5", text);
Assert.Equal(TerminalColor.FromConsoleColor(ConsoleColor.Cyan), snapshot.GetCell(0, 0).Style.Foreground);
Assert.Equal(TerminalColor.FromConsoleColor(ConsoleColor.DarkBlue), snapshot.GetCell(0, 0).Style.Background);
Assert.Equal(TerminalColor.ThemeForeground, snapshot.GetCell(0, 1).Style.Foreground);
Assert.Equal(TerminalColor.ThemeBackground, snapshot.GetCell(0, 1).Style.Background);
}
[Fact]
public void GettersLoopsArithmeticAndRgbAreExecutedFromARealCompiledFixture()
{
using var image = new ManagedImage();
var terminal = new TerminalTextWriter(new TerminalBuffer(100, 12));
var result = new ManagedApplicationRunner().Execute(image.Bytes, new TerminalManagedConsole(terminal));
var snapshot = terminal.Snapshot();
Assert.Contains("GETTERS_OK", Text(snapshot));
Assert.DoesNotContain("GETTERS_FAILED", Text(snapshot));
Assert.Contains("\n6", Text(snapshot));
Assert.Contains("FIXTURE_OK", Text(snapshot));
Assert.Equal(new TerminalColor(1, 2, 3), snapshot.GetCell(0, 2).Style.Foreground);
Assert.Equal(TerminalColor.FromConsoleColor(ConsoleColor.DarkMagenta), snapshot.GetCell(0, 0).Style.Background);
Assert.Equal(0, result.ExitCode);
}
[Fact]
public void InstructionBudgetStopsBeforeSecondInstructionWithoutSuccessMarker()
{
using var image = new ManagedImage();
var console = new RecordingConsole();
var error = Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.Bytes, console, 1));
Assert.Contains("instruction limit", error.Message);
Assert.Equal(ConsoleColor.Gray, console.ForegroundColor);
Assert.Empty(console.Output);
var second = new RecordingConsole();
Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.Bytes, second, 2));
Assert.Equal(ConsoleColor.Green, second.ForegroundColor);
Assert.Empty(second.Output);
}
[Theory]
[InlineData(0)]
[InlineData(100001)]
public void InvalidInstructionBudgetsAreRejected(int budget)
{
using var image = new ManagedImage();
Assert.Throws<ArgumentOutOfRangeException>(() => new ManagedApplicationRunner().Execute(image.Bytes, new RecordingConsole(), budget));
}
[Fact]
public void OversizedAndBrokenPeImagesAreRejected()
{
var runner = new ManagedApplicationRunner();
Assert.Throws<BadImageFormatException>(() => runner.Execute(new byte[127], new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(new byte[4096], new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(new byte[ManagedRuntimeCapabilities.MaxImageBytes + 1], new RecordingConsole()));
}
[Fact]
public void WrongTargetFrameworkIsRejectedBeforeAnyConsoleEffects()
{
using var image = new ManagedImage();
var copy = image.Bytes.ToArray();
var original = Encoding.UTF8.GetBytes(ManagedRuntimeCapabilities.TargetFramework);
int at = copy.AsSpan().IndexOf(original);
Assert.True(at >= 0);
Encoding.UTF8.GetBytes(".NETCoreApp,Version=v11.0").CopyTo(copy, at);
var console = new RecordingConsole();
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(copy, console));
Assert.Empty(console.Output);
}
[Fact]
public void NativeEntryFlagsAndInvalidEntryTokensAreRejected()
{
using var image = new ManagedImage();
var native = image.Bytes.ToArray();
BinaryPrimitives.WriteInt32LittleEndian(native.AsSpan(image.CorHeaderOffset + 16), 0x10);
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(native, new RecordingConsole()));
var invalid = image.Bytes.ToArray();
BinaryPrimitives.WriteInt32LittleEndian(invalid.AsSpan(image.CorHeaderOffset + 20), 0x0A000001);
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(invalid, new RecordingConsole()));
}
[Fact]
public void UnknownOpcodeIsRejectedBeforeExecutingEarlierOutput()
{
using var image = new ManagedImage();
var copy = image.Bytes.ToArray();
copy[image.CodeOffset + image.CodeLength - 1] = 0xFF;
var console = new RecordingConsole();
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(copy, console));
Assert.Empty(console.Output);
}
[Fact]
public void TruncatedOperandIsRejected()
{
using var image = new ManagedImage();
var copy = image.WithCode();
copy[image.CodeOffset + image.CodeLength - 1] = 0x20;
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Theory]
[InlineData(1)]
[InlineData(127)]
public void BranchIntoOperandOrOutsideMethodIsRejected(byte offset)
{
using var image = new ManagedImage();
var copy = image.WithCode(0x2B, offset, 0x20, 0, 0, 0, 0, 0x26, 0x2A);
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Fact]
public void EvaluationStackAndLocalsAreValidated()
{
using var image = new ManagedImage();
var runner = new ManagedApplicationRunner();
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(0x26, 0x2A), new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(0x11, 0xFF, 0x26, 0x2A), new RecordingConsole()));
var overflow = new List<byte> { 0x16 };
overflow.AddRange(Enumerable.Repeat((byte)0x25, image.MaximumStack));
overflow.Add(0x2A);
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(overflow.ToArray()), new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(), new RecordingConsole()));
}
[Fact]
public void UserStringTokenMustUseTheUserStringHeap()
{
using var image = new ManagedImage();
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(
image.WithTokenInstruction(0x72, 0x0A000001, 0x26, 0x2A), new RecordingConsole()));
}
[Fact]
public void CallsCannotReachHostFilesLocalMethodsOrInstanceMembers()
{
using var image = new ManagedImage();
var code = new List<byte>();
Token(code, 0x72, image.UserString("NeverOpenThisFile"));
Token(code, 0x28, image.Member("System.IO.File", "ReadAllText"));
code.AddRange([0x26, 0x2A]);
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), new RecordingConsole()));
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(
image.WithTokenInstruction(0x28, MetadataTokens.GetToken(image.Metadata.MethodDefinitions.First()), 0x2A), new RecordingConsole()));
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(
image.WithTokenInstruction(0x28, image.Member("System.String", "ToUpperInvariant"), 0x2A), new RecordingConsole()));
}
[Fact]
public void ConsoleOutputBudgetStopsARealIlLoop()
{
using var image = new ManagedImage();
var code = new List<byte>();
Token(code, 0x72, image.UserString(string.Concat(Enumerable.Repeat("0123456789ABCDEF", 8))));
Token(code, 0x28, image.Member("System.Console", "Write"));
code.AddRange([0x2B, 0xF4]);
var terminal = new TerminalTextWriter(new TerminalBuffer(40, 5));
var error = Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), new TerminalManagedConsole(terminal)));
Assert.Contains("output limit", error.Message);
}
[Fact]
public void IntermediateConcatenationCannotGrowBeyondTheManagedStringBudget()
{
using var image = new ManagedImage();
var code = new List<byte>();
Token(code, 0x72, image.UserString(string.Concat(Enumerable.Repeat("0123456789ABCDEF", 8))));
for (int index = 0; index < 10; index++) { code.Add(0x25); Token(code, 0x28, image.Member("System.String", "Concat")); }
code.AddRange([0x26, 0x2A]);
Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), new RecordingConsole()));
}
[Fact]
public void MalformedGetterReturnSignatureCannotInvokeAPlatformBinding()
{
using var image = new ManagedImage();
var getter = image.Member("System.Console", "get_ForegroundColor");
var copy = image.WithTokenInstruction(0x28, getter, 0x26, 0x2A);
var member = image.Metadata.GetMemberReference(MetadataTokens.MemberReferenceHandle(getter & 0xFFFFFF));
copy[image.SignaturePayloadOffset(member.Signature) + 2] = 0x01; // void, while retaining the enum type token as malformed trailing data
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Fact]
public void InteropStringSignatureDoesNotAcceptAnInt32StackArgument()
{
using var image = new ManagedImage();
var code = new List<byte> { 0x17 };
var stringWriteLine = image.Metadata.MemberReferences.First(handle =>
{
var member = image.Metadata.GetMemberReference(handle);
return image.Metadata.GetString(member.Name) == "WriteLine" &&
image.Metadata.GetBlobBytes(member.Signature).AsSpan().SequenceEqual(new byte[] { 0x00, 0x01, 0x01, 0x0E });
});
Token(code, 0x28, MetadataTokens.GetToken(stringWriteLine));
code.Add(0x2A);
var console = new RecordingConsole();
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), console));
Assert.Empty(console.Output);
}
[Fact]
public void MainSignatureRejectsTrailingMetadataBytes()
{
using var image = new ManagedImage();
var copy = image.WithCode(0x2A);
var payload = image.SignaturePayloadOffset(image.Entry.Signature);
Assert.Equal(3, copy[payload - 1]);
copy[payload - 1] = 4;
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Fact]
public void EntryPointInt32ReturnBecomesTheExitCode()
{
using var image = new ManagedImage();
var copy = image.WithCode(0x1F, 42, 0x2A);
copy[image.SignaturePayloadOffset(image.Entry.Signature) + 2] = 0x08;
var result = new ManagedApplicationRunner().Execute(copy, new RecordingConsole());
Assert.Equal(42, result.ExitCode);
Assert.Equal(2, result.Instructions);
}
private static void Token(List<byte> code, byte opcode, int token)
{
code.Add(opcode);
byte[] bytes = new byte[4];
BinaryPrimitives.WriteInt32LittleEndian(bytes, token);
code.AddRange(bytes);
}
private static string Text(TerminalSnapshot snapshot) => string.Join("\n", Enumerable.Range(0, snapshot.Rows).Select(row =>
string.Concat(Enumerable.Range(0, snapshot.Columns).Select(column => snapshot.GetCell(column, row).Text)).TrimEnd()));
private sealed class RecordingConsole : IManagedConsole
{
public ConsoleColor ForegroundColor { get; set; } = ConsoleColor.Gray;
public ConsoleColor BackgroundColor { get; set; } = ConsoleColor.Black;
public string Output { get; private set; } = "";
public void ResetColor() { ForegroundColor = ConsoleColor.Gray; BackgroundColor = ConsoleColor.Black; }
public void Write(string value) => Output += value;
public void WriteLine(string value) => Output += value + "\n";
public void XfeWriteLine(string value) => Output += value + "\n";
}
}
using System.Buffers.Binary;
using System.Reflection.Metadata.Ecma335;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFESystem.Console;
using Xunit;
using Xunit.Abstractions;
namespace XFESystem.ManagedRuntime.Tests;
public sealed class ManagedApplicationRunnerTests(ITestOutputHelper log)
{
[Fact]
public void RealNet10SampleRunsThroughIlInterpreterAndActualXfeConsoleBinding()
{
using var image = new ManagedImage("XFESystem.ManagedHello.dll");
var terminal = new TerminalTextWriter(new TerminalBuffer(160, 20));
var result = new ManagedApplicationRunner().Execute(image.Bytes, new TerminalManagedConsole(terminal));
log.WriteLine("ManagedSample: " + JsonSerializer.Serialize(result));
log.WriteLine("ManagedSampleSha256: " + Convert.ToHexStringLower(SHA256.HashData(image.Bytes)));
var snapshot = terminal.Snapshot();
var text = Text(snapshot);
Assert.Equal("XFESystem.ManagedHello", result.AssemblyName);
Assert.Equal(0, result.ExitCode);
Assert.True(result.Instructions > 10);
Assert.Equal(7, result.InteropCalls);
Assert.Contains("XFE_MANAGED_XFECONSOLE_OK", text);
Assert.Contains("XFE_MANAGED_APP_OK", text);
Assert.Contains("ANSI RGB: #75C9F5", text);
Assert.Equal(TerminalColor.FromConsoleColor(ConsoleColor.Cyan), snapshot.GetCell(0, 0).Style.Foreground);
Assert.Equal(TerminalColor.FromConsoleColor(ConsoleColor.DarkBlue), snapshot.GetCell(0, 0).Style.Background);
Assert.Equal(TerminalColor.ThemeForeground, snapshot.GetCell(0, 1).Style.Foreground);
Assert.Equal(TerminalColor.ThemeBackground, snapshot.GetCell(0, 1).Style.Background);
}
[Fact]
public void GettersLoopsArithmeticAndRgbAreExecutedFromARealCompiledFixture()
{
using var image = new ManagedImage();
var terminal = new TerminalTextWriter(new TerminalBuffer(100, 12));
var result = new ManagedApplicationRunner().Execute(image.Bytes, new TerminalManagedConsole(terminal));
var snapshot = terminal.Snapshot();
Assert.Contains("GETTERS_OK", Text(snapshot));
Assert.DoesNotContain("GETTERS_FAILED", Text(snapshot));
Assert.Contains("\n6", Text(snapshot));
Assert.Contains("FIXTURE_OK", Text(snapshot));
Assert.Equal(new TerminalColor(1, 2, 3), snapshot.GetCell(0, 2).Style.Foreground);
Assert.Equal(TerminalColor.FromConsoleColor(ConsoleColor.DarkMagenta), snapshot.GetCell(0, 0).Style.Background);
Assert.Equal(0, result.ExitCode);
}
[Fact]
public void InstructionBudgetStopsBeforeSecondInstructionWithoutSuccessMarker()
{
using var image = new ManagedImage();
var console = new RecordingConsole();
var error = Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.Bytes, console, 1));
Assert.Contains("instruction limit", error.Message);
Assert.Equal(ConsoleColor.Gray, console.ForegroundColor);
Assert.Empty(console.Output);
var second = new RecordingConsole();
Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.Bytes, second, 2));
Assert.Equal(ConsoleColor.Green, second.ForegroundColor);
Assert.Empty(second.Output);
}
[Theory]
[InlineData(0)]
[InlineData(100001)]
public void InvalidInstructionBudgetsAreRejected(int budget)
{
using var image = new ManagedImage();
Assert.Throws<ArgumentOutOfRangeException>(() => new ManagedApplicationRunner().Execute(image.Bytes, new RecordingConsole(), budget));
}
[Fact]
public void OversizedAndBrokenPeImagesAreRejected()
{
var runner = new ManagedApplicationRunner();
Assert.Throws<BadImageFormatException>(() => runner.Execute(new byte[127], new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(new byte[4096], new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(new byte[ManagedRuntimeCapabilities.MaxImageBytes + 1], new RecordingConsole()));
}
[Fact]
public void WrongTargetFrameworkIsRejectedBeforeAnyConsoleEffects()
{
using var image = new ManagedImage();
var copy = image.Bytes.ToArray();
var original = Encoding.UTF8.GetBytes(ManagedRuntimeCapabilities.TargetFramework);
int at = copy.AsSpan().IndexOf(original);
Assert.True(at >= 0);
Encoding.UTF8.GetBytes(".NETCoreApp,Version=v11.0").CopyTo(copy, at);
var console = new RecordingConsole();
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(copy, console));
Assert.Empty(console.Output);
}
[Fact]
public void NativeEntryFlagsAndInvalidEntryTokensAreRejected()
{
using var image = new ManagedImage();
var native = image.Bytes.ToArray();
BinaryPrimitives.WriteInt32LittleEndian(native.AsSpan(image.CorHeaderOffset + 16), 0x10);
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(native, new RecordingConsole()));
var invalid = image.Bytes.ToArray();
BinaryPrimitives.WriteInt32LittleEndian(invalid.AsSpan(image.CorHeaderOffset + 20), 0x0A000001);
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(invalid, new RecordingConsole()));
}
[Fact]
public void UnknownOpcodeIsRejectedBeforeExecutingEarlierOutput()
{
using var image = new ManagedImage();
var copy = image.Bytes.ToArray();
copy[image.CodeOffset + image.CodeLength - 1] = 0xFF;
var console = new RecordingConsole();
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(copy, console));
Assert.Empty(console.Output);
}
[Fact]
public void TruncatedOperandIsRejected()
{
using var image = new ManagedImage();
var copy = image.WithCode();
copy[image.CodeOffset + image.CodeLength - 1] = 0x20;
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Theory]
[InlineData(1)]
[InlineData(127)]
public void BranchIntoOperandOrOutsideMethodIsRejected(byte offset)
{
using var image = new ManagedImage();
var copy = image.WithCode(0x2B, offset, 0x20, 0, 0, 0, 0, 0x26, 0x2A);
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Fact]
public void EvaluationStackAndLocalsAreValidated()
{
using var image = new ManagedImage();
var runner = new ManagedApplicationRunner();
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(0x26, 0x2A), new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(0x11, 0xFF, 0x26, 0x2A), new RecordingConsole()));
var overflow = new List<byte> { 0x16 };
overflow.AddRange(Enumerable.Repeat((byte)0x25, image.MaximumStack));
overflow.Add(0x2A);
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(overflow.ToArray()), new RecordingConsole()));
Assert.Throws<BadImageFormatException>(() => runner.Execute(image.WithCode(), new RecordingConsole()));
}
[Fact]
public void UserStringTokenMustUseTheUserStringHeap()
{
using var image = new ManagedImage();
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(
image.WithTokenInstruction(0x72, 0x0A000001, 0x26, 0x2A), new RecordingConsole()));
}
[Fact]
public void CallsCannotReachHostFilesLocalMethodsOrInstanceMembers()
{
using var image = new ManagedImage();
var code = new List<byte>();
Token(code, 0x72, image.UserString("NeverOpenThisFile"));
Token(code, 0x28, image.Member("System.IO.File", "ReadAllText"));
code.AddRange([0x26, 0x2A]);
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), new RecordingConsole()));
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(
image.WithTokenInstruction(0x28, MetadataTokens.GetToken(image.Metadata.MethodDefinitions.First()), 0x2A), new RecordingConsole()));
Assert.Throws<NotSupportedException>(() => new ManagedApplicationRunner().Execute(
image.WithTokenInstruction(0x28, image.Member("System.String", "ToUpperInvariant"), 0x2A), new RecordingConsole()));
}
[Fact]
public void ConsoleOutputBudgetStopsARealIlLoop()
{
using var image = new ManagedImage();
var code = new List<byte>();
Token(code, 0x72, image.UserString(string.Concat(Enumerable.Repeat("0123456789ABCDEF", 8))));
Token(code, 0x28, image.Member("System.Console", "Write"));
code.AddRange([0x2B, 0xF4]);
var terminal = new TerminalTextWriter(new TerminalBuffer(40, 5));
var error = Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), new TerminalManagedConsole(terminal)));
Assert.Contains("output limit", error.Message);
}
[Fact]
public void IntermediateConcatenationCannotGrowBeyondTheManagedStringBudget()
{
using var image = new ManagedImage();
var code = new List<byte>();
Token(code, 0x72, image.UserString(string.Concat(Enumerable.Repeat("0123456789ABCDEF", 8))));
for (int index = 0; index < 10; index++) { code.Add(0x25); Token(code, 0x28, image.Member("System.String", "Concat")); }
code.AddRange([0x26, 0x2A]);
Assert.Throws<InvalidOperationException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), new RecordingConsole()));
}
[Fact]
public void MalformedGetterReturnSignatureCannotInvokeAPlatformBinding()
{
using var image = new ManagedImage();
var getter = image.Member("System.Console", "get_ForegroundColor");
var copy = image.WithTokenInstruction(0x28, getter, 0x26, 0x2A);
var member = image.Metadata.GetMemberReference(MetadataTokens.MemberReferenceHandle(getter & 0xFFFFFF));
copy[image.SignaturePayloadOffset(member.Signature) + 2] = 0x01; // void, while retaining the enum type token as malformed trailing data
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Fact]
public void InteropStringSignatureDoesNotAcceptAnInt32StackArgument()
{
using var image = new ManagedImage();
var code = new List<byte> { 0x17 };
var stringWriteLine = image.Metadata.MemberReferences.First(handle =>
{
var member = image.Metadata.GetMemberReference(handle);
return image.Metadata.GetString(member.Name) == "WriteLine" &&
image.Metadata.GetBlobBytes(member.Signature).AsSpan().SequenceEqual(new byte[] { 0x00, 0x01, 0x01, 0x0E });
});
Token(code, 0x28, MetadataTokens.GetToken(stringWriteLine));
code.Add(0x2A);
var console = new RecordingConsole();
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), console));
Assert.Empty(console.Output);
}
[Fact]
public void MainSignatureRejectsTrailingMetadataBytes()
{
using var image = new ManagedImage();
var copy = image.WithCode(0x2A);
var payload = image.SignaturePayloadOffset(image.Entry.Signature);
Assert.Equal(3, copy[payload - 1]);
copy[payload - 1] = 4;
Assert.Throws<BadImageFormatException>(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole()));
}
[Fact]
public void EntryPointInt32ReturnBecomesTheExitCode()
{
using var image = new ManagedImage();
var copy = image.WithCode(0x1F, 42, 0x2A);
copy[image.SignaturePayloadOffset(image.Entry.Signature) + 2] = 0x08;
var result = new ManagedApplicationRunner().Execute(copy, new RecordingConsole());
Assert.Equal(42, result.ExitCode);
Assert.Equal(2, result.Instructions);
}
private static void Token(List<byte> code, byte opcode, int token)
{
code.Add(opcode);
byte[] bytes = new byte[4];
BinaryPrimitives.WriteInt32LittleEndian(bytes, token);
code.AddRange(bytes);
}
private static string Text(TerminalSnapshot snapshot) => string.Join("\n", Enumerable.Range(0, snapshot.Rows).Select(row =>
string.Concat(Enumerable.Range(0, snapshot.Columns).Select(column => snapshot.GetCell(column, row).Text)).TrimEnd()));
private sealed class RecordingConsole : IManagedConsole
{
public ConsoleColor ForegroundColor { get; set; } = ConsoleColor.Gray;
public ConsoleColor BackgroundColor { get; set; } = ConsoleColor.Black;
public string Output { get; private set; } = "";
public void ResetColor() { ForegroundColor = ConsoleColor.Gray; BackgroundColor = ConsoleColor.Black; }
public void Write(string value) => Output += value;
public void WriteLine(string value) => Output += value + "\n";
public void XfeWriteLine(string value) => Output += value + "\n";
}
}