char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
e0f52fe
main
1use std:: io; 2 3use avril_core::{ Error , Extension , ModuleSource , Op , Runtime , RuntimeOptions }; 4use serde_json::{ Value , json}; 5 6# [ test ] 7fn typed_ops_and_script_results () { 8let mut runtime =Runtime :: new ( RuntimeOptions { 9extensions : vec! [ Extension { 10name : "ext:test" , 11dependencies : & [], 12ops : vec! [ Op :: new ( "echo" , |value : Value |Ok ::< _ , io:: Error >( value))], 13modules : & [( 14"ext:test/main.js" , 15r#"import {echo} from 'ext:core/ops'; 16globalThis.echo = echo; 17globalThis.bootstrapped = echo({message: "hello", values: [1, true, null]});"# , 18)], 19entry_point : Some ( "ext:test/main.js" ), 20}], 21 ..Default :: default () 22}) 23. unwrap (); 24 25let value: Value = runtime 26. execute_script ( "echo.js" , "echo(bootstrapped)" ) 27. unwrap (); 28assert_eq! ( 29 value, 30json! ({ "message" : "hello" , "values" : [ 1 , true , null]}) 31); 32} 33 34# [ test ] 35fn op_errors_are_javascript_exceptions () { 36let mut runtime =Runtime :: new ( RuntimeOptions { 37extensions : vec! [ Extension { 38name : "ext:test" , 39dependencies : & [], 40ops : vec! [ Op :: new ( "fail" , |_: Vec < String > |{ 41Err ::<(), _ >( io:: Error :: other ( "host failure" )) 42})], 43modules : & [( 44"ext:test/main.js" , 45"import {fail} from 'ext:core/ops'; globalThis.fail = fail;" , 46)], 47entry_point : Some ( "ext:test/main.js" ), 48}], 49 ..Default :: default () 50}) 51. unwrap (); 52 53let errors: Vec < String > = runtime 54. execute_script ( 55"errors.js" , 56r#" 57[42, ["valid"]].map((args) => { 58try { 59fail(args); 60throw new Error("Expected an op error"); 61} catch (error) { 62return error.name + ":" + error.message; 63} 64}); 65"# , 66) 67. unwrap (); 68assert! ( errors[ 0 ]. starts_with ( "TypeError:" )); 69assert_eq! ( errors[ 1 ], "Error:host failure" ); 70} 71 72# [ test ] 73fn script_errors_do_not_poison_the_runtime () { 74let mut runtime =Runtime :: new ( RuntimeOptions :: default ()). unwrap (); 75for sourcein [ "const =" , "throw new Error('oops')" ] { 76assert! ( matches! ( 77 runtime. execute_script ::<()>( "failure.js" , source), 78Err ( Error :: JavaScript ( _)) 79)); 80} 81assert_eq! ( runtime. execute_script ::< u32 >( "ok.js" , "6 * 7" ). unwrap (), 42 ); 82} 83 84# [ test ] 85fn duplicate_ops_are_rejected () { 86let result =Runtime :: new ( RuntimeOptions { 87extensions : vec! [ Extension { 88name : "ext:test" , 89dependencies : & [], 90ops : vec! [ 91Op :: new ( "duplicate" , |_: () |Ok ::< _ , io:: Error >(())), 92Op :: new ( "duplicate" , |_: () |Ok ::< _ , io:: Error >(())), 93], 94modules : & [], 95entry_point : None , 96}], 97 ..Default :: default () 98}); 99assert! ( matches! ( result, Err ( Error :: DuplicateOp ( "duplicate" )))); 100} 101 102# [ test ] 103fn failing_extensions_abort_initialization () { 104let result =Runtime :: new ( RuntimeOptions { 105extensions : vec! [ Extension { 106name : "ext:broken" , 107dependencies : & [], 108ops : vec! [], 109modules : & [( "ext:broken/main.js" , "throw new Error('bootstrap failed');" )], 110entry_point : Some ( "ext:broken/main.js" ), 111}], 112 ..Default :: default () 113}); 114assert! ( 115matches! ( result, Err ( Error :: JavaScript ( message)) if message. contains ( "bootstrap failed" )) 116); 117} 118 119# [ test ] 120fn script_turns_drain_microtasks_and_report_rejections_without_leaking_them () { 121let mut runtime =Runtime :: new ( RuntimeOptions :: default ()). unwrap (); 122assert_eq! ( 123 runtime 124. execute_script ::< Value >( 125"result.js" , 126r#" 127const result = { value: 0 }; 128Promise.resolve().then(() => result.value = 42); 129result; 130"# 131) 132. unwrap (), 133json! ({ "value" : 42 }) 134); 135 runtime 136. execute_script ::<()>( 137"jobs.js" , 138"globalThis.answer = 0; Promise.resolve().then(() => answer = 42);" , 139) 140. unwrap (); 141assert_eq! ( 142 runtime 143. execute_script ::< u32 >( "answer.js" , "answer" ) 144. unwrap (), 14542 146); 147 runtime 148. execute_script ::<()>( 149"handled.js" , 150"const p = Promise.reject('handled'); Promise.resolve().then(() => p.catch(() => {}));" , 151) 152. unwrap (); 153for sourcein [ 154"Promise.reject(new Error('first')); Promise.reject(new Error('second'));" , 155"Promise.reject(new Error('discarded')); throw new Error('first');" , 156] { 157let error = runtime 158. execute_script ::<()>( "rejected.js" , source) 159. unwrap_err (); 160assert! ( error. to_string (). contains ( "first" ), "{error}" ); 161 runtime. run_event_loop (). unwrap (); 162assert_eq! ( runtime. execute_script ::< u32 >( "good.js" , "42" ). unwrap (), 42 ); 163} 164} 165 166# [ test ] 167fn embedding_event_loop_settles_async_ops_and_continuations () { 168let mut runtime =Runtime :: new ( RuntimeOptions { 169extensions : vec! [ Extension { 170name : "ext:async" , 171dependencies : & [], 172ops : vec! [ 173Op :: asynchronous ( 174"echo" , 175 |value : u32 |async move { Ok ::< _ , io:: Error >( value) }, 176), 177Op :: asynchronous ( "fail" , |_: () |async { 178Err ::<(), _ >( io:: Error :: other ( "host failure" )) 179}), 180], 181modules : & [( 182"ext:async/main.js" , 183"import {echo, fail} from 'ext:core/ops'; globalThis.echo = echo; globalThis.fail = fail;" , 184)], 185entry_point : Some ( "ext:async/main.js" ), 186}], 187 ..Default :: default () 188}) 189. unwrap (); 190 runtime 191. execute_script ::<()>( 192"start.js" , 193r#" 194globalThis.answer = 0; 195echo(41).then(value => echo(value + 1)).then(value => answer = value); 196"# , 197) 198. unwrap (); 199assert_eq! ( 200 runtime 201. execute_script ::< u32 >( "before.js" , "answer" ) 202. unwrap (), 2030 204); 205 runtime. run_event_loop (). unwrap (); 206assert_eq! ( 207 runtime. execute_script ::< u32 >( "after.js" , "answer" ). unwrap (), 20842 209); 210 runtime. execute_script ::<()>( "fail.js" , "fail();" ). unwrap (); 211assert! ( 212 runtime 213. run_event_loop () 214. unwrap_err () 215. to_string () 216. contains ( "host failure" ) 217); 218 runtime. run_event_loop (). unwrap (); 219} 220 221# [ test ] 222fn unreferenced_async_ops_do_not_keep_liveness_but_run_with_other_work () { 223use std:: cell:: RefCell ; 224use std:: rc:: Rc ; 225 226let ( wait_sender, wait_receiver) = futures:: channel:: oneshot:: channel ::< u32 >(); 227let ( reference_sender, reference_receiver) = futures:: channel:: oneshot:: channel ::< u32 >(); 228let wait_receiver =Rc :: new ( RefCell :: new ( Some ( wait_receiver))); 229let reference_receiver =Rc :: new ( RefCell :: new ( Some ( reference_receiver))); 230let mut runtime =Runtime :: new ( RuntimeOptions { 231extensions : vec! [ Extension { 232name : "ext:async" , 233dependencies : & [], 234ops : vec! [ 235Op :: asynchronous_unreferenced ( "wait" , { 236let receiver = wait_receiver. clone (); 237move |(): () |{ 238let receiver = receiver. borrow_mut (). take (). unwrap (); 239async move { receiver. await . map_err ( io:: Error :: other) } 240} 241}), 242Op :: asynchronous_unreferenced ( "ready" , |(): () |async { Ok ::< _ , io:: Error >( 7u32 ) }), 243Op :: asynchronous ( "reference" , move |(): () |{ 244let receiver = reference_receiver. borrow_mut (). take (). unwrap (); 245async move { receiver. await . map_err ( io:: Error :: other) } 246}), 247], 248modules : & [( 249"ext:async/main.js" , 250"import {wait, ready, reference} from 'ext:core/ops'; globalThis.testWait = wait; globalThis.testReady = ready; globalThis.testReference = reference;" , 251)], 252entry_point : Some ( "ext:async/main.js" ), 253}], 254 ..Default :: default () 255}) 256. unwrap (); 257 runtime 258. execute_script ::<()>( 259"unreferenced.js" , 260r#" 261globalThis.waitResult = undefined; 262globalThis.readyResult = undefined; 263testWait().then(value => waitResult = value); 264testReady().then(value => readyResult = value); 265"# , 266) 267. unwrap (); 268 runtime. run_event_loop (). unwrap (); 269assert_eq! ( 270 runtime 271. execute_script ::< u32 >( "ready.js" , "readyResult" ) 272. unwrap (), 2737 274); 275assert_eq! ( 276 runtime 277. execute_script ::< Option < u32 >>( "wait.js" , "waitResult" ) 278. unwrap (), 279None 280); 281 wait_sender. send ( 11 ). unwrap (); 282 runtime. run_event_loop (). unwrap (); 283assert_eq! ( 284 runtime 285. execute_script ::< u32 >( "wait.js" , "waitResult" ) 286. unwrap (), 28711 288); 289 290 runtime 291. execute_script ::<()>( 292"referenced.js" , 293r#" 294globalThis.order = []; 295testReady().then(() => order.push('unreferenced')); 296testReference().then(() => order.push('referenced')); 297"# , 298) 299. unwrap (); 300let sender = std:: thread:: spawn ( move ||{ 301 std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 10 )); 302 reference_sender. send ( 13 ). unwrap (); 303}); 304 runtime. run_event_loop (). unwrap (); 305 sender. join (). unwrap (); 306assert_eq! ( 307 runtime 308. execute_script ::< Vec < String >>( "order.js" , "order" ) 309. unwrap (), 310[ "unreferenced" , "referenced" ] 311); 312} 313 314# [ test ] 315fn module_failures_do_not_contaminate_later_turns () { 316struct Loader ; 317impl avril_core:: ModuleLoader for Loader { 318fn resolve ( & self , specifier : & str , _: & str ) ->Result < String , Error > { 319Ok ( specifier. into ()) 320} 321fn load ( & self , name : & str ) ->Result < ModuleSource , Error > { 322Ok ( match name{ 323"pending" =>"await new Promise(() => {});" , 324"rejected" =>"throw new Error('module failure');" , 325 _ =>"export {};" , 326} 327. into ()) 328} 329} 330let mut runtime =Runtime :: new ( RuntimeOptions { 331module_loader : Some ( std:: rc:: Rc :: new ( Loader )), 332 ..Default :: default () 333}) 334. unwrap (); 335assert! ( 336 runtime 337. execute_script ::<()>( "bad.js" , "Promise.reject('script failure');" ) 338. is_err () 339); 340 runtime. execute_module ( "good" ). unwrap (); 341for ( name, message) in [ 342( "pending" , "no runnable host work" ), 343( "rejected" , "module failure" ), 344] { 345assert! ( 346 runtime 347. execute_module ( name) 348. unwrap_err () 349. to_string () 350. contains ( message) 351); 352 runtime. run_event_loop (). unwrap (); 353} 354} 355 356# [ test ] 357fn stack_formatting_preserves_native_headers_and_getter_guards () { 358let mut runtime =Runtime :: new ( RuntimeOptions :: default ()). unwrap (); 359let stacks: Vec < String > = runtime 360. execute_script ( 361"stacks.js" , 362r#" 363const original = new Error("original"); 364original.message = Symbol("changed"); 365original.name = "Renamed"; 366const hostile = new Error("hidden"); 367Object.defineProperty(hostile, "name", { 368get() { throw new Error("bad name"); } 369}); 370const nested = new Error("hidden"); 371Object.defineProperty(nested, "name", { get() { throw null; } }); 372const plain = { name: "Captured", message: "plain" }; 373Error.captureStackTrace(plain); 374[original.stack, hostile.stack, nested.stack, plain.stack]; 375"# , 376) 377. unwrap (); 378for ( stack, header) in stacks. iter (). zip ([ 379"Renamed: original" , 380"<error: Error: bad name>" , 381"<error>" , 382"Captured: plain" , 383]) { 384assert! ( 385 stack. starts_with ( & format! ( "{header}\n at stacks.js:" )), 386"{stack}" 387); 388} 389let error = runtime 390. execute_script ::<()>( 391"hostile.js" , 392r#" 393const thrown = new Error("original"); 394Object.defineProperty(thrown, "name", { get() { throw new Error("bad name"); } }); 395throw thrown; 396"# , 397) 398. unwrap_err () 399. to_string (); 400assert! ( 401 error. contains ( "<error: Error: bad name>\n at hostile.js:" ), 402"{error}" 403); 404assert_eq! ( runtime. execute_script ::< u32 >( "ok.js" , "6 * 7" ). unwrap (), 42 ); 405} 406 407# [ test ] 408fn stack_formatting_uses_current_message () { 409let mut runtime =Runtime :: new ( RuntimeOptions :: default ()). unwrap (); 410let stacks: Vec < String > = runtime 411. execute_script ( 412"current.js" , 413r#" 414const changed = new Error("original"); 415changed.message = "changed"; 416const lazy = new Error("creation"); 417Object.defineProperty(lazy, "message", { 418get() { return "lazy-now"; }, 419configurable: true, 420}); 421const multiline = new Error("original"); 422multiline.message = "line1\nline2"; 423function absent() {} 424const frameless = new Error("with frames"); 425frameless.message = "line1\nline2"; 426Error.captureStackTrace(frameless, absent); 427const plain = { name: "Shaped", message: "plain" }; 428Object.defineProperty(plain, "message", { 429get() { return "lazy-plain"; }, 430configurable: true, 431}); 432Error.captureStackTrace(plain, absent); 433const hostile = new Error("preserved"); 434Object.defineProperty(hostile, "message", { 435get() { throw new Error("bad message"); }, 436}); 437[changed.stack, lazy.stack, multiline.stack, frameless.stack, plain.stack, hostile.stack]; 438"# , 439) 440. unwrap (); 441assert! ( 442 stacks[ 0 ]. starts_with ( "Error: changed\n at current.js:" ), 443"{}" , 444 stacks[ 0 ] 445); 446assert! ( 447 stacks[ 1 ]. starts_with ( "Error: lazy-now\n at current.js:" ), 448"{}" , 449 stacks[ 1 ] 450); 451assert! ( 452 stacks[ 2 ]. starts_with ( "Error: line1\nline2\n at current.js:" ), 453"{}" , 454 stacks[ 2 ] 455); 456assert_eq! ( stacks[ 3 ], "Error: line1\nline2" , "{}" , stacks[ 3 ]); 457assert_eq! ( stacks[ 4 ], "Shaped: lazy-plain" , "{}" , stacks[ 4 ]); 458assert! ( 459 stacks[ 5 ]. starts_with ( "Error: preserved\n at current.js:" ), 460"{}" , 461 stacks[ 5 ] 462); 463} 464 465# [ test ] 466fn custom_stack_hooks_preserve_v8_behavior () { 467let mut runtime =Runtime :: new ( RuntimeOptions :: default ()). unwrap (); 468let result: Value = runtime. execute_script ( "custom.js" , r#" 469const result = { unset: Error.prepareStackTrace === undefined }; 470const custom = function(error, frames) { 471result.receiver = this === Error; 472result.message = error.message; 473return frames; 474}; 475Error.prepareStackTrace = custom; 476result.identity = Error.prepareStackTrace === custom; 477result.frames = Array.isArray(new Error("custom").stack); 478const retry = new Error("retry"); 479Error.prepareStackTrace = () => { throw "formatter threw"; }; 480try { retry.stack; } catch (error) { result.thrown = error; } 481Error.prepareStackTrace = () => "retried"; 482result.retry = retry.stack; 483Object.defineProperty(Error, "prepareStackTrace", { 484get() { throw "getter threw"; }, configurable: true, 485}); 486const getterRetry = new Error("getter retry"); 487try { getterRetry.stack; } catch (error) { result.getter = error; } 488delete Error.prepareStackTrace; 489result.getterRetry = getterRetry.stack.startsWith("Error: getter retry\n at custom.js:"); 490result.restored = new Error("restored").stack.startsWith("Error: restored\n at custom.js:"); 491result; 492"# ). unwrap (); 493assert_eq! ( 494 result, 495json! ({ 496"unset" : true , "receiver" : true , "message" : "custom" , "identity" : true , 497"frames" : true , "thrown" : "formatter threw" , "retry" : "retried" , 498"getter" : "getter threw" , "getterRetry" : true , "restored" : true , 499}) 500); 501} 502 503# [ test ] 504fn dynamic_imports_wait_for_static_async_dependencies_and_share_evaluation () { 505struct Loader ; 506impl avril_core:: ModuleLoader for Loader { 507fn resolve ( & self , specifier : & str , _: & str ) ->Result < String , Error > { 508Ok ( specifier. into ()) 509} 510fn module_url ( & self , name : & str ) ->Option < String > { 511Some ( format! ( "test:{name}" )) 512} 513fn load ( & self , name : & str ) ->Result < ModuleSource , Error > { 514Ok ( match name{ 515"entry" =>{ 516r#" 517import 'waiting'; import 'observer'; 518globalThis.entryRuns = (globalThis.entryRuns ?? 0) + 1; 519globalThis.entryMeta = [import.meta.main, import.meta.url]; 520"# 521} 522"waiting" |"later" =>{ 523r#" 524export const value = await testReady(); 525export const main = import.meta.main; 526"# 527} 528"observer" =>{ 529r#" 530const [a, b] = await Promise.all([import('waiting'), import('waiting')]); 531globalThis.observed = [a === b, a.value, a.main]; 532"# 533} 534 _ =>"globalThis.otherMain = import.meta.main;" , 535} 536. into ()) 537} 538} 539let calls = std:: rc:: Rc :: new ( std:: cell:: Cell :: new ( 0 )); 540let counter = calls. clone (); 541let mut runtime =Runtime :: new ( RuntimeOptions { 542module_loader : Some ( std:: rc:: Rc :: new ( Loader )), 543extensions : vec! [ Extension { 544name : "ext:ready" , 545dependencies : & [], 546modules : & [( 547"ext:ready/main.js" , 548"import {ready} from 'ext:core/ops'; globalThis.testReady = ready;" , 549)], 550entry_point : Some ( "ext:ready/main.js" ), 551ops : vec! [ Op :: asynchronous ( "ready" , move |_: () |{ 552 counter. set ( counter. get () +1 ); 553async { Ok ::< _ , io:: Error >( 42 ) } 554})], 555}], 556}) 557. unwrap (); 558 runtime. execute_module ( "entry" ). unwrap (); 559 runtime. execute_module ( "entry" ). unwrap (); 560 runtime. execute_module ( "other" ). unwrap (); 561let state: Value = runtime 562. execute_script ( 563"check.js" , 564r#" 565({ observed, entryMeta, entryRuns, otherMain }) 566"# , 567) 568. unwrap (); 569assert_eq! ( 570 state, 571json! ({ 572"observed" : [ true , 42 , false ], "entryMeta" : [ true , "test:entry" ], 573"entryRuns" : 1 , "otherMain" : false , 574}) 575); 576assert_eq! ( calls. get (), 1 ); 577 578let result: Value = runtime 579. execute_script ( 580"launch.js" , 581r#" 582globalThis.launched = null; 583import('later').then(ns => { globalThis.launched = ns.value; }); 584launched; 585"# , 586) 587. unwrap (); 588assert_eq! ( result, Value :: Null ); 589assert_eq! ( 590 runtime 591. execute_script ::< Value >( "check.js" , "launched" ) 592. unwrap (), 593Value :: Null 594); 595 runtime. run_event_loop (). unwrap (); 596assert_eq! ( calls. get (), 2 ); 597assert_eq! ( 598 runtime 599. execute_script ::< u32 >( "check.js" , "launched" ) 600. unwrap (), 60142 602); 603} 604 605# [ test ] 606fn registered_modules_capture_exports_without_a_loader () { 607let mut runtime =Runtime :: new ( RuntimeOptions :: default ()). unwrap (); 608 runtime 609. execute_script ::<()>( 610"bootstrap" , 611"globalThis.values = { answer: 42, object: {} };" , 612) 613. unwrap (); 614 runtime 615. register_module ( "test:values" , |scope|{ 616let key = avril_core:: v8:: String :: new ( scope, "values" ) ?; 617let global = scope. get_current_context (). global ( scope); 618 avril_core:: v8:: Local ::< avril_core:: v8:: Object >:: try_from ( 619 global. get ( scope, key. into ()) ?, 620) 621. ok () 622}) 623. unwrap (); 624assert! ( 625 runtime 626. register_module ( "test:values" , |scope|Some ( avril_core:: v8:: Object :: new ( 627 scope 628))) 629. unwrap_err () 630. to_string () 631. contains ( "Module already registered" ) 632); 633 runtime. execute_script ::<()>( "modify" , "globalThis.original = values.object; values.answer = 0; values.object = {}; globalThis.values = null;" ). unwrap (); 634 runtime. execute_module ( "test:values" ). unwrap (); 635 runtime. execute_script ::<()>( "import" , "import('test:values').then(ns => { globalThis.answer = ns.answer; globalThis.same = ns.object === original; globalThis.namespace = ns; });" ). unwrap (); 636 runtime. run_event_loop (). unwrap (); 637assert_eq! ( 638 runtime. execute_script ::< u32 >( "answer" , "answer" ). unwrap (), 63942 640); 641assert! ( runtime. execute_script ::< bool >( "identity" , "same" ). unwrap ()); 642 runtime 643. execute_script ::<()>( 644"reimport" , 645"import('test:values').then(ns => { globalThis.cached = ns === namespace; });" , 646) 647. unwrap (); 648 runtime. run_event_loop (). unwrap (); 649assert! ( runtime. execute_script ::< bool >( "cache" , "cached" ). unwrap ()); 650assert! ( 651 runtime 652. execute_module ( "missing" ) 653. unwrap_err () 654. to_string () 655. contains ( "No module loader configured" ) 656); 657} 658 659# [ test ] 660fn event_loop_runs_finalization_cleanup_tasks () { 661let mut runtime =Runtime :: new ( RuntimeOptions { 662extensions : vec! [ Extension { 663name : "ext:gc-test" , 664dependencies : & [], 665modules : & [( 666"ext:gc-test/main.js" , 667"import {collect} from 'ext:core/ops'; globalThis.collect = collect;" , 668)], 669entry_point : Some ( "ext:gc-test/main.js" ), 670ops : vec! [ Op :: with_scope ( "collect" , |scope, (): () |{ 671 scope. low_memory_notification (); 672Ok ::< _ , io:: Error >(()) 673})], 674}], 675 ..Default :: default () 676}) 677. unwrap (); 678 runtime 679. execute_script ::<()>( 680"garbage" , 681r#" 682globalThis.cleaned = 0; 683globalThis.registry = new FinalizationRegistry(() => cleaned++); 684(()=>{for(let i=0;i<100;i++)registry.register({},i);})(); 685collect(); 686collect(); 687"# , 688) 689. unwrap (); 690 runtime. run_event_loop (). unwrap (); 691assert! ( 692 runtime 693. execute_script ::< bool >( "cleaned" , "cleaned > 0" ) 694. unwrap () 695); 696} 697 698# [ test ] 699fn custom_stack_hooks_do_not_expose_private_formatter_functions () { 700let mut runtime =Runtime :: new ( RuntimeOptions :: default ()). unwrap (); 701assert! ( runtime. execute_script ::< bool >( "hooks.js" , r#" 702let nested; 703const hook=()=>{nested=new Error('inside hook');return 'custom';}; 704Error.prepareStackTrace=hook; 705new Error('trigger').stack; 706Error.prepareStackTrace=(_,frames)=>frames; 707try { 708const frames=nested.stack; 709if (!frames.length) throw Error('Missing frames'); 710for (const frame of frames) { 711const fn=frame.getFunction(); 712if (fn !== undefined && fn !== hook) throw Error('Private formatter function exposed'); 713} 714} finally {delete Error.prepareStackTrace;} 715true; 716"# ). unwrap ()); 717}