using System.Security.Cryptography; using System.Text.Json; using Mono.Cecil; using Mono.Cecil.Cil; namespace XFESystem.Tools.JintCleanupPatch; public static partial class Program { private const string BoundMarker = "XFESystem.JintBoundCallableTargetsV1"; private const string OriginalJintSha256 = "acf8a1a9f98aa771c95c2b15b9b58f21de8b77f027e2a8d5ca92597e87752d9d"; private static void ValidateBoundInput(string path, bool hostOnly) { if (hostOnly) VerifyHostPrivateCopy(path); else VerifyPrivateCopy(path); using var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters { InMemory = true }); if (assembly.Name.Name != "Jint" || assembly.Name.Version != new Version(4, 11, 0, 0)) throw new InvalidOperationException("The callable target patch only supports pinned Jint 4.11.0."); var hash = Hash(path); if (assembly.MainModule.Types.Any(type => type.FullName == BoundMarker)) { var receiptPath = path + ".XfeBind.json"; if (!File.Exists(receiptPath)) throw new InvalidOperationException("A callable patch marker without its receipt is not trusted."); using var receipt = JsonDocument.Parse(File.ReadAllText(receiptPath)); if (receipt.RootElement.GetProperty("AfterSha256").GetString() != hash) throw new InvalidOperationException("The marked Jint assembly differs from its callable patch receipt."); if (receipt.RootElement.GetProperty("Version").GetInt32() != 1 || receipt.RootElement.GetProperty("Kind").GetString() != "ProjectLocalJintBoundCallableTargets" || receipt.RootElement.GetProperty("PinnedOriginalSha256").GetString() != OriginalJintSha256) throw new InvalidOperationException("The callable patch receipt does not identify the pinned original and patch version."); if (assembly.MainModule.Types.Any(type => type.FullName == WeakMarker) && (!receipt.RootElement.TryGetProperty("WeakCollectionsPatchVersion", out var weakVersion) || weakVersion.GetInt32() != 1)) throw new InvalidOperationException("The owned weak collections marker has no matching verification receipt."); if(assembly.MainModule.Types.Any(type=>type.FullName==WeakMarker)) ValidateWeakCollections(assembly.MainModule); if (assembly.MainModule.Types.Any(type => type.FullName.StartsWith("XFESystem.JintOwnedEngineWeakCollections", StringComparison.Ordinal))) { if (!assembly.MainModule.Types.Any(type => type.FullName == EngineWeakMarker) || !receipt.RootElement.TryGetProperty("EngineWeakCollectionsPatchVersion", out var engineVersion) || engineVersion.GetInt32() != 1) throw new InvalidOperationException("The Engine weak cache marker has no matching verification receipt."); ValidateEngineWeakCollections(assembly.MainModule); } else if (receipt.RootElement.TryGetProperty("EngineWeakCollectionsPatchVersion", out _) || assembly.MainModule.GetType("Jint.Engine").Fields.Any(field => field.Name is "_emptyShapes" or "_privateElementStore" && field.FieldType is GenericInstanceType generic && generic.ElementType.FullName != OriginalWeakTable)) throw new InvalidOperationException("A partial Engine weak cache patch is not trusted."); return; } if (hash != OriginalJintSha256) { if (hostOnly || !assembly.MainModule.Types.Any(type => type.FullName == Marker)) throw new InvalidOperationException("An unmarked callable patch input must match the pinned original Jint SHA256."); using var cleanup = JsonDocument.Parse(File.ReadAllText(path + ".XfeCleanup.json")); if (cleanup.RootElement.GetProperty("BeforeSha256").GetString() != OriginalJintSha256 || cleanup.RootElement.GetProperty("AfterSha256").GetString() != hash) throw new InvalidOperationException("The native cleanup input has no valid pinned-original receipt chain."); } _ = BoundShapes(assembly.MainModule); } private static int PatchBoundTargets(string path, bool hostOnly) { ValidateBoundInput(path, hostOnly); using var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters { InMemory = true }); bool alreadyBound = assembly.MainModule.Types.Any(type => type.FullName == BoundMarker); if (alreadyBound && assembly.MainModule.Types.Any(type => type.FullName == EngineWeakMarker)) { Console.WriteLine("XFE_JINT_BOUND_TARGETS_ALREADY_READY|" + Hash(path)); return 0; } var before = Hash(path); var (call, instanceOf) = alreadyBound ? (null, null) : BoundShapes(assembly.MainModule); var callable = assembly.MainModule.GetType("Jint.Native.ICallable") ?? throw new InvalidOperationException("Pinned ICallable type is missing."); var callableCall = callable.Methods.Single(method => method.Name == "Call" && method.Parameters.Count == 2); var objectInstance = assembly.MainModule.GetType("Jint.Native.Object.ObjectInstance")!; var jsValue = assembly.MainModule.GetType("Jint.Native.JsValue")!; var hasInstance = jsValue.Methods.Single(method => method.Name == "OrdinaryHasInstance" && method.Parameters.Count == 1); if (!alreadyBound) { RewriteTarget(call!, callable, callableCall); RewriteTarget(instanceOf!, objectInstance, hasInstance); assembly.MainModule.Types.Add(new TypeDefinition("XFESystem", "JintBoundCallableTargetsV1", TypeAttributes.NotPublic | TypeAttributes.Abstract | TypeAttributes.Sealed, assembly.MainModule.TypeSystem.Object)); } if (!assembly.MainModule.Types.Any(type => type.FullName == WeakMarker)) PatchWeakCollections(assembly.MainModule); PatchEngineWeakCollections(assembly.MainModule); var temporary = path + ".XfeBind.tmp"; assembly.Write(temporary); File.Move(temporary, path, true); var receipt = new { Kind = "ProjectLocalJintBoundCallableTargets", Version = 1, JintVersion = "4.11.0", Path = path, BeforeSha256 = before, AfterSha256 = Hash(path), PinnedOriginalSha256 = OriginalJintSha256, GlobalPackageCacheModified = false, HostOnly = hostOnly, Source = "https://github.com/sebastienros/jint/blob/v4.11.0/Jint/Native/Function/BindFunction.cs", Methods = new[] { "BindFunction.ICallable.Call", "BindFunction.OrdinaryHasInstance" }, WeakCollectionsPatchVersion = 1, EngineWeakCollectionsPatchVersion = 1, WeakCollectionStorage = "JsWeakMap/JsWeakSet retain their intrinsic algorithms; private tables use per-engine explicit weak/dependent handles.", Change = "Call dispatches through ICallable; instanceof dispatches through ObjectInstance/JsValue. Bound arguments, bound this, constructors and original exceptions remain unchanged." }; File.WriteAllText(path + ".XfeBind.json", JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true })); Console.WriteLine("XFE_JINT_BOUND_TARGETS_READY|" + receipt.AfterSha256); return 0; } private static (MethodDefinition Call, MethodDefinition InstanceOf) BoundShapes(ModuleDefinition module) { var type = module.GetType("Jint.Native.Function.BindFunction") ?? throw new InvalidOperationException("Pinned BindFunction type is missing."); var call = type.Methods.Single(method => method.Name.EndsWith("ICallable.Call", StringComparison.Ordinal)); var instanceOf = type.Methods.Single(method => method.Name == "OrdinaryHasInstance"); foreach (var method in new[] { call, instanceOf }) { if (!method.HasBody || method.Body.ExceptionHandlers.Count != 0 || method.Body.Instructions.Count(instruction => instruction.OpCode == OpCodes.Isinst && instruction.Operand is TypeReference reference && reference.FullName == "Jint.Native.Function.Function") != 1 || method.Body.Variables.Any(variable => variable.VariableType.FullName == "Jint.Native.Function.Function") || method.Body.Instructions.Count(instruction => instruction.OpCode == OpCodes.Callvirt && instruction.Operand is MethodReference reference && reference.Name == (method == call ? "Call" : "OrdinaryHasInstance")) != 1 || !method.Body.Instructions.Any(instruction => instruction.OpCode == OpCodes.Ldstr && instruction.Operand is string text && text == (method == call ? "Bind must be called on a function" : "Right-hand side of 'instanceof' is not callable"))) throw new InvalidOperationException("Pinned BindFunction method shape changed: " + method.FullName + "\n" + string.Join("\n", method.Body.Instructions.Select(instruction => instruction.ToString())) + "\nLocals: " + string.Join(",", method.Body.Variables.Select(variable => variable.VariableType.FullName))); } return (call, instanceOf); } private static void RewriteTarget(MethodDefinition method, TypeReference targetType, MethodReference targetMethod) { foreach (var variable in method.Body.Variables.Where(variable => variable.VariableType.FullName == "Jint.Native.Function.Function")) variable.VariableType = targetType; foreach (var instruction in method.Body.Instructions) { if (instruction.OpCode == OpCodes.Isinst && instruction.Operand is TypeReference type && type.FullName == "Jint.Native.Function.Function") instruction.Operand = targetType; if (instruction.OpCode == OpCodes.Callvirt && instruction.Operand is MethodReference target && target.Name == targetMethod.Name) instruction.Operand = targetMethod; } } private static void VerifyHostPrivateCopy(string path) { string? taskRoot = Directory.GetCurrentDirectory(); while (taskRoot is not null && !File.Exists(Path.Combine(taskRoot, "XFESystem.slnx"))) taskRoot = Path.GetDirectoryName(taskRoot); if (taskRoot is null) throw new ArgumentException("Run the host patch from the XFESystem workspace."); var relative = Path.GetRelativePath(taskRoot, path).Replace('\\', '/'); if (Path.GetFileName(path) != "Jint.dll" || relative.StartsWith("../", StringComparison.Ordinal) || !relative.Contains("/obj/", StringComparison.OrdinalIgnoreCase) || !relative.Contains("/XfeJintHost/", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException("Only a workspace project's private obj/XfeJintHost Jint.dll copy may be host-patched."); for (string? candidate = path; candidate is not null && !candidate.Equals(taskRoot, StringComparison.OrdinalIgnoreCase); candidate = Path.GetDirectoryName(candidate)) if ((File.GetAttributes(candidate) & FileAttributes.ReparsePoint) != 0) throw new ArgumentException("A private host Jint copy cannot use a symbolic link or junction."); } }