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(() => 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(() => 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(() => new ManagedApplicationRunner().Execute(image.Bytes, new RecordingConsole(), budget)); } [Fact] public void OversizedAndBrokenPeImagesAreRejected() { var runner = new ManagedApplicationRunner(); Assert.Throws(() => runner.Execute(new byte[127], new RecordingConsole())); Assert.Throws(() => runner.Execute(new byte[4096], new RecordingConsole())); Assert.Throws(() => 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(() => 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(() => new ManagedApplicationRunner().Execute(native, new RecordingConsole())); var invalid = image.Bytes.ToArray(); BinaryPrimitives.WriteInt32LittleEndian(invalid.AsSpan(image.CorHeaderOffset + 20), 0x0A000001); Assert.Throws(() => 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(() => 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(() => 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(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole())); } [Fact] public void EvaluationStackAndLocalsAreValidated() { using var image = new ManagedImage(); var runner = new ManagedApplicationRunner(); Assert.Throws(() => runner.Execute(image.WithCode(0x26, 0x2A), new RecordingConsole())); Assert.Throws(() => runner.Execute(image.WithCode(0x11, 0xFF, 0x26, 0x2A), new RecordingConsole())); var overflow = new List { 0x16 }; overflow.AddRange(Enumerable.Repeat((byte)0x25, image.MaximumStack)); overflow.Add(0x2A); Assert.Throws(() => runner.Execute(image.WithCode(overflow.ToArray()), new RecordingConsole())); Assert.Throws(() => runner.Execute(image.WithCode(), new RecordingConsole())); } [Fact] public void UserStringTokenMustUseTheUserStringHeap() { using var image = new ManagedImage(); Assert.Throws(() => new ManagedApplicationRunner().Execute( image.WithTokenInstruction(0x72, 0x0A000001, 0x26, 0x2A), new RecordingConsole())); } [Fact] public void CallsCannotReachHostFilesLocalMethodsOrInstanceMembers() { using var image = new ManagedImage(); var code = new List(); Token(code, 0x72, image.UserString("NeverOpenThisFile")); Token(code, 0x28, image.Member("System.IO.File", "ReadAllText")); code.AddRange([0x26, 0x2A]); Assert.Throws(() => new ManagedApplicationRunner().Execute(image.WithCode(code.ToArray()), new RecordingConsole())); Assert.Throws(() => new ManagedApplicationRunner().Execute( image.WithTokenInstruction(0x28, MetadataTokens.GetToken(image.Metadata.MethodDefinitions.First()), 0x2A), new RecordingConsole())); Assert.Throws(() => 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(); 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(() => 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(); 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(() => 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(() => new ManagedApplicationRunner().Execute(copy, new RecordingConsole())); } [Fact] public void InteropStringSignatureDoesNotAcceptAnInt32StackArgument() { using var image = new ManagedImage(); var code = new List { 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(() => 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(() => 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 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"; } }