using XFESystem.Collections; namespace XFESystem.Browser; public sealed record BrowserWeakCollectionsRegressionResult(bool Passed,string Actual,string Expected, WeakCollectionLifetime BeforeRelease,WeakCollectionLifetime AfterRelease, bool NativeHandleCountChecked,ulong NativeHandlesBefore,ulong NativeHandlesAfter, bool EngineCachesExercised,bool ConstructorFailureReleased,bool ConstructorExceptionPreserved, WeakCollectionLifetime ConstructorBeforeRelease,WeakCollectionLifetime ConstructorAfterRelease); /// Runs the actual patched interpreter intrinsics, not a JS polyfill. /// The owner releases every slot before returning, including evaluation errors. public static class BrowserWeakCollectionsRegression { public static BrowserWeakCollectionsRegressionResult Run(Func? dependentHandleCount=null) { const string expected="true,true,true,true,true,3,true,true,24384,8128,true"; ulong nativeBefore=dependentHandleCount?.Invoke()??0; var runtime=new JavaScriptRuntime(); JavaScriptResult result; try { result=runtime.Evaluate(""" const k={},v={},m=new WeakMap([[k,v]]),s=new WeakSet([k]); let bad=0;try{new WeakMap()}catch(e){bad+=100}try{WeakMap()}catch(e){bad+=e instanceof TypeError?1:100} try{m.set(1,2)}catch(e){bad+=e instanceof TypeError?1:100}try{s.add(1)}catch(e){bad+=e instanceof TypeError?1:100} const same=m.set(k,v)===m&&m.get(k)===v&&m.has(k)&&s.add(k)===s&&s.has(k); const primitive=m.get(4)===undefined&&!m.has(4)&&!m.delete(4)&&!s.has(4)&&!s.delete(4); const desc=!Object.getOwnPropertyDescriptor(WeakMap.prototype,'set').enumerable&&Object.getPrototypeOf(m)===WeakMap.prototype; m.delete(k);const inserted=m.getOrInsert(k,21)===21&&m.getOrInsert(k,99)===21; m.delete(k);const computed=m.getOrInsertComputed(k,()=>{m.set(k,77);return 42})===42&&m.get(k)===42; class Secret { #value; #read(){return this.#value} get #doubled(){return this.#value*2} constructor(value){this.#value=value} read(){return this.#read()+this.#doubled} } class ShapeType {constructor(value){this.own=value}} globalThis.kept=[];globalThis.shapes=[];globalThis.typedShapes=[];let secretSum=0,shapeSum=0,identity=true; for(let i=0;i<128;i++){ const secret=new Secret(i);kept.push(secret);secretSum+=secret.read(); const typed=new ShapeType(i);typedShapes.push(typed); const proto={index:i},shape=Object.create(proto);shape.own=i;shapes.push(shape); shapeSum+=shape.own;identity=identity&&Object.getPrototypeOf(shape)===proto&&shape.index===i&&typed.own===i; } [same,primitive,desc,inserted,computed,bad,s.delete(k),!s.has(k),secretSum,shapeSum,identity].join(',') """); } catch(Exception error) when(error is not OutOfMemoryException and not StackOverflowException) { result=new(false,string.Empty,string.Empty,BoundedText.Safe(error.Message,512)); } var before=runtime.WeakCollectionLifetime; runtime.Dispose(); var after=runtime.WeakCollectionLifetime; WeakCollectionScope? constructorScope=null; var empty=new WeakCollectionLifetime(0,0,0,0,0,0); var constructorBefore=empty;bool constructorExceptionPreserved=false; var knownError=new InvalidOperationException("XFESystem owned Engine constructor regression"); try { _=new Jint.Engine((engine,options)=> { constructorScope=((IWeakCollectionOwner)(object)engine).WeakCollections; var table=new OwnedWeakTable(constructorScope); table.Add(new object(),new object());constructorBefore=constructorScope.Lifetime; throw knownError; }); } catch(Exception error) when(error is not OutOfMemoryException and not StackOverflowException) {constructorExceptionPreserved=ReferenceEquals(error,knownError);} var constructorAfter=constructorScope?.Lifetime??empty; bool constructorReleased=constructorScope is not null&&constructorBefore.Created>0&&constructorAfter.Tables==0&& constructorAfter.Entries==0&&constructorAfter.Created==constructorAfter.Disposed&& constructorAfter.TableHandlesCreated==constructorAfter.TableHandlesDisposed; ulong nativeAfter=dependentHandleCount?.Invoke()??0; bool engineCaches=result.Success&&result.Text==expected&&before.Tables>=5&&before.Created>=130; bool passed=result.Success&&result.Text==expected&&before.Created>0&&after.Tables==0&&after.Entries==0&& after.Created==after.Disposed&&after.TableHandlesCreated==after.TableHandlesDisposed&& engineCaches&&constructorReleased&&constructorExceptionPreserved&& (dependentHandleCount is null||nativeBefore==nativeAfter); return new(passed,result.Success?result.Text:result.Error,expected,before,after, dependentHandleCount is not null,nativeBefore,nativeAfter,engineCaches,constructorReleased,constructorExceptionPreserved, constructorBefore,constructorAfter); } }