1use std::collections::BTreeMap;
2use std::marker::PhantomData;
3
4use bamts_bytecode::{EcmaString, EcmaStringBuilder};
5use bamts_native::{Decoded, Value};
6
7use crate::{EvalFailure, HeapEntry, Host, Machine, NativeCallable, PropertyMap, ThrowOrigin};
8
9#[path = "builtins/mod.rs"]
10pub(crate) mod builtins;
11
12#[path = "regexp.rs"]
13mod regexp;
14
15#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
16pub(crate) struct BuiltinId(usize);
17
18#[derive(Clone, Debug)]
19pub(crate) enum BuiltinOutcome {
20 Value(Value),
21 Call {
22 callee: Value,
23 this_value: Value,
24 arguments: Vec<Value>,
25 },
26 ConstructCall {
27 callee: Value,
28 this_value: Value,
29 arguments: Vec<Value>,
30 prototype: Value,
31 },
32 GeneratorNext {
33 generator: Value,
34 resume_value: Value,
35 },
36}
37
38pub(crate) type BuiltinHandler<H> = fn(
39 &mut Machine<'_, H>,
40 this: Value,
41 args: &[Value],
42 constructing: bool,
43) -> Result<BuiltinOutcome, EvalFailure>;
44
45#[derive(Clone, Copy)]
46pub(crate) struct BuiltinDef<H: Host> {
47 pub(crate) name: &'static str,
48 pub(crate) length: u32,
49 pub(crate) handler: BuiltinHandler<H>,
50}
51
52pub(crate) struct BuiltinTable<H: Host> {
53 defs: Vec<BuiltinDef<H>>,
54 object_prototype: Value,
55 function_prototype: Value,
56 array_prototype: Value,
57 string_prototype: Value,
58 number_prototype: Value,
59 boolean_prototype: Value,
60 error_prototypes: Vec<(BuiltinId, Value)>,
61 symbol_iterator: Option<Value>,
62 symbol_to_string_tag: Option<Value>,
63 symbol_prototype: Option<Value>,
64 object_to_string: Option<Value>,
65 regexp_prototype: Option<Value>,
66 iterator_prototype: Option<Value>,
67 generator_prototype: Option<Value>,
68 promise_resolver_targets: Option<(Value, Value)>,
69 promise_finally_targets: Option<(Value, Value)>,
70 promise_all_targets: Option<(Value, Value)>,
71 promise_prototype: Option<Value>,
72 marker: PhantomData<fn() -> H>,
73}
74
75impl<H: Host> BuiltinTable<H> {
76 fn new(
77 object_prototype: Value,
78 function_prototype: Value,
79 array_prototype: Value,
80 string_prototype: Value,
81 number_prototype: Value,
82 boolean_prototype: Value,
83 ) -> Self {
84 Self {
85 defs: Vec::new(),
86 object_prototype,
87 function_prototype,
88 array_prototype,
89 string_prototype,
90 number_prototype,
91 boolean_prototype,
92 error_prototypes: Vec::new(),
93 symbol_iterator: None,
94 symbol_to_string_tag: None,
95 symbol_prototype: None,
96 object_to_string: None,
97 regexp_prototype: None,
98 iterator_prototype: None,
99 generator_prototype: None,
100 promise_resolver_targets: None,
101 promise_finally_targets: None,
102 promise_all_targets: None,
103 promise_prototype: None,
104 marker: PhantomData,
105 }
106 }
107
108 pub(crate) fn register(&mut self, def: BuiltinDef<H>) -> BuiltinId {
109 let id = BuiltinId(self.defs.len());
110 self.defs.push(def);
111 id
112 }
113
114 pub(crate) fn get(&self, id: BuiltinId) -> &BuiltinDef<H> {
115 self.defs
116 .get(id.0)
117 .expect("BuiltinId is minted by this realm's table")
118 }
119
120 pub(crate) fn object_prototype(&self) -> Value {
121 self.object_prototype
122 }
123
124 pub(crate) fn function_prototype(&self) -> Value {
125 self.function_prototype
126 }
127
128 pub(crate) fn array_prototype(&self) -> Value {
129 self.array_prototype
130 }
131
132 pub(crate) fn string_prototype(&self) -> Value {
133 self.string_prototype
134 }
135
136 pub(crate) fn number_prototype(&self) -> Value {
137 self.number_prototype
138 }
139
140 pub(crate) fn boolean_prototype(&self) -> Value {
141 self.boolean_prototype
142 }
143
144 pub(crate) fn set_symbol_iterator(&mut self, iterator: Value) {
145 self.symbol_iterator = Some(iterator);
146 }
147
148 pub(crate) fn symbol_iterator(&self) -> Value {
149 self.symbol_iterator.expect("Symbol builtins install first")
150 }
151
152 pub(crate) fn set_symbol_to_string_tag(&mut self, symbol: Value) {
153 self.symbol_to_string_tag = Some(symbol);
154 }
155 pub(crate) fn set_symbol_prototype(&mut self, prototype: Value) {
156 self.symbol_prototype = Some(prototype);
157 }
158
159 pub(crate) fn symbol_prototype(&self) -> Value {
160 self.symbol_prototype
161 .expect("Symbol builtins install their prototype")
162 }
163
164 pub(crate) fn symbol_to_string_tag(&self) -> Value {
165 self.symbol_to_string_tag
166 .expect("Symbol builtins install first")
167 }
168
169 pub(crate) fn set_object_to_string(&mut self, function: Value) {
170 self.object_to_string = Some(function);
171 }
172
173 pub(crate) fn object_to_string(&self) -> Value {
174 self.object_to_string
175 .expect("Object builtins install Object.prototype.toString")
176 }
177
178 pub(crate) fn set_regexp_prototype(&mut self, prototype: Value) {
179 self.regexp_prototype = Some(prototype);
180 }
181
182 pub(crate) fn regexp_prototype(&self) -> Value {
183 self.regexp_prototype
184 .expect("RegExp builtins install their prototype")
185 }
186
187 pub(crate) fn set_iterator_prototype(&mut self, prototype: Value) {
188 self.iterator_prototype = Some(prototype);
189 }
190
191 pub(crate) fn iterator_prototype(&self) -> Value {
192 self.iterator_prototype
193 .expect("iterator builtins install their prototype")
194 }
195
196 pub(crate) fn set_generator_prototype(&mut self, prototype: Value) {
197 self.generator_prototype = Some(prototype);
198 }
199
200 pub(crate) fn generator_prototype(&self) -> Value {
201 self.generator_prototype
202 .expect("generator builtins install their prototype")
203 }
204
205 pub(crate) fn set_promise_prototype(&mut self, prototype: Value) {
206 self.promise_prototype = Some(prototype);
207 }
208
209 pub(crate) fn promise_prototype(&self) -> Value {
210 self.promise_prototype
211 .expect("Promise builtins install their prototype")
212 }
213
214 pub(crate) fn set_promise_resolver_targets(&mut self, resolve: Value, reject: Value) {
215 self.promise_resolver_targets = Some((resolve, reject));
216 }
217
218 pub(crate) fn promise_resolver_targets(&self) -> (Value, Value) {
219 self.promise_resolver_targets
220 .expect("Promise builtins install resolver targets")
221 }
222
223 pub(crate) fn set_promise_finally_targets(&mut self, fulfill: Value, reject: Value) {
224 self.promise_finally_targets = Some((fulfill, reject));
225 }
226
227 pub(crate) fn promise_finally_targets(&self) -> (Value, Value) {
228 self.promise_finally_targets
229 .expect("Promise builtins install finally targets")
230 }
231
232 pub(crate) fn set_promise_all_targets(&mut self, fulfill: Value, reject: Value) {
233 self.promise_all_targets = Some((fulfill, reject));
234 }
235
236 pub(crate) fn promise_all_targets(&self) -> (Value, Value) {
237 self.promise_all_targets
238 .expect("Promise builtins install all targets")
239 }
240
241 pub(crate) fn set_constructor_prototype(
242 &mut self,
243 heap: &mut [HeapEntry],
244 constructor: Value,
245 prototype: Value,
246 ) {
247 let index = heap_index(constructor);
248 let HeapEntry::NativeFunction { properties, .. } = &mut heap[index] else {
249 panic!("builtin constructor is a native function");
250 };
251 properties.insert(
252 crate::PropertyKey::Named(EcmaString::from_utf8("prototype")),
253 crate::Property::Data {
254 value: prototype,
255 writable: false,
256 enumerable: false,
257 configurable: false,
258 },
259 );
260 }
261
262 pub(crate) fn set_function_prototype(
263 &mut self,
264 heap: &mut [HeapEntry],
265 function: Value,
266 prototype: Value,
267 ) {
268 let index = heap_index(function);
269 let HeapEntry::NativeFunction { properties, .. } = &mut heap[index] else {
270 panic!("builtin function is a native function");
271 };
272 properties.insert(
273 crate::PropertyKey::Named(EcmaString::from_utf8("prototype")),
274 crate::Property::Data {
275 value: prototype,
276 writable: true,
277 enumerable: false,
278 configurable: false,
279 },
280 );
281 }
282
283 pub(crate) fn set_error_prototype(
284 &mut self,
285 heap: &mut [HeapEntry],
286 constructor: Value,
287 prototype: Value,
288 ) {
289 let index = heap_index(constructor);
290 let HeapEntry::NativeFunction {
291 callable: NativeCallable::Builtin(id),
292 ..
293 } = heap[index]
294 else {
295 panic!("error constructor is a native function");
296 };
297 self.error_prototypes.push((id, prototype));
298 }
299
300 pub(crate) fn id_named(&self, name: &str) -> Option<BuiltinId> {
301 self.defs
302 .iter()
303 .position(|definition| definition.name == name)
304 .map(BuiltinId)
305 }
306}
307
308pub(crate) struct Intrinsics<H: Host> {
309 pub(crate) globals: BTreeMap<EcmaString, Value>,
310 pub(crate) symbol_registry: BTreeMap<EcmaString, Value>,
311 pub(crate) object_prototype: Value,
312 pub(crate) function_prototype: Value,
313 pub(crate) array_prototype: Value,
314 pub(crate) string_prototype: Value,
315 pub(crate) number_prototype: Value,
316 pub(crate) boolean_prototype: Value,
317 pub(crate) builtins: BuiltinTable<H>,
318}
319
320impl<H: Host> Intrinsics<H> {
321 pub(crate) fn initialize(heap: &mut Vec<HeapEntry>, timers_available: bool) -> Self {
322 let object_prototype = push(
323 heap,
324 HeapEntry::Object {
325 properties: PropertyMap::default(),
326 prototype: None,
327 extensible: true,
328 boxed_primitive: None,
329 },
330 );
331 let function_prototype = ordinary_prototype(heap, object_prototype);
332 let array_prototype = push(
333 heap,
334 HeapEntry::Array {
335 elements: Vec::new(),
336 properties: PropertyMap::default(),
337 prototype: Some(object_prototype),
338 extensible: true,
339 length_writable: true,
340 },
341 );
342 let string_prototype = ordinary_prototype(heap, object_prototype);
343 let number_prototype = ordinary_prototype(heap, object_prototype);
344 let boolean_prototype = ordinary_prototype(heap, object_prototype);
345 let mut globals = BTreeMap::new();
346 let mut builtins = BuiltinTable::new(
347 object_prototype,
348 function_prototype,
349 array_prototype,
350 string_prototype,
351 number_prototype,
352 boolean_prototype,
353 );
354 builtins::install(heap, &mut globals, &mut builtins, timers_available);
355 crate::host_objects::install(heap, &mut globals, &mut builtins);
356
357 Self {
358 globals,
359 symbol_registry: BTreeMap::new(),
360 object_prototype,
361 function_prototype,
362 array_prototype,
363 string_prototype,
364 number_prototype,
365 boolean_prototype,
366 builtins,
367 }
368 }
369
370 pub(crate) fn global(&self, name: &str) -> Option<Value> {
371 debug_assert!(name.is_ascii());
372 self.globals
373 .iter()
374 .find_map(|(candidate, value)| candidate.eq_ascii(name).then_some(*value))
375 }
376
377 pub(crate) fn regexp_prototype(&self) -> Value {
378 self.builtins.regexp_prototype()
379 }
380
381 pub(crate) fn error_prototype(&self, id: BuiltinId) -> Value {
382 self.builtins
383 .error_prototypes
384 .iter()
385 .find_map(|(candidate, prototype)| (*candidate == id).then_some(*prototype))
386 .expect("every error builtin has a realm prototype")
387 }
388
389 pub(crate) fn object_to_string(&self) -> Value {
390 self.builtins.object_to_string()
391 }
392}
393
394fn ordinary_prototype(heap: &mut Vec<HeapEntry>, object_prototype: Value) -> Value {
395 push(
396 heap,
397 HeapEntry::Object {
398 properties: PropertyMap::default(),
399 prototype: Some(object_prototype),
400 extensible: true,
401 boxed_primitive: None,
402 },
403 )
404}
405
406pub(crate) fn native_function(
407 heap: &mut Vec<HeapEntry>,
408 id: BuiltinId,
409 name: &'static str,
410 length: u32,
411) -> Value {
412 let name_value = push(heap, HeapEntry::String(EcmaString::from_utf8(name)));
413 let mut properties = PropertyMap::default();
414 properties.insert(
415 crate::PropertyKey::Named(EcmaString::from_utf8("length")),
416 crate::Property::Data {
417 value: crate::number_value(f64::from(length)),
418 writable: false,
419 enumerable: false,
420 configurable: true,
421 },
422 );
423 properties.insert(
424 crate::PropertyKey::Named(EcmaString::from_utf8("name")),
425 crate::Property::Data {
426 value: name_value,
427 writable: false,
428 enumerable: false,
429 configurable: true,
430 },
431 );
432 push(
433 heap,
434 HeapEntry::NativeFunction {
435 callable: NativeCallable::Builtin(id),
436 properties,
437 extensible: true,
438 },
439 )
440}
441
442pub(crate) fn push(heap: &mut Vec<HeapEntry>, entry: HeapEntry) -> Value {
443 heap.push(entry);
444 let slot = u32::try_from(heap.len()).expect("intrinsic heap fits in a u32 slot");
445 Value::heap_ref(
446 bamts_native::SlotId::from_parts(crate::RUNTIME_HEAP_SEGMENT, slot)
447 .expect("intrinsic slot is nonzero"),
448 )
449}
450fn heap_index(value: Value) -> usize {
451 let Some(Decoded::HeapRef(id)) = value.decode() else {
452 panic!("intrinsic value is a heap reference");
453 };
454 id.slot() as usize - 1
455}
456
457impl<'a, H: Host> Machine<'a, H> {
458 pub(crate) fn call_builtin(
459 &mut self,
460 id: BuiltinId,
461 this_value: Value,
462 arguments: &[Value],
463 constructing: bool,
464 ) -> Result<BuiltinOutcome, EvalFailure> {
465 let handler = self.intrinsics.builtins.get(id).handler;
466 let previous = self.current_builtin_id.replace(id);
467 let outcome = handler(self, this_value, arguments, constructing);
468 self.current_builtin_id = previous;
469 outcome
470 }
471
472 fn object_to_string_tag(&self, value: Value) -> Result<&'static str, EvalFailure> {
473 match value.decode() {
474 Some(Decoded::Undefined | Decoded::Uninitialized | Decoded::Hole) | None => {
475 Ok("Undefined")
476 }
477 Some(Decoded::Null) => Ok("Null"),
478 Some(Decoded::Boolean(_)) => Ok("Boolean"),
479 Some(Decoded::Number(_) | Decoded::Int32(_)) => Ok("Number"),
480 Some(Decoded::HeapRef(_)) => {
481 let Some(index) = self.runtime_slot(value).map_err(EvalFailure::Runtime)? else {
482 return Ok("Object");
483 };
484 Ok(match &self.heap[index] {
485 HeapEntry::String(_) => "String",
486 HeapEntry::Array { .. } => "Array",
487 HeapEntry::Function { .. } | HeapEntry::NativeFunction { .. } => "Function",
488 HeapEntry::RegExp { .. } => "RegExp",
489 HeapEntry::BigInt(_) => "BigInt",
490 HeapEntry::PrivateName { .. } => "Symbol",
491 HeapEntry::Date { .. } => "Date",
492 HeapEntry::Object { .. } if self.is_error_object(index)? => "Error",
493 _ => "Object",
494 })
495 }
496 }
497 }
498
499 fn is_error_object(&self, mut index: usize) -> Result<bool, EvalFailure> {
500 for _ in 0..=self.heap.len() {
501 let value = Value::heap_ref(
502 bamts_native::SlotId::from_parts(
503 crate::RUNTIME_HEAP_SEGMENT,
504 u32::try_from(index + 1).expect("heap index fits in u32"),
505 )
506 .expect("heap index is nonzero"),
507 );
508 if self
509 .intrinsics
510 .builtins
511 .error_prototypes
512 .iter()
513 .any(|(_, prototype)| *prototype == value)
514 {
515 return Ok(true);
516 }
517 match self.prototype_index(index)? {
518 Some(next) => index = next,
519 None => return Ok(false),
520 }
521 }
522 Ok(false)
523 }
524
525 pub fn ordinary_number_to_string(number: f64) -> String {
526 crate::format_number(number)
527 }
528
529 pub(crate) fn to_string(&self, value: Value) -> Result<EcmaString, EvalFailure> {
530 self.value_to_string(value, 0)
531 }
532
533 pub(crate) fn string_constructor_text(
534 &mut self,
535 value: Value,
536 ) -> Result<EcmaString, EvalFailure> {
537 if let Some(index) = self.runtime_slot(value).map_err(EvalFailure::Runtime)? {
538 if let HeapEntry::Symbol { description } = &self.heap[index] {
539 let mut text =
540 EcmaStringBuilder::with_capacity(description.len_units().saturating_add(8));
541 text.push_utf8("Symbol(");
542 for &unit in description.as_units() {
543 text.push_unit(unit);
544 }
545 text.push_unit(u16::from(b')'));
546 return Ok(text.finish());
547 }
548 }
549
550 if !self.is_object(value) {
551 return self.to_string(value);
552 }
553
554 for name in ["toString", "valueOf"] {
555 let method = self.get_named_property(value, name)?;
556 if !self.is_callable(method)? {
557 continue;
558 }
559 let primitive = self.call_value(method, value, &[])?;
560 if !self.is_object(primitive) {
561 return self.to_string(primitive);
562 }
563 }
564
565 Err(EvalFailure::Throw(ThrowOrigin::TypeError {
566 operation: "cannot convert object to primitive without invoking user code",
567 }))
568 }
569
570 pub(crate) fn to_boolean(&self, value: Value) -> bool {
571 self.truthy(value)
572 }
573
574 pub fn same_value_zero(&self, left: Value, right: Value) -> bool {
575 match (left.decode(), right.decode()) {
576 (Some(Decoded::Number(a)), Some(Decoded::Number(b))) => {
577 a == b || (a.is_nan() && b.is_nan())
578 }
579 (Some(Decoded::Number(a)), Some(Decoded::Int32(b)))
580 | (Some(Decoded::Int32(b)), Some(Decoded::Number(a))) => a == f64::from(b),
581 _ => self.strict_equal(left, right),
582 }
583 }
584
585 pub(crate) fn to_primitive(&self, value: Value) -> Result<Value, EvalFailure> {
586 if !self.is_object(value) {
587 return Ok(value);
588 }
589 Err(EvalFailure::Throw(ThrowOrigin::TypeError {
590 operation: "cannot convert object to primitive without invoking user code",
591 }))
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use bamts_bytecode::{
598 Constant, ConstantId, Function, FunctionFlags, FunctionId, Instruction, Module, ModuleId,
599 Program, ProgramModule, Verified,
600 };
601
602 use super::*;
603 use crate::{Limits, Property, PropertyKey};
604
605 #[derive(Default)]
606 struct TestHost;
607 impl Host for TestHost {}
608
609 fn module() -> Program<Verified> {
610 let code = Module::new(
611 vec![Constant::String(EcmaString::from_utf8("<test>"))],
612 vec![Function::new(
613 None,
614 0,
615 0,
616 1,
617 FunctionFlags::default(),
618 vec![Instruction::Halt],
619 Vec::new(),
620 )],
621 FunctionId::new(0),
622 )
623 .verify()
624 .expect("valid test module");
625 Program::link(
626 vec![ProgramModule {
627 name: ConstantId::new(0),
628 code,
629 edges: Vec::new(),
630 bindings: Vec::new(),
631 exports: Vec::new(),
632 }],
633 ModuleId::new(0),
634 )
635 .expect("valid test program")
636 }
637
638 fn call_static(
639 machine: &mut Machine<'_, TestHost>,
640 constructor: &str,
641 method: &str,
642 arguments: &[Value],
643 ) -> Value {
644 let constructor = machine
645 .intrinsics
646 .global(constructor)
647 .expect("global exists");
648 let method = machine
649 .get_named_property(constructor, method)
650 .expect("method exists");
651 machine
652 .call_value(method, constructor, arguments)
653 .expect("builtin call succeeds")
654 }
655
656 #[test]
657 fn corpus_value_builtin_oracles_match_node_24_bytes() {
658 let expected = [
661 ("destr: JSON.stringify parsed object", "{\"test\":123}"),
662 ("dot-prop: Object.hasOwn", "true"),
663 ("defu: Object.assign key order", "1,2,b,a"),
664 ("valita: Array.isArray", "true"),
665 ];
666 let module = module();
667 let mut host = TestHost;
668 let mut machine = Machine::new(&module, &mut host, Limits::default());
669
670 let object = machine
671 .allocate(HeapEntry::Object {
672 properties: PropertyMap::default(),
673 prototype: Some(machine.intrinsics.object_prototype),
674 extensible: true,
675 boxed_primitive: None,
676 })
677 .unwrap();
678 machine
679 .set_data_property(object, "test", Value::int32(123))
680 .unwrap();
681 let json = machine.intrinsics.global("JSON").unwrap();
682 let stringify = machine.get_named_property(json, "stringify").unwrap();
683 let json_text = machine.call_value(stringify, json, &[object]).unwrap();
684
685 let test_key = machine
686 .allocate(HeapEntry::String(EcmaString::from_utf8("test")))
687 .unwrap();
688 let has_own = call_static(&mut machine, "Object", "hasOwn", &[object, test_key]);
689
690 let ordered = machine
691 .allocate(HeapEntry::Object {
692 properties: PropertyMap::default(),
693 prototype: Some(machine.intrinsics.object_prototype),
694 extensible: true,
695 boxed_primitive: None,
696 })
697 .unwrap();
698 for (key, value) in [("b", 1), ("2", 2), ("a", 3), ("1", 4)] {
699 let index = machine.runtime_slot(ordered).unwrap().unwrap();
700 let HeapEntry::Object { properties, .. } = &mut machine.heap[index] else {
701 unreachable!()
702 };
703 properties.insert(
704 PropertyKey::Named(EcmaString::from_utf8(key)),
705 Property::Data {
706 value: Value::int32(value),
707 writable: true,
708 enumerable: true,
709 configurable: true,
710 },
711 );
712 }
713 let keys = call_static(&mut machine, "Object", "keys", &[ordered]);
714 let array = machine
715 .allocate(HeapEntry::Array {
716 elements: Vec::new(),
717 properties: PropertyMap::default(),
718 prototype: Some(machine.intrinsics.array_prototype),
719 extensible: true,
720 length_writable: true,
721 })
722 .unwrap();
723 let is_array = call_static(&mut machine, "Array", "isArray", &[array]);
724
725 let actual = [
726 machine.to_string(json_text).unwrap(),
727 machine.to_string(has_own).unwrap(),
728 machine.to_string(keys).unwrap(),
729 machine.to_string(is_array).unwrap(),
730 ];
731 for ((label, expected), actual) in expected.into_iter().zip(actual) {
732 assert!(actual.eq_ascii(expected), "{label}: {actual:?}");
733 }
734 }
735
736 fn construct_builtin(
737 machine: &mut Machine<'_, TestHost>,
738 name: &str,
739 arguments: &[Value],
740 ) -> Value {
741 let constructor = machine.intrinsics.global(name).expect("global exists");
742 let index = machine.runtime_slot(constructor).unwrap().unwrap();
743 let HeapEntry::NativeFunction {
744 callable: NativeCallable::Builtin(id),
745 ..
746 } = machine.heap[index]
747 else {
748 panic!("constructor is native")
749 };
750 let BuiltinOutcome::Value(value) = machine
751 .call_builtin(id, Value::UNDEFINED, arguments, true)
752 .unwrap()
753 else {
754 panic!("constructor returns a value")
755 };
756 value
757 }
758
759 fn next_value(machine: &mut Machine<'_, TestHost>, iterator: Value) -> (Value, bool) {
760 let next = machine.get_named_property(iterator, "next").unwrap();
761 let result = machine.call_value(next, iterator, &[]).unwrap();
762 let value = machine.get_named_property(result, "value").unwrap();
763 let done = machine.get_named_property(result, "done").unwrap();
764 (value, machine.to_boolean(done))
765 }
766
767 #[test]
768 fn collections_symbols_errors_regexp_and_date_match_node_24_observables() {
769 let module = module();
770 let mut host = TestHost;
771 let mut machine = Machine::new(&module, &mut host, Limits::default());
772
773 let symbol = machine.intrinsics.global("Symbol").unwrap();
774 let symbol_for = machine.get_named_property(symbol, "for").unwrap();
775 let key_text = machine
776 .allocate(HeapEntry::String(EcmaString::from_utf8("shared")))
777 .unwrap();
778 let first = machine.call_value(symbol_for, symbol, &[key_text]).unwrap();
779 let second = machine.call_value(symbol_for, symbol, &[key_text]).unwrap();
780 assert_eq!(first, second, "Symbol.for registry identity");
781
782 let map = construct_builtin(&mut machine, "Map", &[]);
783 let set = machine.get_named_property(map, "set").unwrap();
784 machine
785 .call_value(set, map, &[Value::int32(2), Value::int32(20)])
786 .unwrap();
787 machine
788 .call_value(set, map, &[Value::int32(1), Value::int32(10)])
789 .unwrap();
790 let keys = machine.get_named_property(map, "keys").unwrap();
791 let iterator = machine.call_value(keys, map, &[]).unwrap();
792 let next = machine.get_named_property(iterator, "next").unwrap();
793 let first_result = machine.call_value(next, iterator, &[]).unwrap();
794 let second_result = machine.call_value(next, iterator, &[]).unwrap();
795 assert_eq!(
796 machine.get_named_property(first_result, "value").unwrap(),
797 Value::int32(2)
798 );
799 assert_eq!(
800 machine.get_named_property(second_result, "value").unwrap(),
801 Value::int32(1)
802 );
803
804 let pattern = machine
805 .allocate(HeapEntry::String(EcmaString::from_utf8("^(a|b)\\.js$")))
806 .unwrap();
807 let regexp = construct_builtin(&mut machine, "RegExp", &[pattern]);
808 let test = machine.get_named_property(regexp, "test").unwrap();
809 let input = machine
810 .allocate(HeapEntry::String(EcmaString::from_utf8("b.js")))
811 .unwrap();
812 assert_eq!(
813 machine.call_value(test, regexp, &[input]).unwrap(),
814 Value::TRUE
815 );
816
817 let message = machine
818 .allocate(HeapEntry::String(EcmaString::from_utf8("boom")))
819 .unwrap();
820 let error = construct_builtin(&mut machine, "TypeError", &[message]);
821 let error_message = machine.get_named_property(error, "message").unwrap();
822 assert!(machine.to_string(error_message).unwrap().eq_ascii("boom"));
823 let stack = machine.get_named_property(error, "stack").unwrap();
824 let stack = machine
825 .to_string(stack)
826 .unwrap()
827 .to_utf8_strict()
828 .expect("error stack is well-formed UTF-16");
829 assert!(stack.starts_with("TypeError: boom"));
830
831 let date = construct_builtin(&mut machine, "Date", &[Value::int32(0)]);
832 let object_to_string = machine.intrinsics.object_to_string();
833 let date_tag = machine.call_value(object_to_string, date, &[]).unwrap();
834 assert!(
835 machine
836 .string_value(date_tag)
837 .is_some_and(|text| text.eq_ascii("[object Date]"))
838 );
839 let to_iso = machine.get_named_property(date, "toISOString").unwrap();
840 let iso = machine.call_value(to_iso, date, &[]).unwrap();
841 assert!(
842 machine
843 .to_string(iso)
844 .unwrap()
845 .eq_ascii("1970-01-01T00:00:00.000Z")
846 );
847 }
848
849 #[test]
850 fn realm_handles_never_enter_public_globals() {
851 let module = module();
852 let mut host = TestHost;
853 let machine = Machine::new(&module, &mut host, Limits::default());
854 assert!(
855 machine
856 .intrinsics
857 .globals
858 .keys()
859 .all(|name| name.as_units().first() != Some(&0))
860 );
861
862 let global_this = machine
863 .intrinsics
864 .global("globalThis")
865 .expect("globalThis is installed");
866 let keys = machine
867 .own_property_keys(global_this)
868 .expect("globalThis is an object");
869 assert!(keys.into_iter().all(|key| {
870 key.as_string()
871 .is_none_or(|name| name.as_units().first() != Some(&0))
872 }));
873 }
874
875 #[test]
876 fn date_state_is_typed_and_unforgeable() {
877 let module = module();
878 let mut host = TestHost;
879 let mut machine = Machine::new(&module, &mut host, Limits::default());
880
881 let date = construct_builtin(&mut machine, "Date", &[Value::int32(0)]);
882 assert!(machine.own_property_keys(date).unwrap().is_empty());
883 let get_time = machine.get_named_property(date, "getTime").unwrap();
884
885 machine
886 .set_data_property(date, "\0Date.value", Value::int32(99))
887 .unwrap();
888 assert_eq!(
889 machine.call_value(get_time, date, &[]).unwrap(),
890 Value::int32(0)
891 );
892
893 let derived = machine
894 .allocate(HeapEntry::Object {
895 properties: PropertyMap::default(),
896 prototype: Some(date),
897 extensible: true,
898 boxed_primitive: None,
899 })
900 .unwrap();
901 assert!(machine.call_value(get_time, derived, &[]).is_err());
902
903 let structured_clone = machine.intrinsics.global("structuredClone").unwrap();
904 let clone = machine
905 .call_value(structured_clone, Value::UNDEFINED, &[date])
906 .unwrap();
907 assert_eq!(
908 machine.call_value(get_time, clone, &[]).unwrap(),
909 Value::int32(0)
910 );
911 assert!(machine.own_property_keys(clone).unwrap().is_empty());
912
913 let pair = machine
914 .allocate(HeapEntry::Array {
915 elements: vec![date, date],
916 properties: PropertyMap::default(),
917 prototype: Some(machine.intrinsics.array_prototype),
918 extensible: true,
919 length_writable: true,
920 })
921 .unwrap();
922 let pair_clone = machine
923 .call_value(structured_clone, Value::UNDEFINED, &[pair])
924 .unwrap();
925 let pair_index = machine.runtime_slot(pair_clone).unwrap().unwrap();
926 let HeapEntry::Array { elements, .. } = &machine.heap[pair_index] else {
927 panic!("cloned pair remains an array")
928 };
929 assert_eq!(elements[0], elements[1]);
930 }
931
932 #[test]
933 fn builtin_iterators_keep_typed_live_state() {
934 let module = module();
935 let mut host = TestHost;
936 let mut machine = Machine::new(&module, &mut host, Limits::default());
937 let array = machine
938 .allocate(HeapEntry::Array {
939 elements: vec![Value::HOLE, Value::int32(1)],
940 properties: PropertyMap::default(),
941 prototype: Some(machine.intrinsics.array_prototype),
942 extensible: true,
943 length_writable: true,
944 })
945 .unwrap();
946
947 let values = machine.get_named_property(array, "values").unwrap();
948 let values_iterator = machine.call_value(values, array, &[]).unwrap();
949 assert!(
950 machine
951 .own_property_keys(values_iterator)
952 .unwrap()
953 .is_empty()
954 );
955 machine
956 .set_data_property(values_iterator, "\0iterator.index", Value::int32(99))
957 .unwrap();
958 assert_eq!(
959 next_value(&mut machine, values_iterator),
960 (Value::UNDEFINED, false)
961 );
962 assert_eq!(
963 next_value(&mut machine, values_iterator),
964 (Value::int32(1), false)
965 );
966 assert_eq!(
967 next_value(&mut machine, values_iterator),
968 (Value::UNDEFINED, true)
969 );
970 machine
971 .set_data_property(array, "2", Value::int32(2))
972 .unwrap();
973 assert_eq!(
974 next_value(&mut machine, values_iterator),
975 (Value::UNDEFINED, true)
976 );
977
978 let keys = machine.get_named_property(array, "keys").unwrap();
979 let keys_iterator = machine.call_value(keys, array, &[]).unwrap();
980 machine
981 .set_data_property(array, "3", Value::int32(3))
982 .unwrap();
983 for expected in 0..4 {
984 assert_eq!(
985 next_value(&mut machine, keys_iterator),
986 (Value::int32(expected), false)
987 );
988 }
989 assert_eq!(
990 next_value(&mut machine, keys_iterator),
991 (Value::UNDEFINED, true)
992 );
993
994 let entries = machine.get_named_property(array, "entries").unwrap();
995 let entries_iterator = machine.call_value(entries, array, &[]).unwrap();
996 let (first_entry, done) = next_value(&mut machine, entries_iterator);
997 assert!(!done);
998 let entry_index = machine.runtime_slot(first_entry).unwrap().unwrap();
999 let HeapEntry::Array { elements, .. } = &machine.heap[entry_index] else {
1000 panic!("array entries yield pair arrays")
1001 };
1002 assert_eq!(elements, &[Value::int32(0), Value::UNDEFINED]);
1003
1004 let forged = machine
1005 .allocate(HeapEntry::Object {
1006 properties: PropertyMap::default(),
1007 prototype: Some(machine.intrinsics.object_prototype),
1008 extensible: true,
1009 boxed_primitive: None,
1010 })
1011 .unwrap();
1012 machine
1013 .set_data_property(forged, "\0iterator.source", array)
1014 .unwrap();
1015 machine
1016 .set_data_property(forged, "\0iterator.index", Value::int32(0))
1017 .unwrap();
1018 let next = machine.get_named_property(values_iterator, "next").unwrap();
1019 assert!(machine.call_value(next, forged, &[]).is_err());
1020 }
1021
1022 #[test]
1023 fn collections_hide_state_and_keep_iterator_positions() {
1024 let module = module();
1025 let mut host = TestHost;
1026 let mut machine = Machine::new(&module, &mut host, Limits::default());
1027 let map = construct_builtin(&mut machine, "Map", &[]);
1028 let set = machine.get_named_property(map, "set").unwrap();
1029 for (key, value) in [(1, 10), (2, 20), (3, 30)] {
1030 machine
1031 .call_value(set, map, &[Value::int32(key), Value::int32(value)])
1032 .unwrap();
1033 }
1034 assert!(machine.own_property_keys(map).unwrap().is_empty());
1035
1036 let derived = machine
1037 .allocate(HeapEntry::Object {
1038 properties: PropertyMap::default(),
1039 prototype: Some(map),
1040 extensible: true,
1041 boxed_primitive: None,
1042 })
1043 .unwrap();
1044 let get = machine.get_named_property(map, "get").unwrap();
1045 assert!(
1046 machine
1047 .call_value(get, derived, &[Value::int32(1)])
1048 .is_err()
1049 );
1050
1051 machine
1052 .set_data_property(map, "\0collection.keys", Value::UNDEFINED)
1053 .unwrap();
1054 assert_eq!(
1055 machine.get_named_property(map, "size").unwrap(),
1056 Value::int32(3)
1057 );
1058
1059 let keys = machine.get_named_property(map, "keys").unwrap();
1060 let iterator = machine.call_value(keys, map, &[]).unwrap();
1061 assert_eq!(next_value(&mut machine, iterator), (Value::int32(1), false));
1062 let delete = machine.get_named_property(map, "delete").unwrap();
1063 assert_eq!(
1064 machine.call_value(delete, map, &[Value::int32(1)]).unwrap(),
1065 Value::TRUE
1066 );
1067 assert_eq!(next_value(&mut machine, iterator), (Value::int32(2), false));
1068
1069 let clear = machine.get_named_property(map, "clear").unwrap();
1070 machine.call_value(clear, map, &[]).unwrap();
1071 machine
1072 .call_value(set, map, &[Value::int32(4), Value::int32(40)])
1073 .unwrap();
1074 assert_eq!(next_value(&mut machine, iterator), (Value::int32(4), false));
1075 assert_eq!(next_value(&mut machine, iterator), (Value::UNDEFINED, true));
1076 machine
1077 .call_value(set, map, &[Value::int32(5), Value::int32(50)])
1078 .unwrap();
1079 assert_eq!(next_value(&mut machine, iterator), (Value::UNDEFINED, true));
1080
1081 machine
1082 .call_value(set, map, &[Value::int32(9), map])
1083 .unwrap();
1084 let structured_clone = machine.intrinsics.global("structuredClone").unwrap();
1085 let clone = machine
1086 .call_value(structured_clone, Value::UNDEFINED, &[map])
1087 .unwrap();
1088 let cloned_get = machine.get_named_property(clone, "get").unwrap();
1089 assert_eq!(
1090 machine
1091 .call_value(cloned_get, clone, &[Value::int32(9)])
1092 .unwrap(),
1093 clone
1094 );
1095
1096 let churn = construct_builtin(&mut machine, "Map", &[]);
1097 let churn_keys = machine.get_named_property(churn, "keys").unwrap();
1098 let churn_iterator = machine.call_value(churn_keys, churn, &[]).unwrap();
1099 for key in 0..1_024 {
1100 machine
1101 .call_value(set, churn, &[Value::int32(key), Value::int32(key)])
1102 .unwrap();
1103 assert_eq!(
1104 machine
1105 .call_value(delete, churn, &[Value::int32(key)])
1106 .unwrap(),
1107 Value::TRUE
1108 );
1109 }
1110 machine
1111 .call_value(set, churn, &[Value::int32(2_048), Value::int32(2_048)])
1112 .unwrap();
1113 assert_eq!(
1114 next_value(&mut machine, churn_iterator),
1115 (Value::int32(2_048), false)
1116 );
1117 let churn_index = machine.runtime_slot(churn).unwrap().unwrap();
1118 let HeapEntry::Collection {
1119 entries,
1120 next_order,
1121 ..
1122 } = &machine.heap[churn_index]
1123 else {
1124 panic!("Map owns typed collection storage")
1125 };
1126 assert_eq!(entries.len(), 1);
1127 assert_eq!(entries[0].key, Value::int32(2_048));
1128 assert_eq!(*next_order, 1_025);
1129 }
1130}