XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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";
    }
}