using System.Security.Cryptography;
using System.Text.Json.Nodes;
using Jint;
using Mono.Cecil;
using Mono.Cecil.Cil;
using XFESystem.Collections;
using Patch=XFESystem.Tools.JintCleanupPatch.Program;
namespace XFESystem.JintCleanupPatch.Tests;
public sealed class EngineWeakCachePatchTests
{
[Fact]
public void OriginalPrivateCachesUseTheSameDirectEngineOwnerAndAnExplicitDictionaryFactory()
{
using var assembly=AssemblyDefinition.ReadAssembly(typeof(Engine).Assembly.Location);
var module=assembly.MainModule;Assert.NotNull(module.GetType("XFESystem.JintOwnedEngineWeakCollectionsV1"));
var engine=module.GetType("Jint.Engine");
foreach(string name in new[]{"_emptyShapes","_privateElementStore"})
Assert.Equal(typeof(OwnedWeakTable<,>).FullName,((GenericInstanceType)engine.Fields.Single(field=>field.Name==name).FieldType).ElementType.FullName);
var factory=engine.Methods.Single(method=>method.Name=="XfeCreatePrivateElementDictionary");
Assert.Equal(2,factory.Body.Instructions.Count);Assert.Equal(OpCodes.Newobj,factory.Body.Instructions[0].OpCode);
Assert.Equal(OpCodes.Ret,factory.Body.Instructions[1].OpCode);
Assert.DoesNotContain(engine.Methods.Where(method=>method.HasBody).SelectMany(method=>method.Body.Instructions),instruction=>
instruction.Operand is MethodReference target&&target.DeclaringType.FullName=="System.Activator");
// The unrelated interop cache is intentionally outside this verified patch.
Assert.StartsWith("System.Runtime.CompilerServices.ConditionalWeakTable`2",engine.Fields.Single(field=>field.Name=="_objectWrapperCache").FieldType.FullName);
}
[Fact]
public void ThrowingPublicConfigureCallbackReleasesAlreadyAllocatedScopeAndPreservesExceptionIdentity()
{
WeakCollectionScope? scope=null;var expected=new InvalidOperationException("known constructor failure");
var actual=Assert.Throws<InvalidOperationException>(()=>new Engine((engine,options)=>
{
scope=((IWeakCollectionOwner)(object)engine).WeakCollections;
var table=new OwnedWeakTable<object,object>(scope);table.Add(new object(),new object());
Assert.Equal(1,scope.Lifetime.Entries);throw expected;
}));
Assert.Same(expected,actual);Assert.NotNull(scope);
Assert.Equal(0,scope.Lifetime.Tables);Assert.Equal(0,scope.Lifetime.Entries);
Assert.Equal(scope.Lifetime.Created,scope.Lifetime.Disposed);
Assert.Equal(scope.Lifetime.TableHandlesCreated,scope.Lifetime.TableHandlesDisposed);
Assert.Throws<ObjectDisposedException>(()=>new OwnedWeakTable<object,object>(scope));
}
[Theory]
[InlineData("MissingHandler")]
[InlineData("WrongFactory")]
[InlineData("OldField")]
[InlineData("MissingVersion")]
[InlineData("UnsupportedVersion")]
[InlineData("WrongOwner")]
public void PartialEngineCachePatchCannotBeTrustedByMerelyUpdatingItsHashReceipt(string damage)
{
string path=PrivatePath();File.Copy(typeof(Engine).Assembly.Location,path);
var receipt=JsonNode.Parse(File.ReadAllText(typeof(Engine).Assembly.Location+".XfeBind.json"))!;
using(var assembly=AssemblyDefinition.ReadAssembly(path,new ReaderParameters{InMemory=true}))
{
var engine=assembly.MainModule.GetType("Jint.Engine");
if(damage=="MissingHandler")engine.Methods.Single(method=>method.IsConstructor&&method.Parameters.Count==2).Body.ExceptionHandlers.Clear();
if(damage=="WrongFactory")engine.Methods.Single(method=>method.Name=="XfeCreatePrivateElementDictionary").Body.Instructions[0].OpCode=OpCodes.Ldnull;
if(damage=="WrongOwner")
{
var creation=engine.Methods.Single(method=>method.Name=="GetEmptyShape").Body.Instructions.Single(instruction=>instruction.OpCode==OpCodes.Newobj&&
instruction.Operand is MethodReference target&&target.DeclaringType.FullName.StartsWith("XFESystem.Collections.OwnedWeakTable`2",StringComparison.Ordinal));
creation.Previous.Previous.OpCode=OpCodes.Ldnull;
}
if(damage=="OldField")
{
var field=engine.Fields.Single(field=>field.Name=="_emptyShapes");var old=(GenericInstanceType)field.FieldType;
var restored=new GenericInstanceType(assembly.MainModule.ImportReference(typeof(System.Runtime.CompilerServices.ConditionalWeakTable<,>)));
foreach(var argument in old.GenericArguments)restored.GenericArguments.Add(argument);field.FieldType=restored;
}
assembly.Write(path);
}
if(damage=="MissingVersion")receipt.AsObject().Remove("EngineWeakCollectionsPatchVersion");
if(damage=="UnsupportedVersion")receipt["EngineWeakCollectionsPatchVersion"]=99;
string hash=Hash(path);receipt["AfterSha256"]=hash;File.WriteAllText(path+".XfeBind.json",receipt.ToJsonString());
Assert.Throws<InvalidOperationException>(()=>Patch.Main([path,"--bind-only"]));Assert.Equal(hash,Hash(path));
}
[Fact]
public void PreviouslyVerifiedMapOnlyPatchUpgradesWithoutDuplicatingOwnersOrChangingGlobalPackages()
{
string packages=Environment.GetEnvironmentVariable("NUGET_PACKAGES")??Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),".nuget","packages");
string original=Path.Combine(packages,"jint","4.11.0","lib","net10.0","Jint.dll");
string originalHash=Hash(original),path=PrivatePath();
using(var assembly=AssemblyDefinition.ReadAssembly(original,new ReaderParameters{InMemory=true}))
{
var module=assembly.MainModule;
// Reproduce the previous verified storage stage on the pinned binary.
typeof(Patch).GetMethod("PatchWeakCollections",System.Reflection.BindingFlags.Static|System.Reflection.BindingFlags.NonPublic)!.Invoke(null,[module]);
var type=module.GetType("Jint.Native.Function.BindFunction");
foreach(var method in type.Methods.Where(method=>method.Name.EndsWith("ICallable.Call",StringComparison.Ordinal)||method.Name=="OrdinaryHasInstance"))
{
bool call=method.Name.EndsWith("ICallable.Call",StringComparison.Ordinal);
var target=module.GetType(call?"Jint.Native.ICallable":"Jint.Native.JsValue").Methods.Single(item=>item.Name==(call?"Call":"OrdinaryHasInstance")&&item.Parameters.Count==(call?2:1));
foreach(var instruction in method.Body.Instructions)
{
if(instruction.OpCode==OpCodes.Isinst&&instruction.Operand is TypeReference reference&&reference.FullName=="Jint.Native.Function.Function")
instruction.Operand=module.GetType(call?"Jint.Native.ICallable":"Jint.Native.Object.ObjectInstance");
if(instruction.OpCode==OpCodes.Callvirt&&instruction.Operand is MethodReference referenceMethod&&referenceMethod.Name==target.Name)instruction.Operand=target;
}
}
module.Types.Add(new("XFESystem","JintBoundCallableTargetsV1",TypeAttributes.Abstract|TypeAttributes.Sealed|TypeAttributes.NotPublic,module.TypeSystem.Object));
assembly.Write(path);
}
File.WriteAllText(path+".XfeBind.json",new JsonObject{["Kind"]="ProjectLocalJintBoundCallableTargets",["Version"]=1,
["PinnedOriginalSha256"]=originalHash,["AfterSha256"]=Hash(path),["WeakCollectionsPatchVersion"]=1}.ToJsonString());
Patch.Main([path,"--bind-only"]);string upgraded=Hash(path);Patch.Main([path,"--bind-only"]);Assert.Equal(upgraded,Hash(path));
using var current=AssemblyDefinition.ReadAssembly(path);Assert.NotNull(current.MainModule.GetType("XFESystem.JintOwnedEngineWeakCollectionsV1"));
Assert.Single(current.MainModule.GetType("Jint.Engine").Fields,field=>field.Name=="_xfeWeakCollections");
Assert.Equal(originalHash,Hash(original));
}
private static string PrivatePath()
{
string? root=AppContext.BaseDirectory;while(root is not null&&!File.Exists(Path.Combine(root,"XFESystem.slnx")))root=Path.GetDirectoryName(root);
Assert.NotNull(root);string directory=Path.Combine(root,"build","EngineWeakFixture-"+Guid.NewGuid().ToString("N"),"obj","XfeJintHost");
Directory.CreateDirectory(directory);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.Nodes;
using Jint;
using Mono.Cecil;
using Mono.Cecil.Cil;
using XFESystem.Collections;
using Patch=XFESystem.Tools.JintCleanupPatch.Program;
namespace XFESystem.JintCleanupPatch.Tests;
public sealed class EngineWeakCachePatchTests
{
[Fact]
public void OriginalPrivateCachesUseTheSameDirectEngineOwnerAndAnExplicitDictionaryFactory()
{
using var assembly=AssemblyDefinition.ReadAssembly(typeof(Engine).Assembly.Location);
var module=assembly.MainModule;Assert.NotNull(module.GetType("XFESystem.JintOwnedEngineWeakCollectionsV1"));
var engine=module.GetType("Jint.Engine");
foreach(string name in new[]{"_emptyShapes","_privateElementStore"})
Assert.Equal(typeof(OwnedWeakTable<,>).FullName,((GenericInstanceType)engine.Fields.Single(field=>field.Name==name).FieldType).ElementType.FullName);
var factory=engine.Methods.Single(method=>method.Name=="XfeCreatePrivateElementDictionary");
Assert.Equal(2,factory.Body.Instructions.Count);Assert.Equal(OpCodes.Newobj,factory.Body.Instructions[0].OpCode);
Assert.Equal(OpCodes.Ret,factory.Body.Instructions[1].OpCode);
Assert.DoesNotContain(engine.Methods.Where(method=>method.HasBody).SelectMany(method=>method.Body.Instructions),instruction=>
instruction.Operand is MethodReference target&&target.DeclaringType.FullName=="System.Activator");
// The unrelated interop cache is intentionally outside this verified patch.
Assert.StartsWith("System.Runtime.CompilerServices.ConditionalWeakTable`2",engine.Fields.Single(field=>field.Name=="_objectWrapperCache").FieldType.FullName);
}
[Fact]
public void ThrowingPublicConfigureCallbackReleasesAlreadyAllocatedScopeAndPreservesExceptionIdentity()
{
WeakCollectionScope? scope=null;var expected=new InvalidOperationException("known constructor failure");
var actual=Assert.Throws<InvalidOperationException>(()=>new Engine((engine,options)=>
{
scope=((IWeakCollectionOwner)(object)engine).WeakCollections;
var table=new OwnedWeakTable<object,object>(scope);table.Add(new object(),new object());
Assert.Equal(1,scope.Lifetime.Entries);throw expected;
}));
Assert.Same(expected,actual);Assert.NotNull(scope);
Assert.Equal(0,scope.Lifetime.Tables);Assert.Equal(0,scope.Lifetime.Entries);
Assert.Equal(scope.Lifetime.Created,scope.Lifetime.Disposed);
Assert.Equal(scope.Lifetime.TableHandlesCreated,scope.Lifetime.TableHandlesDisposed);
Assert.Throws<ObjectDisposedException>(()=>new OwnedWeakTable<object,object>(scope));
}
[Theory]
[InlineData("MissingHandler")]
[InlineData("WrongFactory")]
[InlineData("OldField")]
[InlineData("MissingVersion")]
[InlineData("UnsupportedVersion")]
[InlineData("WrongOwner")]
public void PartialEngineCachePatchCannotBeTrustedByMerelyUpdatingItsHashReceipt(string damage)
{
string path=PrivatePath();File.Copy(typeof(Engine).Assembly.Location,path);
var receipt=JsonNode.Parse(File.ReadAllText(typeof(Engine).Assembly.Location+".XfeBind.json"))!;
using(var assembly=AssemblyDefinition.ReadAssembly(path,new ReaderParameters{InMemory=true}))
{
var engine=assembly.MainModule.GetType("Jint.Engine");
if(damage=="MissingHandler")engine.Methods.Single(method=>method.IsConstructor&&method.Parameters.Count==2).Body.ExceptionHandlers.Clear();
if(damage=="WrongFactory")engine.Methods.Single(method=>method.Name=="XfeCreatePrivateElementDictionary").Body.Instructions[0].OpCode=OpCodes.Ldnull;
if(damage=="WrongOwner")
{
var creation=engine.Methods.Single(method=>method.Name=="GetEmptyShape").Body.Instructions.Single(instruction=>instruction.OpCode==OpCodes.Newobj&&
instruction.Operand is MethodReference target&&target.DeclaringType.FullName.StartsWith("XFESystem.Collections.OwnedWeakTable`2",StringComparison.Ordinal));
creation.Previous.Previous.OpCode=OpCodes.Ldnull;
}
if(damage=="OldField")
{
var field=engine.Fields.Single(field=>field.Name=="_emptyShapes");var old=(GenericInstanceType)field.FieldType;
var restored=new GenericInstanceType(assembly.MainModule.ImportReference(typeof(System.Runtime.CompilerServices.ConditionalWeakTable<,>)));
foreach(var argument in old.GenericArguments)restored.GenericArguments.Add(argument);field.FieldType=restored;
}
assembly.Write(path);
}
if(damage=="MissingVersion")receipt.AsObject().Remove("EngineWeakCollectionsPatchVersion");
if(damage=="UnsupportedVersion")receipt["EngineWeakCollectionsPatchVersion"]=99;
string hash=Hash(path);receipt["AfterSha256"]=hash;File.WriteAllText(path+".XfeBind.json",receipt.ToJsonString());
Assert.Throws<InvalidOperationException>(()=>Patch.Main([path,"--bind-only"]));Assert.Equal(hash,Hash(path));
}
[Fact]
public void PreviouslyVerifiedMapOnlyPatchUpgradesWithoutDuplicatingOwnersOrChangingGlobalPackages()
{
string packages=Environment.GetEnvironmentVariable("NUGET_PACKAGES")??Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),".nuget","packages");
string original=Path.Combine(packages,"jint","4.11.0","lib","net10.0","Jint.dll");
string originalHash=Hash(original),path=PrivatePath();
using(var assembly=AssemblyDefinition.ReadAssembly(original,new ReaderParameters{InMemory=true}))
{
var module=assembly.MainModule;
// Reproduce the previous verified storage stage on the pinned binary.
typeof(Patch).GetMethod("PatchWeakCollections",System.Reflection.BindingFlags.Static|System.Reflection.BindingFlags.NonPublic)!.Invoke(null,[module]);
var type=module.GetType("Jint.Native.Function.BindFunction");
foreach(var method in type.Methods.Where(method=>method.Name.EndsWith("ICallable.Call",StringComparison.Ordinal)||method.Name=="OrdinaryHasInstance"))
{
bool call=method.Name.EndsWith("ICallable.Call",StringComparison.Ordinal);
var target=module.GetType(call?"Jint.Native.ICallable":"Jint.Native.JsValue").Methods.Single(item=>item.Name==(call?"Call":"OrdinaryHasInstance")&&item.Parameters.Count==(call?2:1));
foreach(var instruction in method.Body.Instructions)
{
if(instruction.OpCode==OpCodes.Isinst&&instruction.Operand is TypeReference reference&&reference.FullName=="Jint.Native.Function.Function")
instruction.Operand=module.GetType(call?"Jint.Native.ICallable":"Jint.Native.Object.ObjectInstance");
if(instruction.OpCode==OpCodes.Callvirt&&instruction.Operand is MethodReference referenceMethod&&referenceMethod.Name==target.Name)instruction.Operand=target;
}
}
module.Types.Add(new("XFESystem","JintBoundCallableTargetsV1",TypeAttributes.Abstract|TypeAttributes.Sealed|TypeAttributes.NotPublic,module.TypeSystem.Object));
assembly.Write(path);
}
File.WriteAllText(path+".XfeBind.json",new JsonObject{["Kind"]="ProjectLocalJintBoundCallableTargets",["Version"]=1,
["PinnedOriginalSha256"]=originalHash,["AfterSha256"]=Hash(path),["WeakCollectionsPatchVersion"]=1}.ToJsonString());
Patch.Main([path,"--bind-only"]);string upgraded=Hash(path);Patch.Main([path,"--bind-only"]);Assert.Equal(upgraded,Hash(path));
using var current=AssemblyDefinition.ReadAssembly(path);Assert.NotNull(current.MainModule.GetType("XFESystem.JintOwnedEngineWeakCollectionsV1"));
Assert.Single(current.MainModule.GetType("Jint.Engine").Fields,field=>field.Name=="_xfeWeakCollections");
Assert.Equal(originalHash,Hash(original));
}
private static string PrivatePath()
{
string? root=AppContext.BaseDirectory;while(root is not null&&!File.Exists(Path.Combine(root,"XFESystem.slnx")))root=Path.GetDirectoryName(root);
Assert.NotNull(root);string directory=Path.Combine(root,"build","EngineWeakFixture-"+Guid.NewGuid().ToString("N"),"obj","XfeJintHost");
Directory.CreateDirectory(directory);return Path.Combine(directory,"Jint.dll");
}
private static string Hash(string path)=>Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path)));
}