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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace XFESystem.Tools.JintCleanupPatch;

public static partial class Program
{
    private const string RegexSearchMarker = "XFESystem.JintFailedRegexSuffixV1";
    private const string UncachedRegexSearch = "XfeExecuteWithTimeoutUncached";
    private static readonly string[] RegexSearchFields = ["_xfeNoMatchInput", "_xfeNoMatchEngine", "_xfeNoMatchStart", "_xfeNoMatchHits"];

    // An unsuccessful non-sticky search proves that the same immutable input
    // has no match at any later start. Keep that fact on the JsRegExp instance,
    // never on a shared AST or in a process-wide table. No JS results/captures
    // are cached. Engine identity invalidates RegExp.prototype.compile changes.
    private static void PatchRegexSearch(ModuleDefinition module)
    {
        if (module.GetType(RegexSearchMarker) is not null) { ValidateRegexSearch(module); return; }
        var regex = module.GetType("Jint.Native.JsRegExp");
        var prototype = module.GetType("Jint.Native.RegExp.RegExpPrototype");
        var original = prototype.Methods.Single(method => method.Name == "ExecuteWithTimeout");
        if (!original.IsStatic || !original.IsPrivate || original.Parameters.Count != 4 ||
            original.Parameters[0].ParameterType != regex || original.Parameters[2].ParameterType.MetadataType != MetadataType.String ||
            original.Parameters[3].ParameterType.MetadataType != MetadataType.Int32 ||
            original.ReturnType.FullName != "Jint.Runtime.RegExp.RegExpMatchResult" ||
            original.Body.Instructions.Count(instruction => instruction.Operand is MethodReference call &&
                call.Name == "Execute" && call.DeclaringType.FullName == "Jint.Runtime.RegExp.JintRegExpEngine") != 2 ||
            RegexSearchFields.Any(name => regex.Fields.Any(field => field.Name == name)))
            throw new InvalidOperationException("Pinned custom regex timeout/search shape changed.");
        var callers = Types(module.Types).SelectMany(type => type.Methods).Where(method => method.HasBody)
            .SelectMany(method => method.Body.Instructions).Where(instruction => instruction.Operand is MethodReference call && call.FullName == original.FullName).ToArray();
        if (callers.Length != 5) throw new InvalidOperationException("Pinned custom regex search call sites changed.");
        var fields = new[]
        {
            new FieldDefinition(RegexSearchFields[0], FieldAttributes.Assembly, module.TypeSystem.String),
            new FieldDefinition(RegexSearchFields[1], FieldAttributes.Assembly, original.Parameters[1].ParameterType),
            new FieldDefinition(RegexSearchFields[2], FieldAttributes.Assembly, module.TypeSystem.Int32),
            new FieldDefinition(RegexSearchFields[3], FieldAttributes.Assembly, module.TypeSystem.Int64)
        };
        foreach (var field in fields) regex.Fields.Add(field);
        var wrapper = new MethodDefinition(original.Name, original.Attributes, original.ReturnType);
        foreach (var parameter in original.Parameters) wrapper.Parameters.Add(new(parameter.Name, parameter.Attributes, parameter.ParameterType));
        original.Name = UncachedRegexSearch;
        prototype.Methods.Add(wrapper);
        foreach (var caller in callers) caller.Operand = wrapper;
        wrapper.Body.InitLocals = true;
        var result = new VariableDefinition(wrapper.ReturnType); wrapper.Body.Variables.Add(result);
        var match = module.GetType(wrapper.ReturnType.FullName);
        var success = match.Methods.Single(method => method.Name == "get_Success");
        var noMatch = match.Methods.Single(method => method.Name == "get_NoMatch");
        var sticky = regex.Methods.Single(method => method.Name == "get_Sticky");
        var length = module.ImportReference(typeof(string).GetProperty(nameof(string.Length))!.GetMethod!);
        var uncached = Instruction.Create(OpCodes.Ldarg_0);
        var done = Instruction.Create(OpCodes.Ldloc, result);
        var il = wrapper.Body.GetILProcessor();
        // Sticky failures say nothing about later positions. Bound retained input
        // to 128 KiB, matching the editor's code limit; larger inputs use Jint.
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Callvirt, sticky); il.Emit(OpCodes.Brtrue, uncached);
        il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Callvirt, length); il.Emit(OpCodes.Ldc_I4, 131072); il.Emit(OpCodes.Bgt, uncached);
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fields[0]); il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Brfalse, uncached);
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fields[1]); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Brfalse, uncached);
        il.Emit(OpCodes.Ldarg_3); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fields[2]); il.Emit(OpCodes.Blt, uncached);
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fields[3]);
        il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Conv_I8); il.Emit(OpCodes.Add); il.Emit(OpCodes.Stfld, fields[3]);
        il.Emit(OpCodes.Call, noMatch); il.Emit(OpCodes.Ret);
        il.Append(uncached); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Ldarg_3);
        il.Emit(OpCodes.Call, original); il.Emit(OpCodes.Stloc, result);
        il.Emit(OpCodes.Ldloca, result); il.Emit(OpCodes.Call, success); il.Emit(OpCodes.Brtrue, done);
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Callvirt, sticky); il.Emit(OpCodes.Brtrue, done);
        il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Callvirt, length); il.Emit(OpCodes.Ldc_I4, 131072); il.Emit(OpCodes.Bgt, done);
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Stfld, fields[0]);
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, fields[1]);
        il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_3); il.Emit(OpCodes.Stfld, fields[2]);
        il.Append(done); il.Emit(OpCodes.Ret);
        module.Types.Add(new("XFESystem", "JintFailedRegexSuffixV1", TypeAttributes.NotPublic | TypeAttributes.Abstract | TypeAttributes.Sealed, module.TypeSystem.Object));
        ValidateRegexSearch(module);
    }

    private static void ValidateRegexSearch(ModuleDefinition module)
    {
        var regex = module.GetType("Jint.Native.JsRegExp");
        var prototype = module.GetType("Jint.Native.RegExp.RegExpPrototype");
        var wrapper = prototype.Methods.SingleOrDefault(method => method.Name == "ExecuteWithTimeout");
        var original = prototype.Methods.SingleOrDefault(method => method.Name == UncachedRegexSearch);
        if (module.GetType(RegexSearchMarker) is null || original is null || wrapper is null ||
            wrapper.Body.ExceptionHandlers.Count != 0 || wrapper.Body.Instructions.Count != 57 ||
            RegexSearchFields.Any(name => regex.Fields.Count(field => field.Name == name && field.IsAssembly) != 1) ||
            wrapper.Body.Instructions.Count(instruction => instruction.Operand is MethodReference call && call.FullName == original.FullName) != 1 ||
            wrapper.Body.Instructions.Count(instruction => instruction.OpCode == OpCodes.Ldc_I4 && instruction.Operand is int value && value == 131072) != 2)
            throw new InvalidOperationException("The failed-regex-suffix patch is incomplete.");
    }
}