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

XFESystem

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

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

namespace XFESystem.JintCleanupPatch.Tests;

public sealed class BoundTargetPatchTests
{
    private const string OriginalHash = "acf8a1a9f98aa771c95c2b15b9b58f21de8b77f027e2a8d5ca92597e87752d9d";

    [Fact]
    public void ActualHostRuntimeCarriesTheSameVerifiedCallableFix()
    {
        var loaded = typeof(Engine).Assembly;
        Assert.NotNull(loaded.GetType("XFESystem.JintBoundCallableTargetsV1"));
        using var receipt = JsonDocument.Parse(File.ReadAllText(loaded.Location + ".XfeBind.json"));
        Assert.Equal(Hash(loaded.Location), receipt.RootElement.GetProperty("AfterSha256").GetString());
        Assert.Equal(OriginalHash, receipt.RootElement.GetProperty("PinnedOriginalSha256").GetString());
        Assert.False(receipt.RootElement.GetProperty("GlobalPackageCacheModified").GetBoolean());
    }

    [Theory]
    [InlineData("function sum(a,b,c){return this.base+a+b+c;} const first=sum.bind({base:10},1),second=first.bind({base:900},2);String(second(3))", "16")]
    [InlineData("class C {constructor(){this.value=42;this.method=this.method.bind(this);this.second=this.method.bind(this);} method(){return this.value;}} String(new C().second())", "42")]
    [InlineData("const proxy=new Proxy(function(a){return this.base+a;},{apply(target,receiver,args){return Reflect.apply(target,receiver,args)+1}}); String(proxy.bind({base:40},1)())", "42")]
    [InlineData("function C(a,b){this.value=a+b;} const A=C.bind(null,20),B=A.bind(null,22),object=new B();[object.value,object instanceof C,object instanceof A,object instanceof B].join(',')", "42,true,true,true")]
    [InlineData("let result='';try{Function.prototype.bind.call({},null);}catch(error){result=error.name;} result", "TypeError")]
    [InlineData("const a=(function(a,b,c){}).bind(null,1),b=a.bind(null,2);[a.length,b.length,a.name,b.name].join(',')", "2,1,bound ,bound bound ")]
    public void BoundCallsPreserveArgumentsReceiverConstructorsProxyAndErrors(string script, string expected)
    {
        var engine = new Engine(options => { options.Interop.Enabled = false; options.DisableStringCompilation(); options.MaxStatements(100_000); });
        Assert.Equal(expected, engine.Evaluate(script).AsString());
    }

    [Fact]
    public void ExactPinnedPrivateInputIsPatchedIdempotentlyWithoutTouchingGlobalPackage()
    {
        var source = Original(); var before = Hash(source); var path = PrivatePath(); File.Copy(source, path);
        Patch.Main([path, "--bind-only"]); var hash = Hash(path);
        Patch.Main([path, "--bind-only"]); Assert.Equal(hash, Hash(path)); Assert.Equal(before, Hash(source));
        using var assembly = AssemblyDefinition.ReadAssembly(path);
        var type = assembly.MainModule.GetType("Jint.Native.Function.BindFunction");
        var call = type.Methods.Single(method => method.Name.EndsWith("ICallable.Call", StringComparison.Ordinal));
        Assert.Contains(call.Body.Instructions, instruction => instruction.OpCode == OpCodes.Isinst && instruction.Operand is TypeReference reference && reference.FullName == "Jint.Native.ICallable");
        Assert.Contains(call.Body.Instructions, instruction => instruction.OpCode == OpCodes.Callvirt && instruction.Operand is MethodReference reference && reference.DeclaringType.FullName == "Jint.Native.ICallable");
        Assert.DoesNotContain(call.Body.Instructions, instruction => instruction.OpCode == OpCodes.Isinst && instruction.Operand is TypeReference reference && reference.FullName == "Jint.Native.Function.Function");
    }

    [Fact]
    public void ChangedUnmarkedInputAndMissingOrChangedReceiptAreRejectedBeforeMutation()
    {
        var path = PrivatePath(); File.Copy(Original(), path);
        var bytes = File.ReadAllBytes(path); bytes[^1] ^= 1; File.WriteAllBytes(path, bytes); var changed = Hash(path);
        Assert.Throws<InvalidOperationException>(() => Patch.Main([path, "--bind-only"])); Assert.Equal(changed, Hash(path));
        File.Copy(Original(), path, true); Patch.Main([path, "--bind-only"]); var ready = Hash(path);
        File.WriteAllText(path + ".XfeBind.json", "{\"AfterSha256\":\"changed\"}");
        Assert.Throws<InvalidOperationException>(() => Patch.Main([path, "--bind-only"])); Assert.Equal(ready, Hash(path));
        File.Delete(path + ".XfeBind.json");
        Assert.Throws<InvalidOperationException>(() => Patch.Main([path, "--bind-only"])); Assert.Equal(ready, Hash(path));
    }

    [Fact]
    public void GlobalPackageAndNonPrivateWorkspaceFilesAreRejected()
    {
        var source = Original(); var before = Hash(source);
        Assert.Throws<ArgumentException>(() => Patch.Main([source, "--bind-only"])); Assert.Equal(before, Hash(source));
        var outside = Path.Combine(Path.GetDirectoryName(PrivatePath())!, "Outside.dll"); File.Copy(source, outside);
        Assert.Throws<ArgumentException>(() => Patch.Main([outside, "--bind-only"])); Assert.Equal(before, Hash(outside));
    }

    [Fact]
    public void NativeCombinedModeKeepsCleanupAndCallableReceiptsOnTheActualFinalHash()
    {
        var path = PrivatePath(native: true); File.Copy(Original(), path);
        Patch.Main([path, "--bind-and-cleanup"]); var hash = Hash(path);
        Patch.Main([path, "--bind-and-cleanup"]); Assert.Equal(hash, Hash(path));
        foreach (var suffix in new[] { ".XfeCleanup.json", ".XfeBind.json" })
        {
            using var receipt = JsonDocument.Parse(File.ReadAllText(path + suffix));
            Assert.Equal(hash, receipt.RootElement.GetProperty("AfterSha256").GetString());
        }
    }

    [Theory]
    [InlineData("LiteralRegexReplacement", "JintLiteralRegexReplacementV1", false)]
    [InlineData("RegexScheduling", "JintRegexSchedulingV1", false)]
    [InlineData("LiteralRegexReplacement", "JintLiteralRegexReplacementV1", true)]
    [InlineData("RegexScheduling", "JintRegexSchedulingV1", true)]
    public void RegexReceiptAndMarkerMustBothMatchBeforeAnyMutation(string receiptPrefix, string marker, bool removeMarker)
    {
        var path = PrivatePath(); File.Copy(Original(), path); Patch.Main([path, "--bind-only"]);
        var receipt = System.Text.Json.Nodes.JsonNode.Parse(File.ReadAllText(path + ".XfeBind.json"))!.AsObject();
        if (removeMarker)
        {
            using var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters { InMemory = true });
            assembly.MainModule.Types.Remove(assembly.MainModule.GetType("XFESystem." + marker));
            var temporary = path + ".test.tmp"; assembly.Write(temporary); File.Move(temporary, path, true);
            receipt["AfterSha256"] = Hash(path);
        }
        else receipt.Remove(receiptPrefix + "PatchVersion");
        File.WriteAllText(path + ".XfeBind.json", receipt.ToJsonString()); var before = Hash(path);
        Assert.Throws<InvalidOperationException>(() => Patch.Main([path, "--bind-only"]));
        Assert.Equal(before, Hash(path));
    }

    private static string Original()
    {
        var packages = Environment.GetEnvironmentVariable("NUGET_PACKAGES") ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
        var path = Path.Combine(packages, "jint", "4.11.0", "lib", "net10.0", "Jint.dll");
        Assert.Equal(OriginalHash, Hash(path)); return path;
    }
    private static string PrivatePath(bool native = false)
    {
        string? root = AppContext.BaseDirectory;
        while (root is not null && !File.Exists(Path.Combine(root, "XFESystem.slnx"))) root = Path.GetDirectoryName(root);
        Assert.NotNull(root);
        var fixture = Path.Combine(root, "build", "CSharp", "JintBoundFixture-" + Guid.NewGuid().ToString("N"));
        var directory = native ? Path.Combine(fixture, "obj", "cosmos", "ref") : Path.Combine(fixture, "obj", "XfeJintHost");
        Directory.CreateDirectory(directory);
        if (native) File.WriteAllText(Path.Combine(fixture, "XFESystem.Kernel.csproj"), "<Project />");
        return Path.Combine(directory, "Jint.dll");
    }
    private static string Hash(string path) => Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path)));
}