char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
d71572b
main
1use std:: cell:: RefCell ; 2use std:: collections:: HashMap ; 3use std:: rc:: Rc ; 4 5use crate ::{ Error , Extension }; 6use v8:: MapFnTo ; 7 8type Exports =Vec <( String , v8:: Global < v8:: Value >)>; 9 10struct Modules { 11sources : HashMap < &' static str , &' static str >, 12builtins : HashMap < String , Exports >, 13modules : RefCell < HashMap < String , v8:: Global < v8:: Module >>>, 14// Identity hashes collide, so each bucket holds candidates confirmed by Local == Global. 15by_identity : RefCell < HashMap < i32 , Vec < String >>>, 16origin : v8:: Global < v8:: PrimitiveArray >, 17} 18 19fn valid_name ( name : & str ) ->bool { 20 name. starts_with ( "ext:" ) 21 && name !="ext:core" 22 && name. len () >4 23 && name[ 4 ..] 24. bytes () 25. all ( |b| b. is_ascii_alphanumeric () || b ==b'_' || b ==b'-' ) 26} 27 28# [ derive ( Clone , Copy , PartialEq )] 29enum Mark { 30Unvisited , 31Visiting , 32Done , 33} 34 35fn append_extension_in_dependency_order ( 36index : usize , 37extensions : & [ Extension ], 38indices : & HashMap < &' static str , usize >, 39marks : & mut [ Mark ], 40stack : & mut Vec < usize >, 41order : & mut Vec < usize >, 42) ->Result <(), Error > { 43match marks[ index] { 44Mark :: Done =>return Ok (()), 45Mark :: Visiting =>{ 46let start = stack. iter (). position ( |item|* item == index). unwrap (); 47let mut cycle = stack[ start..] 48. iter () 49. map ( |item| extensions[ * item]. name ) 50. collect ::< Vec < _ >>(); 51 cycle. push ( extensions[ index]. name ); 52return Err ( Error :: Module ( format! ( 53"Extension dependency cycle: {}" , 54 cycle. join ( " -> " ) 55))); 56} 57Mark :: Unvisited =>{} 58} 59 marks[ index] =Mark :: Visiting ; 60 stack. push ( index); 61for dependencyin extensions[ index]. dependencies { 62if !valid_name ( dependency) { 63return Err ( Error :: Module ( format! ( 64"Invalid extension dependency: {dependency}" 65))); 66} 67let Some ( dependency) = indices. get ( dependency) else { 68return Err ( Error :: Module ( format! ( 69"Extension {} depends on missing extension {dependency}" , 70 extensions[ index]. name 71))); 72}; 73append_extension_in_dependency_order ( 74* dependency, 75 extensions, 76 indices, 77 marks, 78 stack, 79 order, 80) ?; 81} 82 stack. pop (); 83 marks[ index] =Mark :: Done ; 84 order. push ( index); 85Ok (()) 86} 87 88pub ( crate ) fn validate ( extensions : & [ Extension ]) ->Result < Vec < usize >, Error > { 89let mut names = std:: collections:: HashSet :: new (); 90let mut sources = std:: collections:: HashSet :: new (); 91for extensionin extensions{ 92let name = extension. name ; 93if !valid_name ( name) { 94return Err ( Error :: Module ( format! ( "Invalid extension name: {name}" ))); 95} 96if !names. insert ( name) { 97return Err ( Error :: Module ( format! ( "Duplicate extension: {name}" ))); 98} 99for & ( specifier, _) in extension. modules { 100if ( specifier != name && !specifier. starts_with ( & format! ( "{name}/" ))) 101 ||normalize ( specifier). as_deref () !=Some ( specifier) 102{ 103return Err ( Error :: Module ( format! ( 104"Invalid extension module: {specifier}" 105))); 106} 107if !sources. insert ( specifier) { 108return Err ( Error :: Module ( format! ( 109"Duplicate extension module: {specifier}" 110))); 111} 112} 113if let Some ( entry) = extension. entry_point 114 && !extension. modules . iter (). any ( |( name, _) |* name == entry) 115{ 116return Err ( Error :: Module ( format! ( 117"Missing extension entry point: {entry}" 118))); 119} 120} 121let indices = extensions 122. iter () 123. enumerate () 124. map ( |( index, extension) |( extension. name , index)) 125. collect ::< HashMap < _ , _ >>(); 126let mut marks =vec! [ Mark :: Unvisited ; extensions. len ()]; 127let mut stack =Vec :: new (); 128let mut order =Vec :: with_capacity ( extensions. len ()); 129for indexin 0 ..extensions. len () { 130append_extension_in_dependency_order ( 131 index, extensions, & indices, & mut marks, & mut stack, & mut order, 132) ?; 133} 134Ok ( order) 135} 136 137pub ( crate ) fn clear ( isolate : & mut v8:: Isolate ) { 138 isolate. remove_slot ::< Rc < Modules >>(); 139} 140 141pub ( crate ) const SOURCES : & [( & str , & str )] =& [ 142( "ext:core/promises" , include_str! ( "promises.js" )), 143( "ext:core/bootstrap" , include_str! ( "bootstrap.js" )), 144( 145"ext:core/shared_array_buffer" , 146include_str! ( "shared_array_buffer.js" ), 147), 148( "ext:core/stack_trace.js" , include_str! ( "stack_trace.js" )), 149]; 150 151type NativeModule =( &' static str , Vec <( &' static str , v8:: FunctionCallback )>); 152 153pub ( crate ) fn native_modules () ->Vec < NativeModule > { 154vec! [ 155( 156"ext:core/tasks" , 157vec! [( "enqueueTask" , crate :: tasks:: enqueue. map_fn_to ())], 158), 159( 160"ext:core/microtasks" , 161vec! [( "enqueueMicrotask" , crate :: microtasks:: enqueue. map_fn_to ())], 162), 163( 164"ext:core/promise_hooks" , 165vec! [ 166( 167"markPromiseHandled" , 168crate :: op:: mark_promise_handled. map_fn_to (), 169), 170( 171"performPromiseThen" , 172crate :: op:: perform_promise_then. map_fn_to (), 173), 174( "promiseState" , crate :: op:: promise_state. map_fn_to ()), 175], 176), 177( 178"ext:core/event_hooks" , 179vec! [ 180( 181"setEventHandlers" , 182crate :: reporting:: set_event_handlers. map_fn_to (), 183), 184( "isPromise" , crate :: reporting:: is_promise. map_fn_to ()), 185( 186"reportNonfatalException" , 187crate :: reporting:: report_nonfatal_exception. map_fn_to (), 188), 189], 190), 191( 192"ext:core/structured_clone" , 193vec! [( "clone" , crate :: structured_clone:: clone. map_fn_to ())], 194), 195( 196"ext:core/source_map" , 197vec! [( 198"mapSourceLocation" , 199crate :: modules:: map_source_location_op. map_fn_to (), 200)], 201), 202] 203} 204 205pub ( crate ) fn external_references () ->Vec < v8:: ExternalReference > { 206let evaluate: v8:: SyntheticModuleEvaluationSteps = evaluate_builtin. map_fn_to (); 207let mut references =vec! [ v8:: ExternalReference { 208pointer : evaluateas * mut std:: ffi:: c_void , 209}]; 210for ( _, exports) in native_modules () { 211 references. extend ( 212 exports 213. into_iter () 214. map ( |( _, function) | v8:: ExternalReference { function}), 215); 216} 217 references 218} 219 220pub ( crate ) fn init ( 221scope : & mut v8:: PinScope , 222sources : HashMap < &' static str , &' static str >, 223primordials : v8:: Local < v8:: Object >, 224ops : v8:: Local < v8:: Object >, 225) ->Option <()> { 226let exports = |scope : & mut v8:: PinScope , object : v8:: Local < v8:: Object > | ->Option < Exports > { 227let keys = object. get_own_property_names ( scope, Default :: default ()) ?; 228let mut exports =Vec :: new (); 229for indexin 0 ..keys. length () { 230let key = keys. get_index ( scope, index) ?; 231let value = object. get ( scope, key) ?; 232 exports. push (( 233 key. to_rust_string_lossy ( scope), 234 v8:: Global :: new ( scope, value), 235)); 236} 237Some ( exports) 238}; 239let mut builtins =HashMap :: new (); 240 builtins. insert ( "ext:core/primordials" . into (), exports ( scope, primordials) ?); 241 builtins. insert ( "ext:core/ops" . into (), exports ( scope, ops) ?); 242 builtins. insert ( 243"ext:core" . into (), 244vec! [ 245( 246"primordials" . into (), 247 v8:: Global :: new ( scope, v8:: Local ::< v8:: Value >:: from ( primordials)), 248), 249( 250"ops" . into (), 251 v8:: Global :: new ( scope, v8:: Local ::< v8:: Value >:: from ( ops)), 252), 253], 254); 255for ( module, functions) in native_modules () { 256let mut exports =Vec :: new (); 257for ( name, callback) in functions{ 258let function = v8:: Function :: builder_raw ( callback) 259. constructor_behavior ( v8:: ConstructorBehavior :: Throw ) 260. build ( scope) ?; 261 exports. push (( 262 name. into (), 263 v8:: Global :: new ( scope, v8:: Local ::< v8:: Value >:: from ( function)), 264)); 265} 266 builtins. insert ( module. into (), exports); 267} 268let origin = v8:: PrimitiveArray :: new ( scope, 1 ); 269let token = v8:: Symbol :: new ( scope, None ); 270 origin. set ( scope, 0 , token. into ()); 271let origin = v8:: Global :: new ( scope, origin); 272 scope. set_slot ( Rc :: new ( Modules { 273 sources, 274 builtins, 275modules : RefCell :: new ( HashMap :: new ()), 276by_identity : RefCell :: new ( HashMap :: new ()), 277 origin, 278})); 279Some (()) 280} 281 282# [ derive ( Clone , serde :: Serialize , serde :: Deserialize )] 283pub ( crate ) struct SnapshotState { 284origin : usize , 285builtins : std:: collections:: BTreeMap < String , Vec <( String , usize )>>, 286modules : std:: collections:: BTreeMap < String , usize >, 287} 288 289/// Records handles in name order: context data indices are part of the artifact bytes. 290pub ( crate ) fn capture ( scope : & mut v8:: PinScope ) ->SnapshotState { 291let map = scope. get_slot ::< Rc < Modules >>(). unwrap (). clone (); 292let context = scope. get_current_context (); 293let marker = v8:: Local :: new ( scope, & map. origin ). get ( scope, 0 ); 294let origin = scope. add_context_data ( context, marker); 295let mut builtins = std:: collections:: BTreeMap :: new (); 296for ( name, exports) in map 297. builtins 298. iter () 299. collect ::< std:: collections:: BTreeMap < _ , _ >>() 300{ 301let exports = exports 302. iter () 303. map ( |( key, value) |{ 304let value = v8:: Local :: new ( scope, value); 305( key. clone (), scope. add_context_data ( context, value)) 306}) 307. collect (); 308 builtins. insert ( name. clone (), exports); 309} 310let modules = map 311. modules 312. borrow () 313. iter () 314. map ( |( name, module) |( name. clone (), module. clone ())) 315. collect ::< std:: collections:: BTreeMap < _ , _ >>() 316. into_iter () 317. map ( |( name, module) |{ 318let module = v8:: Local :: new ( scope, module); 319( name, scope. add_context_data ( context, module)) 320}) 321. collect (); 322SnapshotState { origin, builtins, modules} 323} 324 325pub ( crate ) fn restore ( 326scope : & mut v8:: PinScope , 327sources : HashMap < &' static str , &' static str >, 328state : & SnapshotState , 329) ->Result <(), crate :: SnapshotError > { 330let marker = scope. get_context_data_from_snapshot_once ::< v8:: Symbol >( state. origin ) ?; 331let origin = v8:: PrimitiveArray :: new ( scope, 1 ); 332 origin. set ( scope, 0 , marker. into ()); 333let origin = v8:: Global :: new ( scope, origin); 334let mut builtins =HashMap :: new (); 335for ( name, exports) in & state. builtins { 336let mut values =Vec :: new (); 337for ( key, index) in exports{ 338let value = scope. get_context_data_from_snapshot_once ::< v8:: Value >( * index) ?; 339 values. push (( key. clone (), v8:: Global :: new ( scope, value))); 340} 341 builtins. insert ( name. clone (), values); 342} 343let mut modules =HashMap :: new (); 344let mut by_identity: HashMap < i32 , Vec < String >> =HashMap :: new (); 345for ( name, & index) in & state. modules { 346let module = scope. get_context_data_from_snapshot_once ::< v8:: Module >( index) ?; 347// Identity hashes belong to the restored heap, not the snapshot-producing isolate. 348 by_identity 349. entry ( module. get_identity_hash (). get ()) 350. or_default () 351. push ( name. clone ()); 352 modules. insert ( name. clone (), v8:: Global :: new ( scope, module)); 353} 354 scope. set_slot ( Rc :: new ( Modules { 355 sources, 356 builtins, 357modules : RefCell :: new ( modules), 358by_identity : RefCell :: new ( by_identity), 359 origin, 360})); 361Ok (()) 362} 363 364pub ( crate ) fn dynamic_imports_disabled ( 365scope : & mut v8:: PinScope , 366origin : v8:: Local < v8:: Data >, 367) ->bool { 368let Some ( map) = scope. get_slot ::< Rc < Modules >>(). cloned () else { 369return false ; 370}; 371let Ok ( options) = v8:: Local ::< v8:: FixedArray >:: try_from ( origin) else { 372return false ; 373}; 374if options. length () !=1 { 375return false ; 376} 377// Snapshot restoration can copy the options array; the private symbol retains identity. 378let marker = v8:: Local :: new ( scope, & map. origin ). get ( scope, 0 ); 379 options 380. get ( scope, 0 ) 381. and_then ( |token| v8:: Local ::< v8:: Value >:: try_from ( token). ok ()) 382. is_some_and ( |token| token. strict_equals ( marker. into ())) 383} 384 385pub ( crate ) fn module_name ( 386scope : & mut v8:: PinScope , 387module : v8:: Local < v8:: Module >, 388) ->Option < String > { 389let map = scope. get_slot ::< Rc < Modules >>() ?; 390let hash = module. get_identity_hash (). get (); 391let by_identity = map. by_identity . borrow (); 392let modules = map. modules . borrow (); 393for namein by_identity. get ( & hash). into_iter (). flatten () { 394if let Some ( global) = modules. get ( name) 395 && v8:: Local :: new ( scope, global) == module 396{ 397return Some ( name. clone ()); 398} 399} 400None 401} 402 403fn normalize ( name : & str ) ->Option < String > { 404let ( root, path) = name. split_once ( '/' ). unwrap_or (( name, "" )); 405if !root. starts_with ( "ext:" ) || root. len () ==4 || name. contains ([ '?' , '#' , '\\' , '%' ]) { 406return None ; 407} 408let mut components =Vec :: new (); 409if !path. is_empty () { 410for partin path. split ( '/' ) { 411match part{ 412"" =>return None , 413"." =>{} 414".." =>{ 415 components. pop () ?; 416} 417 part => components. push ( part), 418} 419} 420} 421if components. is_empty () { 422Some ( root. into ()) 423} else { 424Some ( format! ( "{root}/{}" , components. join ( "/" ))) 425} 426} 427 428fn fail < T >( scope : & mut v8:: PinScope , message : impl AsRef < str >) ->Option < T > { 429let message = v8:: String :: new ( scope, message. as_ref ()) ?; 430let error = v8:: Exception :: error ( scope, message); 431 scope. throw_exception ( error); 432None 433} 434 435fn load < ' s >( scope : & mut v8:: PinScope < ' s , ' _ >, name : & str ) ->Option < v8:: Local < ' s , v8:: Module >> { 436let map = scope. get_slot ::< Rc < Modules >>(). unwrap (). clone (); 437{ 438let modules = map. modules . borrow (); 439if let Some ( cached) = modules. get ( name) { 440return Some ( v8:: Local :: new ( scope, cached)); 441} 442} 443let module =if let Some ( exports) = map. builtins . get ( name) { 444let name = v8:: String :: new ( scope, name) ?; 445let keys = exports 446. iter () 447. map ( |( name, _) | v8:: String :: new ( scope, name)) 448. collect ::< Option < Vec < _ >>>() ?; 449 v8:: Module :: create_synthetic_module ( scope, name, & keys, evaluate_builtin) 450} else { 451let Some ( code) = map. sources . get ( name) else { 452return fail ( scope, format! ( "Unknown extension module: {name}" )); 453}; 454let origin = v8:: Local :: new ( scope, & map. origin ); 455let mut source =crate :: source ( scope, name, code, true , Some ( origin. into ())); 456 v8:: script_compiler:: compile_module ( scope, & mut source) ? 457}; 458 map. modules 459. borrow_mut () 460. insert ( name. to_owned (), v8:: Global :: new ( scope, module)); 461let hash = module. get_identity_hash (). get (); 462let mut by_identity = map. by_identity . borrow_mut (); 463let bucket = by_identity. entry ( hash). or_default (); 464if !bucket. iter (). any ( |candidate| candidate == name) { 465 bucket. push ( name. to_owned ()); 466} 467Some ( module) 468} 469 470fn evaluate_builtin < ' s >( 471context : v8:: Local < ' s , v8:: Context >, 472module : v8:: Local < ' s , v8:: Module >, 473) ->Option < v8:: Local < ' s , v8:: Value >> { 474// SAFETY: V8 supplies this module's entered context. 475 v8:: callback_scope!( unsafe scope, context); 476let name =module_name ( scope, module) ?; 477let map = scope. get_slot ::< Rc < Modules >>(). unwrap (). clone (); 478for ( key, value) in & map. builtins [ &* name] { 479let key = v8:: String :: new ( scope, key) ?; 480let value = v8:: Local :: new ( scope, value); 481 module. set_synthetic_module_export ( scope, key, value) ?; 482} 483let resolver = v8:: PromiseResolver :: new ( scope) ?; 484 resolver. resolve ( scope, v8:: undefined ( scope). into ()) ?; 485Some ( resolver. get_promise ( scope). into ()) 486} 487 488fn resolve < ' s >( 489context : v8:: Local < ' s , v8:: Context >, 490specifier : v8:: Local < ' s , v8:: String >, 491attributes : v8:: Local < ' s , v8:: FixedArray >, 492referrer : v8:: Local < ' s , v8:: Module >, 493) ->Option < v8:: Local < ' s , v8:: Module >> { 494// Trust module identity, never the origin string supplied by a guest loader. 495// SAFETY: V8 invokes module resolution with the supplied context entered. 496 v8:: callback_scope!( unsafe scope, context); 497let Some ( referrer) =module_name ( scope, referrer) else { 498return fail ( scope, "Not an extension module" ); 499}; 500if attributes. length () !=0 { 501return fail ( scope, "not yet implemented: extension import attributes" ); 502} 503let specifier = specifier. to_rust_string_lossy ( scope); 504let name =resolve_name ( scope, & specifier, & referrer) ?; 505load ( scope, & name) 506} 507 508pub ( crate ) fn resolve_name ( 509scope : & mut v8:: PinScope , 510specifier : & str , 511referrer : & str , 512) ->Option < String > { 513let candidate =if specifier. starts_with ( "ext:" ) { 514 specifier. to_owned () 515} else if specifier. starts_with ( "./" ) || specifier. starts_with ( "../" ) { 516let Some (( parent, _)) = referrer. rsplit_once ( '/' ) else { 517return fail ( scope, "Relative import from an extension package root" ); 518}; 519format! ( "{parent}/{specifier}" ) 520} else { 521return fail ( 522 scope, 523"Extensions can only import embedded extension modules" , 524); 525}; 526let Some ( name) =normalize ( & candidate) else { 527return fail ( scope, format! ( "Invalid extension import: {candidate}" )); 528}; 529Some ( name) 530} 531 532pub ( crate ) fn execute < ' s >( 533scope : & mut v8:: PinScope < ' s , ' _ >, 534entry : & str , 535) ->Option < v8:: Local < ' s , v8:: Module >> { 536let module =load ( scope, entry) ?; 537if module. get_status () == v8:: ModuleStatus :: Uninstantiated 538 && module. instantiate_module ( scope, resolve) !=Some ( true ) 539{ 540return None ; 541} 542let evaluation = module. evaluate ( scope) ?; 543let promise = v8:: Local ::< v8:: Promise >:: try_from ( evaluation). ok () ?; 544 promise. mark_as_handled (); 545crate :: op:: run_event_loop ( scope, Some ( promise), false ) ?; 546Some ( module) 547}