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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text.Json;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Patch = XFESystem.Tools.JintCleanupPatch.Program;

namespace XFESystem.JintCleanupPatch.Tests;

public sealed class CleanupPatchTests
{
    [Theory]
    [InlineData(false, false, null)]
    [InlineData(true, false, "Original")]
    [InlineData(true, true, "Cleanup")]
    [InlineData(false, true, "Cleanup")]
    public void ActualPatchedIlRunsCleanupExactlyOnceAndPreservesExceptionReplacement(bool throwBody, bool throwCleanup, string? error)
    {
        var fixture = Fixture.Create(); fixture.MakeAssembly();
        Patch.Main([fixture.Path]);
        var context = new AssemblyLoadContext("CleanupFixture" + Guid.NewGuid(), true);
        try
        {
            var assembly = context.LoadFromAssemblyPath(fixture.Path); var type = assembly.GetType("Fixture.Scope")!; var method = type.GetMethod("Run")!;
            if (error is null) method.Invoke(null, [throwBody, throwCleanup]);
            else Assert.Equal(error, Assert.Throws<TargetInvocationException>(() => method.Invoke(null, [throwBody, throwCleanup])).InnerException!.Message);
            Assert.Equal(1, type.GetField("Cleanups")!.GetValue(null));
        }
        finally { context.Unload(); }
    }

    [Fact]
    public void RepeatedPatchIsVerifiedIdempotentAndChangedReceiptIsRejected()
    {
        var fixture = Fixture.Create(); fixture.MakeAssembly(); Patch.Main([fixture.Path]); var hash = Hash(fixture.Path);
        Patch.Main([fixture.Path]); Assert.Equal(hash, Hash(fixture.Path));
        using var receipt = JsonDocument.Parse(File.ReadAllText(fixture.Path + ".XfeCleanup.json"));
        Assert.False(receipt.RootElement.GetProperty("GlobalPackageCacheModified").GetBoolean());
        File.WriteAllText(fixture.Path + ".XfeCleanup.json", "{\"AfterSha256\":\"changed\"}");
        Assert.Throws<InvalidOperationException>(() => Patch.Main([fixture.Path])); Assert.Equal(hash, Hash(fixture.Path));
    }

    [Fact]
    public void FilesOutsidePrivateObjAreRejectedBeforeAnyMutation()
    {
        var fixture = Fixture.Create(); fixture.MakeAssembly(); var outside = System.IO.Path.Combine(fixture.Root, "Jint.dll"); File.Copy(fixture.Path, outside); var hash = Hash(outside);
        Assert.Throws<ArgumentException>(() => Patch.Main([outside])); Assert.Equal(hash, Hash(outside));
    }

    [Fact]
    public void UnsupportedCriticalCleanupIsRejectedWithOriginalFileIntact()
    {
        var fixture = Fixture.Create(); fixture.MakeAssembly(criticalUnsupported: true); var hash = Hash(fixture.Path);
        Assert.Throws<InvalidOperationException>(() => Patch.Main([fixture.Path])); Assert.Equal(hash, Hash(fixture.Path)); Assert.False(File.Exists(fixture.Path + ".XfeCleanup.json"));
    }

    [Fact]
    public void ReenteredTryAfterHandledExceptionResetsItsCleanupGuard()
    {
        var fixture = Fixture.Create(); fixture.MakeAssembly(loop: true); Patch.Main([fixture.Path]);
        var context = new AssemblyLoadContext("CleanupLoop" + Guid.NewGuid(), true);
        try { var type = context.LoadFromAssemblyPath(fixture.Path).GetType("Fixture.Scope")!; type.GetMethod("Run")!.Invoke(null, [false, false]); Assert.Equal(2, type.GetField("Cleanups")!.GetValue(null)); }
        finally { context.Unload(); }
    }

    private static string Hash(string path) => Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path)));
    private sealed class Fixture
    {
        private Fixture(string root, string path) { Root = root; Path = path; }
        public string Root { get; }
        public string Path { get; }
        public static Fixture Create()
        {
            var root = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "XfeJintCleanup", Guid.NewGuid().ToString("N"));
            var directory = System.IO.Path.Combine(root, "obj", "cosmos", "ref"); Directory.CreateDirectory(directory);
            File.WriteAllText(System.IO.Path.Combine(root, "XFESystem.Kernel.csproj"), "<Project />");
            return new(root, System.IO.Path.Combine(directory, "Jint.dll"));
        }
        public void MakeAssembly(bool criticalUnsupported = false, bool loop = false)
        {
            using var assembly = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition("Jint", new Version(4, 11, 0, 0)), "Jint", ModuleKind.Dll);
            var module = assembly.MainModule; var type = new TypeDefinition(criticalUnsupported ? "Jint" : "Fixture", criticalUnsupported ? "Engine" : "Scope", Mono.Cecil.TypeAttributes.Public | Mono.Cecil.TypeAttributes.Abstract | Mono.Cecil.TypeAttributes.Sealed, module.TypeSystem.Object); module.Types.Add(type);
            var counter = new FieldDefinition("Cleanups", Mono.Cecil.FieldAttributes.Public | Mono.Cecil.FieldAttributes.Static, module.TypeSystem.Int32); type.Fields.Add(counter);
            var method = new MethodDefinition("Run", Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static, module.TypeSystem.Void);
            method.Parameters.Add(new("throwBody", Mono.Cecil.ParameterAttributes.None, module.TypeSystem.Boolean)); method.Parameters.Add(new("throwCleanup", Mono.Cecil.ParameterAttributes.None, module.TypeSystem.Boolean)); type.Methods.Add(method);
            var il = method.Body.GetILProcessor(); var first = il.Create(OpCodes.Ldarg_0); var bodyNormal = il.Create(OpCodes.Nop); var handlerStart = il.Create(OpCodes.Ldsfld, counter); var handlerDone = il.Create(OpCodes.Endfinally); var done = il.Create(OpCodes.Ret);
            var exceptionConstructor = module.ImportReference(typeof(Exception).GetConstructor([typeof(string)])!);
            if (loop)
            {
                var iteration = new VariableDefinition(module.TypeSystem.Int32); method.Body.Variables.Add(iteration); method.Body.InitLocals = true;
                var loopStart = il.Create(OpCodes.Ldloc, iteration); var innerDone = il.Create(OpCodes.Leave, done); var outerCatch = il.Create(OpCodes.Pop); var afterCatch = il.Create(OpCodes.Ldloc, iteration);
                il.Append(loopStart); il.Append(il.Create(OpCodes.Brtrue, bodyNormal)); il.Append(il.Create(OpCodes.Ldstr, "Original")); il.Append(il.Create(OpCodes.Newobj, exceptionConstructor)); il.Append(il.Create(OpCodes.Throw));
                il.Append(bodyNormal); il.Append(il.Create(OpCodes.Leave, innerDone));
                il.Append(handlerStart); il.Append(il.Create(OpCodes.Ldc_I4_1)); il.Append(il.Create(OpCodes.Add)); il.Append(il.Create(OpCodes.Stsfld, counter)); il.Append(handlerDone);
                il.Append(innerDone); innerDone.Operand = afterCatch;
                il.Append(outerCatch); il.Append(il.Create(OpCodes.Leave, afterCatch)); il.Append(afterCatch); il.Append(il.Create(OpCodes.Ldc_I4_1)); il.Append(il.Create(OpCodes.Add)); il.Append(il.Create(OpCodes.Stloc, iteration));
                il.Append(il.Create(OpCodes.Ldloc, iteration)); il.Append(il.Create(OpCodes.Ldc_I4_2)); il.Append(il.Create(OpCodes.Blt, loopStart)); il.Append(done);
                method.Body.ExceptionHandlers.Add(new(ExceptionHandlerType.Finally) { TryStart = loopStart, TryEnd = handlerStart, HandlerStart = handlerStart, HandlerEnd = innerDone });
                method.Body.ExceptionHandlers.Add(new(ExceptionHandlerType.Catch) { TryStart = loopStart, TryEnd = outerCatch, HandlerStart = outerCatch, HandlerEnd = afterCatch, CatchType = module.ImportReference(typeof(Exception)) });
                assembly.Write(Path); return;
            }
            il.Append(first); il.Append(il.Create(OpCodes.Brfalse, bodyNormal)); il.Append(il.Create(OpCodes.Ldstr, "Original")); il.Append(il.Create(OpCodes.Newobj, exceptionConstructor)); il.Append(il.Create(OpCodes.Throw));
            il.Append(bodyNormal); il.Append(il.Create(OpCodes.Leave, done));
            il.Append(handlerStart); il.Append(il.Create(OpCodes.Ldc_I4_1)); il.Append(il.Create(OpCodes.Add)); il.Append(il.Create(OpCodes.Stsfld, counter));
            il.Append(il.Create(OpCodes.Ldarg_1)); il.Append(il.Create(OpCodes.Brfalse, criticalUnsupported ? done : handlerDone)); il.Append(il.Create(OpCodes.Ldstr, "Cleanup")); il.Append(il.Create(OpCodes.Newobj, exceptionConstructor)); il.Append(il.Create(OpCodes.Throw));
            il.Append(handlerDone); il.Append(done);
            method.Body.ExceptionHandlers.Add(new(ExceptionHandlerType.Finally) { TryStart = first, TryEnd = handlerStart, HandlerStart = handlerStart, HandlerEnd = done });
            assembly.Write(Path);
        }
    }
}