char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1mod common; 2 3use common:: run; 4 5# [ test ] 6fn primitives_cycles_and_property_serialization () { 7assert_eq! ( run ( r#" 8for(const value of [undefined,null,true,false,0,-0,NaN,Infinity,-Infinity,123n,'\ud800']){ 9if(!Object.is(structuredClone(value),value))throw Error('Primitive'); 10} 11let calls=0; 12const child={x:1}, source=Object.create(null); 13Object.defineProperties(source,{ 14a:{value:child,enumerable:true},b:{value:child,enumerable:true}, 15hidden:{value:()=>{},enumerable:false}, 16getter:{enumerable:true,get(){calls++;return child;}} 17}); 18source.self=source;source[Symbol('ignored')]=()=>{}; 19const result=structuredClone(source); 20if(result===source || result.self!==result || result.a!==result.b || result.a!==result.getter || result.a===child || calls!==1)throw Error('Graph'); 21if(Object.getPrototypeOf(result)!==Object.prototype || 'hidden' in result || Object.getOwnPropertySymbols(result).length)throw Error('Properties'); 22const d=Object.getOwnPropertyDescriptor(result,'getter'); 23if(!d.writable || !d.enumerable || !d.configurable || d.get)throw Error('Descriptor'); 24const sparse=[];sparse.length=4;sparse[2]=child;sparse.extra=child; 25const array=structuredClone(sparse); 26if(array.length!==4 || 0 in array || array[2]!==array.extra)throw Error('Sparse array'); 27class Custom { constructor(){this.value=7;} method(){} } 28if(Object.getPrototypeOf(structuredClone(new Custom()))!==Object.prototype)throw Error('Custom prototype'); 29if(!Object.getOwnPropertyDescriptor(globalThis,'structuredClone').enumerable || structuredClone.name!=='structuredClone' || structuredClone.length!==1)throw Error('Function shape'); 30try{new structuredClone({});throw Error('Constructor');}catch(e){if(!(e instanceof TypeError))throw e;} 31console.log('graphs'); 32"# , & []). stdout , b"graphs\n" ); 33} 34 35# [ test ] 36fn maps_sets_dates_regexps_and_boxed_primitives () { 37assert_eq! ( run ( r#" 38const key={}, map=new Map(), set=new Set(); 39map.set(key,set);map.set(map,key);set.add(key);set.add(map); 40const date=new Date(123456), re=/a+/giy;re.lastIndex=9; 41for(const value of [map,set,date,re,new Number(NaN),new Boolean(false),new String('hi'),Object(9n)])value.extra=()=>{}; 42const result=structuredClone({map,set,key,date,re,boxed:[new Number(NaN),new Boolean(false),new String('hi'),Object(9n)]}); 43if(result.map.get(result.key)!==result.set || result.map.get(result.map)!==result.key || !result.set.has(result.map) || !result.set.has(result.key))throw Error('Collection identity'); 44if(result.date.getTime()!==123456 || result.re.source!=='a+' || result.re.flags!=='giy' || result.re.lastIndex!==0)throw Error('Date/RegExp'); 45if(!Number.isNaN(result.boxed[0].valueOf()) || result.boxed[1].valueOf()!==false || result.boxed[2].valueOf()!=='hi' || result.boxed[3].valueOf()!==9n)throw Error('Boxed'); 46if(!Number.isNaN(structuredClone(new Date(NaN)).getTime()))throw Error('Invalid Date'); 47console.log('builtins'); 48"# , & []). stdout , b"builtins\n" ); 49} 50 51# [ test ] 52fn buffers_and_views_copy_bytes_and_preserve_shared_graph_backing () { 53assert_eq! ( run ( r#" 54const buffer=new ArrayBuffer(64);new Uint8Array(buffer).set([1,2,3,4]); 55const views=[new Int8Array(buffer,1,3),new Uint8Array(buffer,1,3),new Uint8ClampedArray(buffer,1,3),new Int16Array(buffer,2,3),new Uint16Array(buffer,2,3),new Int32Array(buffer,4,3),new Uint32Array(buffer,4,3),new Float32Array(buffer,4,3),new Float64Array(buffer,8,3),new BigInt64Array(buffer,8,3),new BigUint64Array(buffer,8,3),new DataView(buffer,3,7)]; 56if(typeof Float16Array==='function')views.push(new Float16Array(buffer,2,3)); 57const result=structuredClone({buffer,views,again:views[0]}); 58if(result.buffer===buffer || result.again!==result.views[0] || buffer.detached)throw Error('Buffer identity'); 59for(let i=0;i<views.length;i++){ 60const a=views[i],b=result.views[i]; 61if(b.buffer!==result.buffer || b.byteOffset!==a.byteOffset || b.byteLength!==a.byteLength || Object.getPrototypeOf(b)!==Object.getPrototypeOf(a))throw Error('View'); 62} 63new Uint8Array(buffer).fill(0); 64if(new Uint8Array(result.buffer).slice(0,4).join()!=='1,2,3,4')throw Error('Aliasing'); 65console.log('views'); 66"# , & []). stdout , b"views\n" ); 67} 68 69# [ test ] 70fn transfers_move_buffers_and_detach_even_unreferenced_entries () { 71assert_eq! ( run ( r#" 72const buffer=new ArrayBuffer(4), extra=new ArrayBuffer(2), view=new Uint8Array(buffer);view.set([1,2,3,4]); 73const result=structuredClone({buffer,view,again:buffer},{transfer:new Set([buffer,extra])}); 74if(!buffer.detached || !extra.detached || view.byteLength!==0 || result.buffer===buffer || result.again!==result.buffer || result.view.buffer!==result.buffer || result.view.join()!=='1,2,3,4')throw Error('Transfer'); 75const empty=new ArrayBuffer(0), moved=structuredClone(empty,{transfer:[empty]}); 76if(!empty.detached || moved.detached || moved.byteLength!==0)throw Error('Empty transfer'); 77console.log('transfers'); 78"# , & []). stdout , b"transfers\n" ); 79} 80 81# [ test ] 82fn options_and_transfer_lists_follow_webidl_conversion_order () { 83assert_eq! ( run ( r#" 84for(const options of [1,'x',true,Symbol()]){ 85try{structuredClone({},options);throw Error('Dictionary');}catch(e){if(!(e instanceof TypeError))throw e;} 86} 87for(const transfer of [null,1,'x',{},[null],[1],[Symbol()]]){ 88try{structuredClone({},{transfer});throw Error('Sequence');}catch(e){if(!(e instanceof TypeError))throw e;} 89} 90try{structuredClone();throw Error('Missing value');}catch(e){if(!(e instanceof TypeError))throw e;} 91if(structuredClone(undefined,null)!==undefined)throw Error('Null dictionary'); 92const log=[], buffer=new ArrayBuffer(2); 93const list={get [Symbol.iterator](){log.push('iterator');return function(){log.push('call');let n=0;return {get next(){log.push('next');return function(){log.push('step');return n++ ? {done:true} : {done:false,value:buffer};}}};};}}; 94const source={get x(){log.push('value');return 1;}}; 95structuredClone(source,{get transfer(){log.push('transfer');return list;}}); 96if(log.join()!=='transfer,iterator,call,next,step,step,value' || !buffer.detached)throw Error('Order '+log); 97const reason={}, bad={get transfer(){throw reason;}}; 98try{structuredClone({},bad);throw Error('Getter');}catch(e){if(e!==reason)throw e;} 99console.log('conversions'); 100"# , & []). stdout , b"conversions\n" ); 101} 102 103# [ test ] 104fn clone_failures_are_dom_exceptions_and_do_not_detach_before_serialization () { 105assert_eq! ( run ( r#" 106function fails(value,options){try{structuredClone(value,options);throw Error('Accepted');}catch(e){if(!(e instanceof DOMException) || e.name!=='DataCloneError' || e.code!==25)throw e;}} 107for(const value of [()=>{},Symbol(),Object(Symbol()),new WeakMap(),new WeakSet(),new WeakRef({}),new Promise(()=>{}),new Proxy({},{}),new Proxy([],{}),[()=>{}],new Map([[()=>{},1]])])fails(value); 108const buffer=new ArrayBuffer(4); 109fails({fn:()=>{}},{transfer:[buffer]}); 110fails({},{transfer:[buffer,buffer]}); 111fails({},{transfer:[buffer,{}]}); 112fails({},{transfer:[new SharedArrayBuffer(1)]}); 113if(buffer.detached)throw Error('Early detach'); 114const detached=new ArrayBuffer(1), view=new Uint8Array(detached), data=new DataView(detached);detached.transfer(); 115for(const value of [detached,view,data])fails(value); 116fails({},{transfer:[detached]}); 117const beforeDetached={}; 118try{structuredClone({get x(){throw beforeDetached;}},{transfer:[detached]});throw Error('Early detached check');}catch(e){if(e!==beforeDetached)throw e;} 119const frozenStream=new ReadableStream(), frozenBuffer=new ArrayBuffer(1); 120Object.freeze(frozenStream); 121fails({stream:frozenStream},{transfer:[frozenBuffer,frozenStream]}); 122if(frozenBuffer.detached || frozenStream.locked)throw Error('Frozen stream transfer changed sources'); 123const reason={exact:true}; 124try{structuredClone({get x(){throw reason;}},{transfer:[buffer]});throw Error('Getter');}catch(e){if(e!==reason)throw e;} 125if(buffer.detached || structuredClone({x:1}).x!==1)throw Error('Failed state'); 126console.log('failures'); 127"# , & []). stdout , b"failures\n" ); 128} 129 130# [ test ] 131fn getters_can_resize_or_detach_transfers_and_fail_in_list_order () { 132assert_eq! ( run ( r#" 133const first=new ArrayBuffer(2), second=new ArrayBuffer(3); 134try{structuredClone({get x(){second.transfer();return 1;}},{transfer:[first,second]});throw Error('Detached late buffer');}catch(e){if(e.name!=='DataCloneError')throw e;} 135if(!first.detached || !second.detached)throw Error('Transfer order'); 136const a=new ArrayBuffer(2), b=new ArrayBuffer(3); 137try{structuredClone({get x(){a.transfer();return 1;}},{transfer:[a,b]});throw Error('Detached first');}catch(e){if(e.name!=='DataCloneError')throw e;} 138if(!a.detached || b.detached)throw Error('Failure order'); 139const rab=new ArrayBuffer(2,{maxByteLength:8});new Uint8Array(rab).set([3,4]); 140const result=structuredClone({rab,get x(){rab.resize(4);return 1;}},{transfer:[rab]}); 141if(!rab.detached || !result.rab.resizable || result.rab.maxByteLength!==8 || result.rab.byteLength!==4 || new Uint8Array(result.rab).join()!=='3,4,0,0')throw Error('Resize during serialization'); 142const memory=new WebAssembly.Memory({initial:1}); 143try{structuredClone(memory.buffer,{transfer:[memory.buffer]});throw Error('Wasm buffer transfer');}catch(e){if(!(e instanceof TypeError))throw e;} 144const shrunk=new ArrayBuffer(4,{maxByteLength:8}), fixed=new Uint8Array(shrunk,1,3); 145try{structuredClone({fixed,get x(){shrunk.resize(1);return 1;}},{transfer:[shrunk]});throw Error('Invalidated view');}catch(e){if(e.name!=='DataCloneError' || !e.message.includes('not yet implemented'))throw e;} 146if(!shrunk.detached || structuredClone(3)!==3)throw Error('Deserialization failure state'); 147console.log('late transfers'); 148"# , & []). stdout , b"late transfers\n" ); 149} 150 151# [ test ] 152fn resizable_and_shared_buffers_keep_their_native_semantics () { 153assert_eq! ( run ( r#" 154const buffer=new ArrayBuffer(4,{maxByteLength:12}), tracking=new Uint8Array(buffer), fixed=new Uint8Array(buffer,1,2); 155tracking.set([1,2,3,4]);const result=structuredClone({buffer,tracking,fixed}); 156if(!result.buffer.resizable || result.buffer.maxByteLength!==12 || result.tracking.buffer!==result.buffer || result.fixed.buffer!==result.buffer)throw Error('Resizable'); 157result.buffer.resize(8); 158if(result.tracking.length!==8 || result.fixed.length!==2 || buffer.byteLength!==4)throw Error('Tracking'); 159const shared=new SharedArrayBuffer(4,{maxByteLength:12}), view=new Uint8Array(shared);view[0]=5; 160const copy=structuredClone({shared,view,again:shared}); 161if(copy.shared===shared || copy.shared!==copy.again || copy.view.buffer!==copy.shared || !copy.shared.growable || copy.shared.maxByteLength!==12)throw Error('Shared graph'); 162copy.view[0]=9;copy.shared.grow(8); 163if(view[0]!==9 || shared.byteLength!==8 || copy.view.length!==8 || view.length!==8)throw Error('Shared memory'); 164console.log('native buffers'); 165"# , & []). stdout , b"native buffers\n" ); 166} 167 168# [ test ] 169fn errors_dom_exceptions_and_webassembly_modules_clone () { 170assert_eq! ( run ( r#" 171for(const C of [Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError]){ 172const error=new C('message',{cause:{x:1}});error.extra=()=>{}; 173const result=structuredClone(error); 174if(!(result instanceof C) || result.message!=='message' || result.cause.x!==1 || result.cause===error.cause || 'extra' in result)throw Error('Error'); 175} 176const e=new Error('cyclic');e.cause=e;const c=structuredClone(e);if(c.cause!==c)throw Error('Error cycle'); 177const source=new DOMException('message','DataCloneError');source.extra=()=>{}; 178const stack=source.stack; 179Object.defineProperty(source,'name',{get(){throw Error('Own name getter');}}); 180const d=structuredClone(source); 181if(!Error.isError(d) || d.stack!==stack || DOMException.DATA_CLONE_ERR!==25 || d.DATA_CLONE_ERR!==25)throw Error('DOMException error data'); 182if(!(d instanceof DOMException) || d.name!=='DataCloneError' || d.code!==25 || d.message!=='message' || 'extra' in d)throw Error('DOMException'); 183const module=new WebAssembly.Module(new Uint8Array([0,97,115,109,1,0,0,0])); 184const modules=structuredClone([module,module]); 185if(modules[0]===module || modules[0]!==modules[1] || !(modules[0] instanceof WebAssembly.Module) || !(new WebAssembly.Instance(modules[0]) instanceof WebAssembly.Instance))throw Error('Wasm module'); 186console.log('errors and wasm'); 187"# , & []). stdout , b"errors and wasm\n" ); 188} 189 190# [ test ] 191fn blobs_and_files_clone_private_immutable_storage_and_metadata () { 192assert_eq! ( run ( r#" 193const bytes=new Uint8Array([1,2,3]), blob=new Blob([bytes],{type:'Text/Plain'}), file=new File([blob],'a\0\ud800.txt',{type:'x/file',lastModified:123}); 194bytes.fill(0);blob.extra=()=>{};file.extra=()=>{}; 195Object.defineProperty(file,'name',{get(){throw Error('Public metadata');}}); 196Object.setPrototypeOf(blob,null); 197const result=structuredClone({blob,file,again:file,map:new Map([[file,blob]])}); 198if(!(result.blob instanceof Blob) || result.blob===blob || result.blob.type!=='text/plain' || result.blob.size!==3)throw Error('Blob'); 199if(!(result.file instanceof File) || result.file===file || result.file!==result.again || result.map.get(result.file)!==result.blob || result.file.name!=='a\0\ufffd.txt' || result.file.lastModified!==123 || result.file.type!=='x/file')throw Error('File'); 200if('extra' in result.blob || 'extra' in result.file)throw Error('Expandos'); 201(await result.file.bytes()).fill(0); 202if((await result.blob.bytes()).join()!=='1,2,3' || (await File.prototype.bytes.call(file)).join()!=='1,2,3')throw Error('Mutable clone'); 203const mime=await new Response('x',{headers:{'Content-Type':'Text/HTML; Charset=UTF-8'}}).blob(); 204if(structuredClone(mime).type!=='text/html;charset=UTF-8')throw Error('MIME case'); 205let rope=new Blob(['x']);for(let i=0;i<50;i++)rope=new Blob([rope,rope]); 206const copied=structuredClone(rope);if(copied.size!==2**50 || await copied.slice(-1).text()!=='x')throw Error('Flattened rope'); 207console.log('blob clones'); 208"# , & []). stdout , b"blob clones\n" ); 209} 210 211# [ test ] 212fn platform_brands_reject_cloning_without_reading_public_properties () { 213assert_eq! ( run ( r#" 214let readableController, byteController, writableController, transformController; 215const readable=new ReadableStream({start(c){readableController=c;}}), bytes=new ReadableStream({type:'bytes',start(c){byteController=c;}}); 216const writable=new WritableStream({start(c){writableController=c;}}), transform=new TransformStream({start(c){transformController=c;}}); 217const reader=readable.getReader(), byob=bytes.getReader({mode:'byob'}), writer=writable.getWriter(); 218const values=[new URL('https://example.test'),new URLSearchParams(),new URLSearchParams().entries(),new FormData().values(),new URLPattern({pathname:'*'}),new FormData(),new Headers(),new Request('https://example.test'),new Response(),new Event('x'),new EventTarget(),new AbortController(),new AbortController().signal,new TextEncoder(),new TextDecoder(),new TextEncoderStream(),new TextDecoderStream(),new ByteLengthQueuingStrategy({highWaterMark:1}),new CountQueuingStrategy({highWaterMark:1}),readable,bytes,writable,transform,reader,byob,writer,readableController,byteController,writableController,transformController,transform.readable,transform.writable]; 219for(const value of values){ 220Object.defineProperty(value,'trap',{enumerable:true,get(){throw Error('Read a platform expando');}}); 221Object.setPrototypeOf(value,null); 222try{structuredClone(value);throw Error('Cloned platform object');}catch(e){if(e.name!=='DataCloneError')throw e;} 223} 224for(const stream of [readable,writable]){ 225const buffer=new ArrayBuffer(1);let getter=false; 226try{structuredClone({get value(){getter=true;return 1;}}, {transfer:[buffer,stream]});throw Error('Transferred locked stream');}catch(e){if(e.name!=='DataCloneError')throw e;} 227if(buffer.detached||getter)throw Error('Invalid transfer was not rejected first'); 228} 229const transferBuffer=new ArrayBuffer(1), movedTransform=structuredClone(transform,{transfer:[transferBuffer,transform]}); 230if(!transferBuffer.detached || !(movedTransform instanceof TransformStream) 231|| !(movedTransform.readable instanceof ReadableStream) 232|| !(movedTransform.writable instanceof WritableStream))throw Error('Transform transfer'); 233const fake=Object.create(Blob.prototype);fake.value=1; 234if(structuredClone(fake).value!==1)throw Error('Prototype mistaken for brand'); 235console.log('brands'); 236"# , & []). stdout , b"brands\n" ); 237} 238 239# [ test ] 240fn cloning_is_permissionless_and_native_helpers_remain_private () { 241assert_eq! ( run ( r#" 242import {pledge} from 'avril:std';pledge({net:false,read:false,jsExec:false}); 243const buffer=new Uint8Array([1,2]).buffer; 244if(structuredClone(buffer,{transfer:[buffer]}).byteLength!==2 || !buffer.detached)throw Error('Permissionless clone'); 245if(await structuredClone(new Blob(['x'])).text()!=='x')throw Error('Permissionless Blob'); 246for(const name of ['ext:core/structured_clone','ext:web/serialization.js','ext:structured_clone/main.js']){ 247try{await import(name);throw Error('Private import');}catch(e){if(!e.message.includes('private'))throw e;} 248} 249const opsSymbol=Symbol.for('__avril.ops'); 250if(opsSymbol in globalThis || globalThis[opsSymbol]!==undefined || Reflect.ownKeys(globalThis).includes(opsSymbol))throw Error('Public native op registry'); 251console.log('private and permissionless'); 252"# , & []). stdout , b"private and permissionless\n" ); 253} 254 255# [ test ] 256fn private_cloning_uses_primordials_not_guest_constructors_or_prototype_hooks () { 257assert_eq! ( run ( r#" 258const blob=new Blob(['x']), file=new File([blob],'name',{lastModified:7}), exception=new DOMException('message','DataCloneError'); 259const buffer=new Uint8Array([1,2,3]).buffer, originalDOMException=DOMException; 260const define=Object.defineProperty, getDescriptor=Object.getOwnPropertyDescriptor, getPrototype=Object.getPrototypeOf, setPrototype=Object.setPrototypeOf; 261const restore=[]; 262const poison=()=>{throw Error('Guest hook invoked');}; 263for(const [object,key] of [[WeakMap.prototype,'get'],[WeakMap.prototype,'set'],[Array.prototype,'push'],[ArrayBuffer.prototype,'slice'],[ArrayBuffer.prototype,'transfer'],[Object,'create'],[Object,'defineProperty'],[Reflect,'construct'],[Reflect,'apply']]){ 264restore.push([object,key,getDescriptor(object,key)]); 265} 266for(let i=0;i<restore.length;i++)define(restore[i][0],restore[i][1],{value:poison,writable:true,configurable:true}); 267const oldSuper=getPrototype(originalDOMException);setPrototype(originalDOMException,poison); 268define(Array.prototype,'0',{set:poison,configurable:true}); 269define(blob,'size',{get:poison});define(file,'name',{get:poison}); 270globalThis.DOMException=poison; 271let used=false; 272const transfer={ [Symbol.iterator](){return {next(){if(used)return {done:true};used=true;return {done:false,value:buffer};}};} }; 273const result=structuredClone({blob,file,exception,buffer},{transfer}); 274let failure; 275try{structuredClone(()=>{});}catch(e){failure=e;} 276delete Array.prototype[0]; 277for(let i=0;i<restore.length;i++)define(restore[i][0],restore[i][1],restore[i][2]); 278setPrototype(originalDOMException,oldSuper);globalThis.DOMException=originalDOMException; 279if(!buffer.detached || new Uint8Array(result.buffer).join()!=='1,2,3' || result.file.name!=='name' || result.file.lastModified!==7 || await result.blob.text()!=='x' || result.exception.message!=='message')throw Error('Tampered clone'); 280if(!(failure instanceof originalDOMException) || failure.name!=='DataCloneError' || failure.code!==25 || !Error.isError(failure))throw Error('Tampered clone error'); 281console.log('primordials'); 282"# , & []). stdout , b"primordials\n" ); 283}