Skip to main content

bamts_runtime/
intrinsics.rs

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        // Byte-exact outputs captured with Node v24.18.0. The labels name the
659        // corpus programs whose observable operation each row exercises.
660        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}