char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
05d50bd
main
1use std:: collections:: HashSet ; 2use std:: rc:: Rc ; 3 4use super ::{ ModuleFormat , ModuleMap , fail, fail_syntax, module_name}; 5use crate :: Error ; 6 7fn define_data < ' s >( 8scope : & mut v8:: PinScope < ' s , ' _ >, 9object : v8:: Local < ' s , v8:: Object >, 10key : & str , 11value : v8:: Local < ' s , v8:: Value >, 12) ->Option <()> { 13let key_local = v8:: String :: new ( scope, key) ?; 14if object. create_data_property ( scope, key_local. into (), value) !=Some ( true ) { 15return fail ( scope, format! ( "Failed to define {key}" )); 16} 17Some (()) 18} 19 20fn require_arg_string ( 21scope : & mut v8:: PinScope , 22args : & v8:: FunctionCallbackArguments , 23) ->Option < String > { 24let value = args. get ( 0 ); 25let string =match v8:: Local ::< v8:: String >:: try_from ( value) { 26Ok ( string) => string, 27Err ( _) =>{ 28let message = v8:: String :: new ( scope, "require() argument must be a string" ) ?; 29 scope. throw_exception ( v8:: Exception :: type_error ( scope, message)); 30return None ; 31} 32}; 33Some ( string. to_rust_string_lossy ( scope)) 34} 35 36pub ( super ) fn strip_preamble ( source : & str ) ->& str { 37let source = source. strip_prefix ( '\u{FEFF}' ). unwrap_or ( source); 38if source. starts_with ( "#!" ) { 39match source. find ( '\n' ) { 40Some ( index) =>& source[ index..], 41None =>"" , 42} 43} else { 44 source 45} 46} 47 48pub ( super ) fn strip_json ( source : & str ) ->& str { 49 source. strip_prefix ( '\u{FEFF}' ). unwrap_or ( source) 50} 51 52fn dirname ( name : & str ) ->String { 53 std:: path:: Path :: new ( name) 54. parent () 55. and_then ( |parent| parent. to_str ()) 56. filter ( |parent| !parent. is_empty ()) 57. unwrap_or ( "." ) 58. to_owned () 59} 60 61fn make_require < ' s >( 62scope : & mut v8:: PinScope < ' s , ' _ >, 63referrer : & str , 64) ->Option < v8:: Local < ' s , v8:: Function >> { 65let referrer_value = v8:: String :: new ( scope, referrer) ?; 66let require = v8:: Function :: builder ( require_call) 67. data ( referrer_value. into ()) 68. length ( 1 ) 69. constructor_behavior ( v8:: ConstructorBehavior :: Throw ) 70. build ( scope) ?; 71let resolve = v8:: Function :: builder ( require_resolve_call) 72. data ( referrer_value. into ()) 73. length ( 1 ) 74. constructor_behavior ( v8:: ConstructorBehavior :: Throw ) 75. build ( scope) ?; 76let require_value: v8:: Local < v8:: Value > = require. into (); 77let require_object = v8:: Local ::< v8:: Object >:: try_from ( require_value). ok () ?; 78let key = v8:: String :: new ( scope, "resolve" ) ?; 79if require_object. create_data_property ( scope, key. into (), resolve. into ()) !=Some ( true ) { 80return None ; 81} 82Some ( require) 83} 84 85fn make_module < ' s >( 86scope : & mut v8:: PinScope < ' s , ' _ >, 87name : & str , 88) ->Option < v8:: Local < ' s , v8:: Object >> { 89let require =make_require ( scope, name) ?; 90let module = v8:: Object :: new ( scope); 91let id = v8:: String :: new ( scope, name) ?; 92define_data ( scope, module, "id" , id. into ()) ?; 93let filename = v8:: String :: new ( scope, name) ?; 94define_data ( scope, module, "filename" , filename. into ()) ?; 95define_data ( 96 scope, 97 module, 98"loaded" , 99 v8:: Boolean :: new ( scope, false ). into (), 100) ?; 101define_data ( scope, module, "exports" , v8:: Object :: new ( scope). into ()) ?; 102define_data ( scope, module, "require" , require. into ()) ?; 103Some ( module) 104} 105 106fn cloned_module ( map : & Rc < ModuleMap >, name : & str ) ->Option < v8:: Global < v8:: Object >> { 107 map. cjs_modules . borrow (). get ( name). cloned () 108} 109 110pub ( super ) fn load_synthetic < ' s >( 111scope : & mut v8:: PinScope < ' s , ' _ >, 112map : & Rc < ModuleMap >, 113loader : & Rc < dyn super :: ModuleLoader >, 114name : & str , 115) ->Option < v8:: Local < ' s , v8:: Module >> { 116let mut exports =vec! [ "default" . to_owned ()]; 117let named =match loader. cjs_named_exports ( name) { 118Ok ( named) => named, 119Err ( Error :: Syntax ( message)) =>return fail_syntax ( scope, message), 120Err ( error) =>return fail ( scope, error. to_string ()), 121}; 122for exportin named{ 123if export !="default" && !exports. contains ( & export) { 124 exports. push ( export); 125} 126} 127let module_name = v8:: String :: new ( scope, name) ?; 128let export_names = exports 129. iter () 130. map ( |export| v8:: String :: new ( scope, export)) 131. collect ::< Option < Vec < _ >>>() ?; 132let module = 133 v8:: Module :: create_synthetic_module ( scope, module_name, & export_names, evaluate_synthetic); 134let hash = module. get_identity_hash (). get (); 135let id = map. insert ( name. into (), v8:: Global :: new ( scope, module), None , hash); 136{ 137let mut records = map. records . borrow_mut (); 138 records[ id. 0 as usize ]. cjs_exports =Some ( exports); 139} 140Some ( v8:: Local :: new ( 141 scope, 142& map. records . borrow ()[ id. 0 as usize ]. module , 143)) 144} 145 146fn evaluate_synthetic < ' s >( 147context : v8:: Local < ' s , v8:: Context >, 148module : v8:: Local < ' s , v8:: Module >, 149) ->Option < v8:: Local < ' s , v8:: Value >> { 150// SAFETY: V8 invokes synthetic evaluation with this context entered. 151 v8:: callback_scope!( unsafe scope, context); 152let name =module_name ( scope, module); 153let map = scope. get_slot ::< Rc < ModuleMap >>(). unwrap (). clone (); 154let loader = map. loader . clone () ?; 155if let Err ( error) = loader. check_access ( & name) { 156return match error{ 157Error :: Syntax ( message) =>fail_syntax ( scope, message), 158 error =>fail ( scope, error. to_string ()), 159}; 160} 161let stored ={ 162let records = map. records . borrow (); 163let id = map. id_of ( & name) ?; 164 records[ id. 0 as usize ]. cjs_exports . clone () 165}; 166let Some ( stored) = storedelse { 167return fail ( scope, format! ( "Missing CommonJS exports for {name}" )); 168}; 169let exports =ensure_exports ( scope, & map, & loader, & name) ?; 170if let Err ( error) = loader. check_access ( & name) { 171return match error{ 172Error :: Syntax ( message) =>fail_syntax ( scope, message), 173 error =>fail ( scope, error. to_string ()), 174}; 175} 176check_closure ( scope, & map, & loader, & name) ?; 177let exports_local = v8:: Local :: new ( scope, & exports); 178let key = v8:: String :: new ( scope, "default" ) ?; 179 module. set_synthetic_module_export ( scope, key, exports_local) ?; 180let is_object = exports_local. is_object () || exports_local. is_function (); 181for exportin stored{ 182if export =="default" { 183continue ; 184} 185let key = v8:: String :: new ( scope, & export) ?; 186let value =if is_object{ 187let object = v8:: Local ::< v8:: Object >:: try_from ( exports_local). ok (); 188 object. and_then ( |object| object. get ( scope, key. into ())) ? 189} else { 190 v8:: undefined ( scope). into () 191}; 192if let Err ( error) = loader. check_access ( & name) { 193return match error{ 194Error :: Syntax ( message) =>fail_syntax ( scope, message), 195 error =>fail ( scope, error. to_string ()), 196}; 197} 198check_closure ( scope, & map, & loader, & name) ?; 199 module. set_synthetic_module_export ( scope, key, value) ?; 200} 201if let Err ( error) = loader. check_access ( & name) { 202return match error{ 203Error :: Syntax ( message) =>fail_syntax ( scope, message), 204 error =>fail ( scope, error. to_string ()), 205}; 206} 207check_closure ( scope, & map, & loader, & name) ?; 208let resolver = v8:: PromiseResolver :: new ( scope) ?; 209 resolver. resolve ( scope, v8:: undefined ( scope). into ()) ?; 210Some ( resolver. get_promise ( scope). into ()) 211} 212 213pub ( super ) fn check_closure ( 214scope : & mut v8:: PinScope , 215map : & Rc < ModuleMap >, 216loader : & Rc < dyn super :: ModuleLoader >, 217root : & str , 218) ->Option <()> { 219let mut visited =HashSet :: new (); 220let mut pending =vec! [ root. to_owned ()]; 221while let Some ( name) = pending. pop () { 222if !visited. insert ( name. clone ()) { 223continue ; 224} 225// Prior ESM semantics skip rechecking already-evaluated ESM deps; 226// only CJS/JSON closures (which share mutable exports) are rechecked 227// so cached ESM wrappers cannot bypass a later pledge via lazy deps. 228let is_cjs ={ 229 map. cjs_modules . borrow (). contains_key ( name. as_str ()) 230 || map. json_modules . borrow (). contains_key ( name. as_str ()) 231 || map. cjs_deps . borrow (). contains_key ( name. as_str ()) 232}; 233if is_cjs &&let Err ( error) = loader. check_access ( & name) { 234return match error{ 235Error :: Syntax ( message) =>fail_syntax ( scope, message), 236 error =>fail ( scope, error. to_string ()), 237}; 238} 239 pending. extend ( 240 map. cjs_deps 241. borrow () 242. get ( name. as_str ()) 243. cloned () 244. unwrap_or_default (), 245); 246} 247Some (()) 248} 249 250fn record_edge ( map : & Rc < ModuleMap >, parent : & str , child : & str ) { 251if parent == child{ 252return ; 253} 254let mut deps = map. cjs_deps . borrow_mut (); 255let entry = deps. entry ( parent. into ()). or_default (); 256if !entry. iter (). any ( |existing| existing == child) { 257 entry. push ( child. to_owned ()); 258} 259} 260 261fn require_call ( 262scope : & mut v8:: PinScope , 263args : v8:: FunctionCallbackArguments , 264mut result : v8:: ReturnValue , 265) { 266let referrer = args. data (). to_rust_string_lossy ( scope); 267let Some ( specifier) =require_arg_string ( scope, & args) else { 268return ; 269}; 270if let Some ( value) =require_module ( scope, & specifier, & referrer) { 271 result. set ( value); 272} 273} 274 275fn require_resolve_call ( 276scope : & mut v8:: PinScope , 277args : v8:: FunctionCallbackArguments , 278mut result : v8:: ReturnValue , 279) { 280let referrer = args. data (). to_rust_string_lossy ( scope); 281let Some ( specifier) =require_arg_string ( scope, & args) else { 282return ; 283}; 284let map = scope. get_slot ::< Rc < ModuleMap >>(). unwrap (). clone (); 285let Some ( loader) = map. loader . clone () else { 286fail ::<()>( scope, "No module loader configured" ); 287return ; 288}; 289if specifier. starts_with ( "ext:" ) { 290fail ::<()>( scope, "Extension modules are private" ); 291return ; 292} 293if let Err ( error) = loader. check_access ( & referrer) { 294match error{ 295Error :: Syntax ( message) =>fail_syntax ( scope, message), 296 error =>fail ::<()>( scope, error. to_string ()), 297}; 298return ; 299} 300let name =match loader. require_resolve ( & specifier, & referrer) { 301Ok ( name) => name, 302Err ( Error :: Syntax ( message)) =>{ 303fail_syntax ::<()>( scope, message); 304return ; 305} 306Err ( error) =>{ 307fail ::<()>( scope, error. to_string ()); 308return ; 309} 310}; 311if name. starts_with ( "ext:" ) { 312fail ::<()>( scope, "Extension modules are private" ); 313return ; 314} 315if let Err ( error) = loader. check_access ( & name) { 316match error{ 317Error :: Syntax ( message) =>fail_syntax ::<()>( scope, message), 318 error =>fail ::<()>( scope, error. to_string ()), 319}; 320return ; 321} 322if let Some ( value) = v8:: String :: new ( scope, & name) { 323 result. set ( value. into ()); 324} 325} 326 327fn require_module < ' s >( 328scope : & mut v8:: PinScope < ' s , ' _ >, 329specifier : & str , 330referrer : & str , 331) ->Option < v8:: Local < ' s , v8:: Value >> { 332let map = scope. get_slot ::< Rc < ModuleMap >>(). unwrap (). clone (); 333let loader = map. loader . clone () ?; 334if specifier. starts_with ( "ext:" ) { 335return fail ( scope, "Extension modules are private" ); 336} 337if let Err ( error) = loader. check_access ( referrer) { 338return match error{ 339Error :: Syntax ( message) =>fail_syntax ( scope, message), 340 error =>fail ( scope, error. to_string ()), 341}; 342} 343let name =match loader. require_resolve ( specifier, referrer) { 344Ok ( name) => name, 345Err ( Error :: Syntax ( message)) =>return fail_syntax ( scope, message), 346Err ( error) =>return fail ( scope, error. to_string ()), 347}; 348if name. starts_with ( "ext:" ) { 349return fail ( scope, "Extension modules are private" ); 350} 351if let Err ( error) = loader. check_access ( & name) { 352return match error{ 353Error :: Syntax ( message) =>fail_syntax ( scope, message), 354 error =>fail ( scope, error. to_string ()), 355}; 356} 357let format =match loader. module_format ( & name) { 358Ok ( format) => format, 359Err ( Error :: Syntax ( message)) =>return fail_syntax ( scope, message), 360Err ( error) =>return fail ( scope, error. to_string ()), 361}; 362match format{ 363ModuleFormat :: Esm =>fail ( 364 scope, 365format! ( "require() of ESM {name} is not supported, use import() instead" ), 366), 367ModuleFormat :: Json =>{ 368let value =require_json ( scope, & map, & loader, & name) ?; 369if let Err ( error) = loader. check_access ( referrer) { 370return match error{ 371Error :: Syntax ( message) =>fail_syntax ( scope, message), 372 error =>fail ( scope, error. to_string ()), 373}; 374} 375if let Err ( error) = loader. check_access ( & name) { 376return match error{ 377Error :: Syntax ( message) =>fail_syntax ( scope, message), 378 error =>fail ( scope, error. to_string ()), 379}; 380} 381check_closure ( scope, & map, & loader, & name) ?; 382record_edge ( & map, referrer, & name); 383Some ( value) 384} 385ModuleFormat :: CommonJs =>{ 386let value =ensure_exports ( scope, & map, & loader, & name) ?; 387if let Err ( error) = loader. check_access ( referrer) { 388return match error{ 389Error :: Syntax ( message) =>fail_syntax ( scope, message), 390 error =>fail ( scope, error. to_string ()), 391}; 392} 393if let Err ( error) = loader. check_access ( & name) { 394return match error{ 395Error :: Syntax ( message) =>fail_syntax ( scope, message), 396 error =>fail ( scope, error. to_string ()), 397}; 398} 399check_closure ( scope, & map, & loader, & name) ?; 400record_edge ( & map, referrer, & name); 401Some ( v8:: Local :: new ( scope, & value)) 402} 403} 404} 405 406fn require_json < ' s >( 407scope : & mut v8:: PinScope < ' s , ' _ >, 408map : & Rc < ModuleMap >, 409loader : & Rc < dyn super :: ModuleLoader >, 410name : & str , 411) ->Option < v8:: Local < ' s , v8:: Value >> { 412let cached ={ map. json_modules . borrow (). get ( name). cloned () }; 413if let Some ( cached) = cached{ 414return Some ( v8:: Local :: new ( scope, & cached)); 415} 416let source =match loader. load ( name) { 417Ok ( source) => source, 418Err ( Error :: Syntax ( message)) =>return fail_syntax ( scope, message), 419Err ( error) =>return fail ( scope, error. to_string ()), 420}; 421let stripped =strip_json ( & source); 422let text = v8:: String :: new ( scope, stripped) ?; 423let value = v8:: json:: parse ( scope, text) ?; 424{ 425 map. json_modules 426. borrow_mut () 427. insert ( name. into (), v8:: Global :: new ( scope, value)); 428} 429Some ( value) 430} 431 432fn ensure_exports < ' s >( 433scope : & mut v8:: PinScope < ' s , ' _ >, 434map : & Rc < ModuleMap >, 435loader : & Rc < dyn super :: ModuleLoader >, 436name : & str , 437) ->Option < v8:: Global < v8:: Value >> { 438if let Some ( module) =cloned_module ( map, name) { 439check_closure ( scope, map, loader, name) ?; 440let module = v8:: Local :: new ( scope, & module); 441let key = v8:: String :: new ( scope, "exports" ) ?; 442let current = module. get ( scope, key. into ()) ?; 443return Some ( v8:: Global :: new ( scope, current)); 444} 445execute ( scope, map, loader, name) ?; 446check_closure ( scope, map, loader, name) ?; 447let module =cloned_module ( map, name) ?; 448let module = v8:: Local :: new ( scope, & module); 449let key = v8:: String :: new ( scope, "exports" ) ?; 450let current = module. get ( scope, key. into ()) ?; 451Some ( v8:: Global :: new ( scope, current)) 452} 453 454fn execute ( 455scope : & mut v8:: PinScope , 456map : & Rc < ModuleMap >, 457loader : & Rc < dyn super :: ModuleLoader >, 458name : & str , 459) ->Option <()> { 460let ( module_global, exports_global, require_global) ={ 461let module =make_module ( scope, name) ?; 462let exports_key = v8:: String :: new ( scope, "exports" ) ?; 463let exports = module. get ( scope, exports_key. into ()) ?; 464let require_key = v8:: String :: new ( scope, "require" ) ?; 465let require = module. get ( scope, require_key. into ()) ?; 466( 467 v8:: Global :: new ( scope, module), 468 v8:: Global :: new ( scope, exports), 469 v8:: Global :: new ( scope, require), 470) 471}; 472{ 473 map. cjs_modules 474. borrow_mut () 475. insert ( name. into (), module_global. clone ()); 476 map. cjs_deps . borrow_mut (). insert ( name. into (), Vec :: new ()); 477} 478let remove = |map : & Rc < ModuleMap > |{ 479 map. cjs_modules . borrow_mut (). remove ( name); 480 map. cjs_deps . borrow_mut (). remove ( name); 481}; 482let source =match loader. load ( name) { 483Ok ( source) => source, 484Err ( Error :: Syntax ( message)) =>{ 485remove ( map); 486return fail_syntax ( scope, message); 487} 488Err ( error) =>{ 489remove ( map); 490return fail ( scope, error. to_string ()); 491} 492}; 493if let Err ( error) = loader. check_access ( name) { 494remove ( map); 495return match error{ 496Error :: Syntax ( message) =>fail_syntax ( scope, message), 497 error =>fail ( scope, error. to_string ()), 498}; 499} 500let stripped =strip_preamble ( & source). to_owned (); 501let params =[ "exports" , "require" , "module" , "__filename" , "__dirname" ] 502. iter () 503. map ( |name| v8:: String :: new ( scope, name)) 504. collect ::< Option < Vec < _ >>>() ?; 505let mut source_ref =crate :: source ( scope, name, & stripped, false , None ); 506let function =match v8:: script_compiler:: compile_function ( 507 scope, 508& mut source_ref, 509& params, 510& [], 511 v8:: script_compiler:: CompileOptions :: NoCompileOptions , 512 v8:: script_compiler:: NoCacheReason :: NoReason , 513) { 514Some ( function) => function, 515None =>{ 516remove ( map); 517return None ; 518} 519}; 520let ( exports, require, module, filename, dirname) ={ 521let exports = v8:: Local :: new ( scope, & exports_global); 522let require = v8:: Local :: new ( scope, & require_global); 523let module = v8:: Local :: new ( scope, & module_global); 524let filename = v8:: String :: new ( scope, name) ?; 525let dirname = v8:: String :: new ( scope, & dirname ( name)) ?; 526( exports, require, module, filename, dirname) 527}; 528let args =[ 529 exports, 530 require, 531 module. into (), 532 filename. into (), 533 dirname. into (), 534]; 535let receiver: v8:: Local < v8:: Value > = exports; 536if function. call ( scope, receiver, & args). is_none () { 537remove ( map); 538return None ; 539} 540{ 541let module = v8:: Local :: new ( scope, & module_global); 542let key = v8:: String :: new ( scope, "loaded" ) ?; 543match module. create_data_property ( scope, key. into (), v8:: Boolean :: new ( scope, true ). into ()) { 544Some ( true ) =>{} 545Some ( false ) =>{ 546remove ( map); 547return fail ( scope, format! ( "Failed to mark {name} loaded" )); 548} 549None =>{ 550remove ( map); 551return None ; 552} 553} 554} 555if let Err ( error) = loader. check_access ( name) { 556return match error{ 557Error :: Syntax ( message) =>fail_syntax ( scope, message), 558 error =>fail ( scope, error. to_string ()), 559}; 560} 561Some (()) 562}