1use crate::{JavascriptHeap, JavascriptManagedKind, JavascriptManagedObject, JavascriptValue};
4use sim_lib_dispatch::{
5 AccessContext, AccessError, AccessorDescriptor, DataDescriptor, DefineError, Descriptor,
6 PropertyHook, PropertyStore,
7};
8use sim_lib_mutation::{ArenaError, ManagedHandle, ManagedId};
9use std::collections::{BTreeMap, HashSet};
10
11#[derive(Clone, Debug, Eq, Hash, PartialEq)]
14pub enum JavascriptPropertyKey {
15 String(String),
17 Symbol(u64),
19 Private {
21 class: ManagedId,
23 name: String,
25 },
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum JavascriptFunctionKind {
31 Function,
33 Arrow,
35 ClassConstructor,
37}
38
39#[derive(Clone, Debug)]
42pub struct JavascriptFunction {
43 pub kind: JavascriptFunctionKind,
45 pub environment: ManagedHandle,
47 pub constructable: bool,
49 pub private_names: Vec<String>,
51}
52
53#[derive(Clone, Debug, PartialEq)]
55pub enum JavascriptThis {
56 Lexical(JavascriptValue),
58 Dynamic(JavascriptValue),
60}
61
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
65pub enum JavascriptObjectGap {
66 ProxyInvariants,
68 ExoticObjectInvariants,
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
74pub enum JavascriptObjectError {
75 Arena(ArenaError),
77 Descriptor(DefineError),
79 Access,
81 PrototypeCycle,
83 NotConstructor,
85 PrivateBrand,
87}
88impl From<ArenaError> for JavascriptObjectError {
89 fn from(v: ArenaError) -> Self {
90 Self::Arena(v)
91 }
92}
93impl From<DefineError> for JavascriptObjectError {
94 fn from(v: DefineError) -> Self {
95 Self::Descriptor(v)
96 }
97}
98
99#[derive(Clone, Debug, PartialEq)]
100struct Accessor {
101 get: Option<JavascriptValue>,
102 set: bool,
103}
104#[derive(Default)]
105struct Hooks {
106 writes: Vec<(ManagedId, JavascriptPropertyKey, JavascriptValue)>,
107}
108impl PropertyHook<ManagedId, JavascriptPropertyKey, JavascriptValue, Accessor> for Hooks {
109 type Error = ();
110 fn get(
111 &mut self,
112 _: &mut AccessContext<ManagedId, JavascriptPropertyKey>,
113 hook: &Accessor,
114 _: &ManagedId,
115 _: &JavascriptPropertyKey,
116 ) -> Result<JavascriptValue, AccessError<()>> {
117 hook.get.clone().ok_or(AccessError::Hook(()))
118 }
119 fn set(
120 &mut self,
121 _: &mut AccessContext<ManagedId, JavascriptPropertyKey>,
122 hook: &Accessor,
123 receiver: &ManagedId,
124 key: &JavascriptPropertyKey,
125 value: JavascriptValue,
126 ) -> Result<(), AccessError<()>> {
127 if !hook.set {
128 return Err(AccessError::Hook(()));
129 }
130 self.writes.push((*receiver, key.clone(), value));
131 Ok(())
132 }
133}
134
135pub struct JavascriptObjects {
137 heap: JavascriptHeap,
138 properties: PropertyStore<ManagedId, JavascriptPropertyKey, JavascriptValue, Accessor>,
139 prototypes: BTreeMap<ManagedId, ManagedHandle>,
140 functions: BTreeMap<ManagedId, JavascriptFunction>,
141 lexical_this: BTreeMap<ManagedId, JavascriptValue>,
142}
143impl JavascriptObjects {
144 pub fn new(heap: JavascriptHeap) -> Self {
146 Self {
147 heap,
148 properties: PropertyStore::new(),
149 prototypes: BTreeMap::new(),
150 functions: BTreeMap::new(),
151 lexical_this: BTreeMap::new(),
152 }
153 }
154 pub fn ordinary(&mut self) -> Result<ManagedHandle, JavascriptObjectError> {
157 Ok(self.heap.allocate(JavascriptManagedObject::default())?)
158 }
159 pub fn function(
161 &mut self,
162 function: JavascriptFunction,
163 lexical_this: Option<JavascriptValue>,
164 ) -> Result<ManagedHandle, JavascriptObjectError> {
165 let h = self.heap.allocate(JavascriptManagedObject {
166 kind: JavascriptManagedKind::Function,
167 edges: vec![function.environment.id()],
168 })?;
169 if let Some(v) = lexical_this {
170 self.lexical_this.insert(h.id(), v);
171 }
172 self.functions.insert(h.id(), function);
173 Ok(h)
174 }
175 pub fn call_this(
177 &self,
178 function: ManagedHandle,
179 receiver: JavascriptValue,
180 ) -> Result<JavascriptThis, JavascriptObjectError> {
181 let f = self
182 .functions
183 .get(&function.id())
184 .ok_or(JavascriptObjectError::NotConstructor)?;
185 Ok(if f.kind == JavascriptFunctionKind::Arrow {
186 JavascriptThis::Lexical(
187 self.lexical_this
188 .get(&function.id())
189 .cloned()
190 .unwrap_or(JavascriptValue::Undefined),
191 )
192 } else {
193 JavascriptThis::Dynamic(receiver)
194 })
195 }
196 pub fn call<T, E>(
200 &self,
201 function: ManagedHandle,
202 receiver: JavascriptValue,
203 arguments: &[JavascriptValue],
204 body: impl FnOnce(ManagedHandle, JavascriptThis, &[JavascriptValue]) -> Result<T, E>,
205 ) -> Result<T, E>
206 where
207 E: From<JavascriptObjectError>,
208 {
209 let metadata = self
210 .functions
211 .get(&function.id())
212 .ok_or(JavascriptObjectError::NotConstructor)?;
213 let this = self.call_this(function, receiver)?;
214 body(metadata.environment, this, arguments)
215 }
216 pub fn construct(
218 &mut self,
219 function: ManagedHandle,
220 ) -> Result<ManagedHandle, JavascriptObjectError> {
221 let f = self
222 .functions
223 .get(&function.id())
224 .ok_or(JavascriptObjectError::NotConstructor)?;
225 if !f.constructable || f.kind == JavascriptFunctionKind::Arrow {
226 return Err(JavascriptObjectError::NotConstructor);
227 }
228 let instance = self.ordinary()?;
229 self.set_prototype(instance, function)?;
230 Ok(instance)
231 }
232 pub fn set_prototype(
234 &mut self,
235 object: ManagedHandle,
236 prototype: ManagedHandle,
237 ) -> Result<(), JavascriptObjectError> {
238 let mut at = Some(prototype);
239 let mut seen = HashSet::new();
240 while let Some(h) = at {
241 if h.id() == object.id() || !seen.insert(h.id()) {
242 return Err(JavascriptObjectError::PrototypeCycle);
243 }
244 at = self.prototypes.get(&h.id()).copied();
245 }
246 self.heap.connect(object, prototype)?;
247 self.prototypes.insert(object.id(), prototype);
248 Ok(())
249 }
250 pub fn define_data(
252 &mut self,
253 object: ManagedHandle,
254 key: JavascriptPropertyKey,
255 value: JavascriptValue,
256 writable: bool,
257 enumerable: bool,
258 configurable: bool,
259 ) -> Result<(), JavascriptObjectError> {
260 self.properties.define(
261 &object.id(),
262 key,
263 Descriptor::Data(DataDescriptor {
264 value,
265 writable,
266 enumerable,
267 configurable,
268 }),
269 )?;
270 Ok(())
271 }
272 pub fn define_accessor(
274 &mut self,
275 object: ManagedHandle,
276 key: JavascriptPropertyKey,
277 get: Option<JavascriptValue>,
278 set: bool,
279 enumerable: bool,
280 configurable: bool,
281 ) -> Result<(), JavascriptObjectError> {
282 self.properties.define(
283 &object.id(),
284 key,
285 Descriptor::Accessor(AccessorDescriptor {
286 get: Some(Accessor { get, set }),
287 set: Some(Accessor { get: None, set }),
288 enumerable,
289 configurable,
290 }),
291 )?;
292 Ok(())
293 }
294 fn chain(
295 &self,
296 object: ManagedHandle,
297 budget: usize,
298 ) -> Result<Vec<ManagedId>, JavascriptObjectError> {
299 let mut out = Vec::new();
300 let mut at = Some(object);
301 let mut seen = HashSet::new();
302 while let Some(h) = at {
303 if out.len() >= budget {
304 return Err(JavascriptObjectError::Access);
305 }
306 if !seen.insert(h.id()) {
307 break;
308 }
309 out.push(h.id());
310 at = self.prototypes.get(&h.id()).copied();
311 }
312 Ok(out)
313 }
314 pub fn get(
316 &self,
317 object: ManagedHandle,
318 key: &JavascriptPropertyKey,
319 budget: usize,
320 ) -> Result<Option<JavascriptValue>, JavascriptObjectError> {
321 let chain = self.chain(object, budget)?;
322 let mut hooks = Hooks::default();
323 self.properties
324 .get(
325 &chain,
326 &object.id(),
327 key,
328 &mut AccessContext::new(budget),
329 &mut hooks,
330 )
331 .map_err(|_| JavascriptObjectError::Access)
332 }
333 pub fn set(
336 &mut self,
337 object: ManagedHandle,
338 key: &JavascriptPropertyKey,
339 value: JavascriptValue,
340 budget: usize,
341 ) -> Result<bool, JavascriptObjectError> {
342 let chain = self.chain(object, budget)?;
343 let mut hooks = Hooks::default();
344 self.properties
345 .set(
346 &chain,
347 &object.id(),
348 key,
349 value,
350 &mut AccessContext::new(budget),
351 &mut hooks,
352 )
353 .map_err(|_| JavascriptObjectError::Access)
354 }
355 pub fn delete(
357 &mut self,
358 object: ManagedHandle,
359 key: &JavascriptPropertyKey,
360 ) -> Result<bool, JavascriptObjectError> {
361 Ok(self.properties.delete(&object.id(), key)?)
362 }
363 pub fn enumerable_keys(&self, object: ManagedHandle) -> Vec<JavascriptPropertyKey> {
366 let keys = self.properties.own_keys(&object.id(), true);
367 let mut indices = Vec::new();
368 let mut strings = Vec::new();
369 let mut symbols = Vec::new();
370 for key in keys {
371 match &key {
372 JavascriptPropertyKey::String(s) => match s.parse::<u32>() {
373 Ok(n) if n != u32::MAX && n.to_string() == *s => indices.push((n, key)),
374 _ => strings.push(key),
375 },
376 JavascriptPropertyKey::Symbol(_) => symbols.push(key),
377 JavascriptPropertyKey::Private { .. } => {}
378 }
379 }
380 indices.sort_by_key(|(n, _)| *n);
381 indices
382 .into_iter()
383 .map(|(_, k)| k)
384 .chain(strings)
385 .chain(symbols)
386 .collect()
387 }
388 pub fn private_key(
390 &self,
391 class: ManagedHandle,
392 name: &str,
393 ) -> Result<JavascriptPropertyKey, JavascriptObjectError> {
394 let f = self
395 .functions
396 .get(&class.id())
397 .ok_or(JavascriptObjectError::PrivateBrand)?;
398 if !f.private_names.iter().any(|n| n == name) {
399 return Err(JavascriptObjectError::PrivateBrand);
400 }
401 Ok(JavascriptPropertyKey::Private {
402 class: class.id(),
403 name: name.into(),
404 })
405 }
406 pub fn collect(
408 &mut self,
409 ) -> Result<Option<sim_lib_gc_tracing::CollectionReceipt>, sim_lib_gc_tracing::CollectionError>
410 {
411 self.heap.collect()
412 }
413 pub fn live_len(&self) -> usize {
415 self.heap.live_len()
416 }
417}
418
419pub const fn javascript_callable_shape_constraints() -> &'static [&'static str] {
422 &[]
423}
424pub const fn javascript_object_gaps() -> &'static [JavascriptObjectGap] {
426 &[
427 JavascriptObjectGap::ProxyInvariants,
428 JavascriptObjectGap::ExoticObjectInvariants,
429 ]
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use sim_lib_gc_tracing::CollectionLimits;
436 fn model() -> JavascriptObjects {
437 JavascriptObjects::new(
438 JavascriptHeap::standard(
439 32,
440 CollectionLimits {
441 objects: 32,
442 edges: 64,
443 stack: 32,
444 work: 256,
445 clears: 32,
446 finalizers: 0,
447 },
448 )
449 .unwrap(),
450 )
451 }
452 fn s(v: &str) -> JavascriptPropertyKey {
453 JavascriptPropertyKey::String(v.into())
454 }
455 #[test]
456 fn descriptors_prototypes_arrays_and_private_names_are_shared_mechanics() {
457 let mut m = model();
458 let env = m.ordinary().unwrap();
459 let class = m
460 .function(
461 JavascriptFunction {
462 kind: JavascriptFunctionKind::ClassConstructor,
463 environment: env,
464 constructable: true,
465 private_names: vec!["x".into()],
466 },
467 None,
468 )
469 .unwrap();
470 m.define_data(
471 class,
472 s("inherited"),
473 JavascriptValue::Number(7.0),
474 false,
475 true,
476 false,
477 )
478 .unwrap();
479 let o = m.construct(class).unwrap();
480 assert_eq!(
481 m.get(o, &s("inherited"), 8).unwrap(),
482 Some(JavascriptValue::Number(7.0))
483 );
484 m.define_data(o, s("10"), JavascriptValue::Null, true, true, true)
485 .unwrap();
486 m.define_data(o, s("2"), JavascriptValue::Null, true, true, true)
487 .unwrap();
488 m.define_accessor(
489 o,
490 s("answer"),
491 Some(JavascriptValue::Number(42.0)),
492 false,
493 true,
494 true,
495 )
496 .unwrap();
497 assert_eq!(m.enumerable_keys(o), vec![s("2"), s("10"), s("answer")]);
498 assert_eq!(
499 m.get(o, &s("answer"), 8).unwrap(),
500 Some(JavascriptValue::Number(42.0))
501 );
502 assert!(m.private_key(class, "x").is_ok());
503 assert!(m.private_key(class, "y").is_err());
504 assert!(m.delete(o, &s("answer")).unwrap());
505 }
506 #[test]
507 fn functions_arrows_construction_shapes_and_gaps_are_explicit() {
508 let mut m = model();
509 let env = m.ordinary().unwrap();
510 let arrow = m
511 .function(
512 JavascriptFunction {
513 kind: JavascriptFunctionKind::Arrow,
514 environment: env,
515 constructable: false,
516 private_names: vec![],
517 },
518 Some(JavascriptValue::String("lexical".into())),
519 )
520 .unwrap();
521 assert_eq!(
522 m.call_this(arrow, JavascriptValue::String("dynamic".into()))
523 .unwrap(),
524 JavascriptThis::Lexical(JavascriptValue::String("lexical".into()))
525 );
526 let called = m
527 .call(
528 arrow,
529 JavascriptValue::Undefined,
530 &[JavascriptValue::Number(42.0)],
531 |captured, this, arguments| {
532 Ok::<_, JavascriptObjectError>((captured, this, arguments[0].clone()))
533 },
534 )
535 .unwrap();
536 assert_eq!(called.0, env);
537 assert_eq!(called.2, JavascriptValue::Number(42.0));
538 assert_eq!(
539 m.construct(arrow),
540 Err(JavascriptObjectError::NotConstructor)
541 );
542 assert!(javascript_callable_shape_constraints().is_empty());
543 assert_eq!(javascript_object_gaps().len(), 2);
544 }
545 #[test]
546 fn mixed_language_cycles_reclaim_without_changing_observed_values() {
547 let mut m = model();
548 let env = m.ordinary().unwrap();
549 let f = m
550 .function(
551 JavascriptFunction {
552 kind: JavascriptFunctionKind::Function,
553 environment: env,
554 constructable: true,
555 private_names: vec![],
556 },
557 None,
558 )
559 .unwrap();
560 let array = m.ordinary().unwrap();
561 m.set_prototype(array, f).unwrap();
562 m.define_accessor(
563 array,
564 s("stable"),
565 Some(JavascriptValue::Number(42.0)),
566 false,
567 true,
568 true,
569 )
570 .unwrap();
571 assert_eq!(
572 m.get(array, &s("stable"), 8).unwrap(),
573 Some(JavascriptValue::Number(42.0))
574 );
575 assert_eq!(m.live_len(), 3);
576 assert_eq!(m.collect().unwrap().unwrap().swept.len(), 3);
577 assert_eq!(m.live_len(), 0);
578 }
579}