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());
}
}
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)));
}
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());
}
}
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)));
}