1use crate::host::{self, with_host, JsObj};
20use fusevm::Value;
21
22pub fn parts(v: &Value) -> Option<(Value, Value)> {
24 with_host(|h| match h.get(v) {
25 Some(JsObj::Proxy {
26 target, handler, ..
27 }) => Some((target.clone(), handler.clone())),
28 _ => None,
29 })
30}
31
32fn revoked(v: &Value) -> bool {
34 with_host(|h| matches!(h.get(v), Some(JsObj::Proxy { revoked, .. }) if *revoked))
35}
36
37pub fn ultimate_target(v: &Value) -> Option<Value> {
41 let mut cur = parts(v)?.0;
42 for _ in 0..100 {
43 match parts(&cur) {
44 Some((t, _)) => cur = t,
45 None => return Some(cur),
46 }
47 }
48 Some(cur)
49}
50
51fn revoked_err(op: &str) -> String {
53 host::type_error(&format!(
54 "Cannot perform '{op}' on a proxy that has been revoked"
55 ))
56}
57
58pub fn has_trap(v: &Value, name: &str) -> bool {
71 matches!(trap(v, name), Ok(Some(_)))
72}
73
74fn trap(v: &Value, name: &str) -> Result<Option<(Value, Value, Value)>, String> {
75 let Some((target, handler)) = parts(v) else {
76 return Ok(None);
77 };
78 if revoked(v) {
79 return Err(revoked_err(name));
80 }
81 let t = crate::builtins::get_property(&handler, name)?;
82 if matches!(t, Value::Undef) || with_host(|h| h.is_null(&t)) {
83 return Ok(None);
84 }
85 if !with_host(|h| host::is_callable(h, &t)) {
86 return Err(host::type_error(&format!(
87 "'{}' returned for property '{name}' of object '#<Object>' is not a function",
88 with_host(|h| h.str_of(&t))
89 )));
90 }
91 Ok(Some((t, target, handler)))
92}
93
94fn no_trap(v: &Value, op: &str) -> Result<Option<Value>, String> {
97 match parts(v) {
98 None => Ok(None),
99 Some((target, _)) if !revoked(v) => Ok(Some(target)),
100 Some(_) => Err(revoked_err(op)),
101 }
102}
103
104pub fn key_value(k: &str) -> Value {
108 with_host(|h| {
109 if let Some(s) = h.symbol_of_key(k) {
110 return s;
111 }
112 match k.strip_prefix("@@") {
113 Some(name) if host::WELL_KNOWN_SYMBOLS.contains(&name) => h.well_known_symbol(name),
114 _ => h.new_str(k),
115 }
116 })
117}
118
119fn call(t: &Value, handler: &Value, args: Vec<Value>) -> Result<Value, String> {
120 host::invoke(t, args, Some(handler.clone()))
121}
122
123fn invariant(msg: &str) -> String {
135 host::type_error(msg)
136}
137
138pub fn get(v: &Value, key: &str, receiver: &Value) -> Result<Option<Value>, String> {
139 if let Some((t, target, handler)) = trap(v, "get")? {
140 let k = key_value(key);
141 let got = call(&t, &handler, vec![target.clone(), k, receiver.clone()])?;
142 if let Some((val, writable, configurable, is_accessor)) =
145 crate::builtins::own_prop_facts(&target, key)
146 {
147 if !configurable && !is_accessor && !writable && !with_host(|h| h.strict_eq(&got, &val))
148 {
149 return Err(invariant(&format!(
150 "'get' on proxy: property '{key}' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value"
151 )));
152 }
153 }
154 return Ok(Some(got));
155 }
156 match no_trap(v, "get")? {
157 Some(target) => crate::builtins::get_property_recv(&target, key, receiver).map(Some),
158 None => Ok(None),
159 }
160}
161
162pub fn set(v: &Value, key: &str, val: &Value, receiver: &Value) -> Result<bool, String> {
164 if let Some((t, target, handler)) = trap(v, "set")? {
165 let k = key_value(key);
166 let r = call(
167 &t,
168 &handler,
169 vec![target.clone(), k, val.clone(), receiver.clone()],
170 )?;
171 if !with_host(|h| h.truthy(&r)) {
177 return Ok(false);
178 }
179 if let Some((cur, writable, configurable, is_accessor)) =
181 crate::builtins::own_prop_facts(&target, key)
182 {
183 if !configurable && !is_accessor && !writable && !with_host(|h| h.strict_eq(val, &cur))
184 {
185 return Err(invariant(&format!(
186 "'set' on proxy: trap returned truish for property '{key}' which exists in the proxy target as a non-configurable and non-writable data property with a different value"
187 )));
188 }
189 }
190 return Ok(true);
191 }
192 match no_trap(v, "set")? {
193 Some(target) => {
194 if receiver_observes_set(v, &target, key, receiver) {
195 return define_on_proxy_receiver(v, key, val);
196 }
197 crate::builtins::set_property_pub(&target, key, val.clone())?;
198 Ok(true)
199 }
200 None => Ok(false),
201 }
202}
203
204fn receiver_observes_set(v: &Value, target: &Value, key: &str, receiver: &Value) -> bool {
215 with_host(|h| h.strict_eq(receiver, v))
216 && (has_trap(v, "getOwnPropertyDescriptor") || has_trap(v, "defineProperty"))
217 && parts(target).is_none()
218 && with_host(|h| {
219 host::lookup_accessor(h, target, key).is_none() && h.can_write_prop(target, key)
220 })
221}
222
223fn define_on_proxy_receiver(v: &Value, key: &str, val: &Value) -> Result<bool, String> {
230 let existing = get_own_descriptor(v, key)?.unwrap_or(Value::Undef);
231 let mut desc = indexmap::IndexMap::new();
232 desc.insert("value".to_string(), val.clone());
233 if !matches!(existing, Value::Undef) {
234 let field = |n: &str| crate::builtins::get_property(&existing, n);
235 let is_accessor = with_host(|h| {
236 host::lookup_chain(h, &existing, "get").is_some()
237 || host::lookup_chain(h, &existing, "set").is_some()
238 });
239 let writable = field("writable")?;
240 if is_accessor || !with_host(|h| h.truthy(&writable)) {
241 return Ok(false);
242 }
243 } else {
244 for flag in ["writable", "enumerable", "configurable"] {
245 desc.insert(flag.to_string(), Value::Bool(true));
246 }
247 }
248 let desc = with_host(|h| h.new_object(desc));
249 if define_property(v, key, &desc)? {
250 return Ok(true);
251 }
252 if with_host(|h| h.current_strict()) {
253 return Err(host::type_error(&format!(
254 "'defineProperty' on proxy: trap returned falsish for property '{key}'"
255 )));
256 }
257 Ok(false)
258}
259
260pub fn has(v: &Value, key: &str) -> Result<Option<bool>, String> {
262 if let Some((t, target, handler)) = trap(v, "has")? {
263 let k = key_value(key);
264 let r = call(&t, &handler, vec![target.clone(), k])?;
265 let reported = with_host(|h| h.truthy(&r));
266 if !reported {
269 if let Some((_, _, configurable, _)) = crate::builtins::own_prop_facts(&target, key) {
270 if !configurable || !with_host(|h| h.is_extensible(&target)) {
271 return Err(invariant(&format!(
272 "'has' on proxy: trap returned falsish for property '{key}' which exists in the proxy target as non-configurable"
273 )));
274 }
275 }
276 }
277 return Ok(Some(reported));
278 }
279 match no_trap(v, "has")? {
280 Some(target) => crate::builtins::has_property(&target, key).map(Some),
281 None => Ok(None),
282 }
283}
284
285pub fn delete(v: &Value, key: &str) -> Result<Option<bool>, String> {
287 if let Some((t, target, handler)) = trap(v, "deleteProperty")? {
288 let k = key_value(key);
289 let r = call(&t, &handler, vec![target.clone(), k])?;
290 let reported = with_host(|h| h.truthy(&r));
291 if reported {
293 if let Some((_, _, configurable, _)) = crate::builtins::own_prop_facts(&target, key) {
294 if !configurable {
295 return Err(invariant(&format!(
296 "'deleteProperty' on proxy: trap returned truish for property '{key}' which is non-configurable in the proxy target"
297 )));
298 }
299 }
300 }
301 return Ok(Some(reported));
302 }
303 match no_trap(v, "deleteProperty")? {
304 Some(target) => crate::builtins::delete_property(&target, key).map(Some),
305 None => Ok(None),
306 }
307}
308
309pub fn own_keys(v: &Value) -> Result<Option<Vec<String>>, String> {
312 if let Some((t, target, handler)) = trap(v, "ownKeys")? {
313 let r = call(&t, &handler, vec![target.clone()])?;
314 let items = with_host(|h| h.iter_vec(&r))?;
315 let mut out = Vec::with_capacity(items.len());
316 for k in items {
317 out.push(host::to_property_key(&k)?);
318 }
319 let mut seen: Vec<&String> = Vec::with_capacity(out.len());
321 for k in &out {
322 if seen.contains(&k) {
323 return Err(invariant(&format!(
324 "'ownKeys' on proxy: trap returned duplicate entries for property '{k}'"
325 )));
326 }
327 seen.push(k);
328 }
329 let target_keys = with_host(|h| {
331 let mut ks = h.own_key_names(&target, false);
332 ks.extend(
333 h.own_symbol_keys(&target)
334 .iter()
335 .map(|sym| h.property_key(sym))
336 .collect::<Vec<_>>(),
337 );
338 ks
339 });
340 for k in &target_keys {
341 let pinned =
342 crate::builtins::own_prop_facts(&target, k).is_some_and(|(_, _, conf, _)| !conf);
343 if pinned && !out.contains(k) {
344 return Err(invariant(&format!(
345 "'ownKeys' on proxy: trap result did not include '{k}'"
346 )));
347 }
348 }
349 if !with_host(|h| h.is_extensible(&target)) {
351 for k in &target_keys {
352 if !out.contains(k) {
353 return Err(invariant(&format!(
354 "'ownKeys' on proxy: trap result did not include '{k}'"
355 )));
356 }
357 }
358 for k in &out {
359 if !target_keys.contains(k) {
360 return Err(invariant(
361 "'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible",
362 ));
363 }
364 }
365 }
366 return Ok(Some(out));
367 }
368 match no_trap(v, "ownKeys")? {
369 Some(target) => {
370 let mut keys = with_host(|h| h.own_key_names(&target, false));
371 keys.extend(with_host(|h| {
372 h.own_symbol_keys(&target)
373 .iter()
374 .map(|s| h.property_key(s))
375 .collect::<Vec<_>>()
376 }));
377 Ok(Some(keys))
378 }
379 None => Ok(None),
380 }
381}
382
383pub fn get_own_descriptor(v: &Value, key: &str) -> Result<Option<Value>, String> {
385 if let Some((t, target, handler)) = trap(v, "getOwnPropertyDescriptor")? {
386 let k = key_value(key);
387 let d = call(&t, &handler, vec![target.clone(), k])?;
388 if matches!(d, Value::Undef) {
390 if let Some((_, _, configurable, _)) = crate::builtins::own_prop_facts(&target, key) {
391 if !configurable {
392 return Err(invariant(&format!(
393 "'getOwnPropertyDescriptor' on proxy: trap returned undefined for property '{key}' which is non-configurable in the proxy target"
394 )));
395 }
396 }
397 }
398 return Ok(Some(d));
399 }
400 match no_trap(v, "getOwnPropertyDescriptor")? {
401 Some(target) => {
402 let k = key_value(key);
403 crate::builtins::own_descriptor_pub(&target, k).map(Some)
404 }
405 None => Ok(None),
406 }
407}
408
409pub fn define_property(v: &Value, key: &str, desc: &Value) -> Result<bool, String> {
411 if let Some((t, target, handler)) = trap(v, "defineProperty")? {
412 let k = key_value(key);
413 let r = call(&t, &handler, vec![target.clone(), k, desc.clone()])?;
414 if !with_host(|h| h.truthy(&r)) {
420 return Ok(false);
421 }
422 if crate::builtins::own_prop_facts(&target, key).is_none()
424 && !with_host(|h| h.is_extensible(&target))
425 {
426 return Err(invariant(&format!(
427 "'defineProperty' on proxy: trap returned truish for adding property '{key}' to the non-extensible proxy target"
428 )));
429 }
430 return Ok(true);
431 }
432 match no_trap(v, "defineProperty")? {
433 Some(target) => {
434 let k = key_value(key);
435 crate::builtins::define_property_pub(&target, k, desc.clone())?;
436 Ok(true)
437 }
438 None => Ok(false),
439 }
440}
441
442pub fn get_prototype_of(v: &Value) -> Result<Option<Value>, String> {
444 if let Some((t, target, handler)) = trap(v, "getPrototypeOf")? {
445 let reported = call(&t, &handler, vec![target.clone()])?;
446 if !with_host(|h| h.is_extensible(&target)) {
449 let actual = crate::builtins::prototype_of(&target);
450 if !with_host(|h| h.strict_eq(&reported, &actual)) {
451 return Err(invariant(
452 "'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype",
453 ));
454 }
455 }
456 return Ok(Some(reported));
457 }
458 match no_trap(v, "getPrototypeOf")? {
459 Some(target) => Ok(Some(crate::builtins::prototype_of(&target))),
460 None => Ok(None),
461 }
462}
463
464pub fn set_prototype_of(v: &Value, proto: &Value) -> Result<bool, String> {
466 if let Some((t, target, handler)) = trap(v, "setPrototypeOf")? {
467 call(&t, &handler, vec![target, proto.clone()])?;
468 return Ok(true);
469 }
470 match no_trap(v, "setPrototypeOf")? {
471 Some(target) => {
472 with_host(|h| h.set_proto(&target, proto.clone()));
473 Ok(true)
474 }
475 None => Ok(false),
476 }
477}
478
479pub fn is_extensible(v: &Value) -> Result<Option<bool>, String> {
481 if let Some((t, target, handler)) = trap(v, "isExtensible")? {
482 let reported = call(&t, &handler, vec![target.clone()])?;
485 let reported = with_host(|h| h.truthy(&reported));
486 if reported != with_host(|h| h.is_extensible(&target)) {
487 return Err(invariant(
488 "'isExtensible' on proxy: trap result does not reflect extensibility of proxy target",
489 ));
490 }
491 return Ok(Some(reported));
492 }
493 match no_trap(v, "isExtensible")? {
494 Some(target) => Ok(Some(with_host(|h| h.is_extensible(&target)))),
495 None => Ok(None),
496 }
497}
498
499pub fn prevent_extensions(v: &Value) -> Result<bool, String> {
501 if let Some((t, target, handler)) = trap(v, "preventExtensions")? {
502 call(&t, &handler, vec![target])?;
503 return Ok(true);
504 }
505 match no_trap(v, "preventExtensions")? {
506 Some(target) => {
507 with_host(|h| h.prevent_extensions(&target));
508 Ok(true)
509 }
510 None => Ok(false),
511 }
512}
513
514pub fn apply(v: &Value, args: Vec<Value>, this: Option<Value>) -> Result<Option<Value>, String> {
516 if let Some((t, target, handler)) = trap(v, "apply")? {
517 let this_arg = this.unwrap_or(Value::Undef);
518 let list = with_host(|h| h.new_array(args));
519 return call(&t, &handler, vec![target, this_arg, list]).map(Some);
520 }
521 match no_trap(v, "apply")? {
522 Some(target) => host::invoke(&target, args, this).map(Some),
523 None => Ok(None),
524 }
525}
526
527pub fn construct(v: &Value, args: Vec<Value>, new_target: &Value) -> Result<Option<Value>, String> {
529 if let Some((t, target, handler)) = trap(v, "construct")? {
530 let list = with_host(|h| h.new_array(args));
531 return call(&t, &handler, vec![target, list, new_target.clone()]).map(Some);
532 }
533 match no_trap(v, "construct")? {
534 Some(target) => host::construct_nt(&target, args, new_target.clone()).map(Some),
535 None => Ok(None),
536 }
537}
538
539pub fn own_enum_string_keys(v: &Value) -> Result<Vec<String>, String> {
546 let Some(keys) = own_keys(v)? else {
547 return Ok(Vec::new());
548 };
549 let mut out = Vec::new();
550 for k in keys {
551 if host::is_symbol_key(&k) {
552 continue;
553 }
554 let Some(d) = get_own_descriptor(v, &k)? else {
555 continue;
556 };
557 let enumerable = with_host(|h| match h.get(&d) {
558 Some(JsObj::Object(p)) => p.get("enumerable").map(|e| h.truthy(e)).unwrap_or(false),
559 _ => false,
560 });
561 if enumerable {
562 out.push(k);
563 }
564 }
565 Ok(out)
566}
567
568pub fn own_enumerable(v: &Value, key: &str) -> Result<bool, String> {
574 let Some(d) = get_own_descriptor(v, key)? else {
575 return Ok(false);
576 };
577 Ok(with_host(|h| match h.get(&d) {
578 Some(JsObj::Object(p)) => p.get("enumerable").map(|e| h.truthy(e)).unwrap_or(false),
579 _ => false,
580 }))
581}
582
583pub fn own_enum_entries(v: &Value) -> Result<Vec<(String, Value)>, String> {
587 let keys = own_enum_string_keys(v)?;
588 let mut out = Vec::with_capacity(keys.len());
589 for k in keys {
590 let val = get(v, &k, v)?.unwrap_or(Value::Undef);
591 out.push((k, val));
592 }
593 Ok(out)
594}
595
596fn wraps_array(v: &Value) -> bool {
599 match ultimate_target(v) {
600 Some(t) => with_host(|h| matches!(h.get(&t), Some(JsObj::Array(_)))),
601 None => false,
602 }
603}
604
605pub fn iterate(v: &Value) -> Result<Option<Vec<Value>>, String> {
613 if parts(v).is_none() {
614 return Ok(None);
615 }
616 let array_backed = wraps_array(v);
617 let iter_fn = get(v, "@@iterator", v)?.unwrap_or(Value::Undef);
618 let default_array_iter =
626 array_backed && with_host(|h| matches!(h.get(&iter_fn), Some(JsObj::BoundMethod { .. })));
627 if !default_array_iter && with_host(|h| host::is_callable(h, &iter_fn)) {
628 let iterator = host::invoke(&iter_fn, Vec::new(), Some(v.clone()))?;
629 return host::drain_iterator(&iterator).map(Some);
630 }
631 if array_backed {
632 let len_v = get(v, "length", v)?.unwrap_or(Value::Undef);
633 let len = with_host(|h| h.to_number(&len_v));
634 let len = if len.is_finite() && len > 0.0 {
635 len as usize
636 } else {
637 0
638 };
639 let mut out = Vec::with_capacity(len);
640 for i in 0..len {
641 out.push(get(v, &i.to_string(), v)?.unwrap_or(Value::Undef));
642 }
643 return Ok(Some(out));
644 }
645 let target = no_trap(v, "get")?.expect("checked it is a proxy");
646 host::iter_all(&target).map(Some)
647}
648
649pub fn json_snapshot(v: &Value) -> Result<Value, String> {
653 if wraps_array(v) {
654 let items = iterate(v)?.unwrap_or_default();
655 return Ok(with_host(|h| h.new_array(items)));
656 }
657 let entries = own_enum_entries(v)?;
658 Ok(with_host(|h| {
659 let mut m = indexmap::IndexMap::new();
660 for (k, val) in entries {
661 m.insert(k, val);
662 }
663 h.new_object(m)
664 }))
665}
666
667pub fn create(args: &[Value]) -> Result<Value, String> {
671 let target = args.first().cloned().unwrap_or(Value::Undef);
672 let handler = args.get(1).cloned().unwrap_or(Value::Undef);
673 let ok = |v: &Value| {
674 with_host(|h| matches!(v, Value::Obj(_)) && !h.is_null(v) && !host::is_primitive(h, v))
675 };
676 if !ok(&target) || !ok(&handler) {
677 return Err(host::type_error(
678 "Cannot create proxy with a non-object as target or handler",
679 ));
680 }
681 Ok(with_host(|h| {
682 h.alloc(JsObj::Proxy {
683 target,
684 handler,
685 revoked: false,
686 })
687 }))
688}
689
690pub fn revocable(args: &[Value]) -> Result<Value, String> {
694 let proxy = create(args)?;
695 let idx = match proxy {
696 Value::Obj(i) => i,
697 _ => unreachable!("create returns a heap object"),
698 };
699 let revoke = with_host(|h| h.alloc(JsObj::Builtin(format!("@@prevoke:{idx}"))));
700 Ok(with_host(|h| {
701 let mut m = indexmap::IndexMap::new();
702 m.insert("proxy".to_string(), proxy);
703 m.insert("revoke".to_string(), revoke);
704 h.new_object(m)
705 }))
706}
707
708pub fn revoke(idx: u32) -> Value {
716 with_host(|h| {
717 if let Some(JsObj::Proxy { revoked, .. }) = h.get_mut(&Value::Obj(idx)) {
718 *revoked = true;
719 }
720 });
721 Value::Undef
722}