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 crate::builtins::set_property_pub(&target, key, val.clone())?;
195 Ok(true)
196 }
197 None => Ok(false),
198 }
199}
200
201pub fn has(v: &Value, key: &str) -> Result<Option<bool>, String> {
203 if let Some((t, target, handler)) = trap(v, "has")? {
204 let k = key_value(key);
205 let r = call(&t, &handler, vec![target.clone(), k])?;
206 let reported = with_host(|h| h.truthy(&r));
207 if !reported {
210 if let Some((_, _, configurable, _)) = crate::builtins::own_prop_facts(&target, key) {
211 if !configurable || !with_host(|h| h.is_extensible(&target)) {
212 return Err(invariant(&format!(
213 "'has' on proxy: trap returned falsish for property '{key}' which exists in the proxy target as non-configurable"
214 )));
215 }
216 }
217 }
218 return Ok(Some(reported));
219 }
220 match no_trap(v, "has")? {
221 Some(target) => crate::builtins::has_property(&target, key).map(Some),
222 None => Ok(None),
223 }
224}
225
226pub fn delete(v: &Value, key: &str) -> Result<Option<bool>, String> {
228 if let Some((t, target, handler)) = trap(v, "deleteProperty")? {
229 let k = key_value(key);
230 let r = call(&t, &handler, vec![target.clone(), k])?;
231 let reported = with_host(|h| h.truthy(&r));
232 if reported {
234 if let Some((_, _, configurable, _)) = crate::builtins::own_prop_facts(&target, key) {
235 if !configurable {
236 return Err(invariant(&format!(
237 "'deleteProperty' on proxy: trap returned truish for property '{key}' which is non-configurable in the proxy target"
238 )));
239 }
240 }
241 }
242 return Ok(Some(reported));
243 }
244 match no_trap(v, "deleteProperty")? {
245 Some(target) => crate::builtins::delete_property(&target, key).map(Some),
246 None => Ok(None),
247 }
248}
249
250pub fn own_keys(v: &Value) -> Result<Option<Vec<String>>, String> {
253 if let Some((t, target, handler)) = trap(v, "ownKeys")? {
254 let r = call(&t, &handler, vec![target.clone()])?;
255 let items = with_host(|h| h.iter_vec(&r))?;
256 let mut out = Vec::with_capacity(items.len());
257 for k in items {
258 out.push(host::to_property_key(&k)?);
259 }
260 let mut seen: Vec<&String> = Vec::with_capacity(out.len());
262 for k in &out {
263 if seen.contains(&k) {
264 return Err(invariant(&format!(
265 "'ownKeys' on proxy: trap returned duplicate entries for property '{k}'"
266 )));
267 }
268 seen.push(k);
269 }
270 let target_keys = with_host(|h| {
272 let mut ks = h.own_key_names(&target, false);
273 ks.extend(
274 h.own_symbol_keys(&target)
275 .iter()
276 .map(|sym| h.property_key(sym))
277 .collect::<Vec<_>>(),
278 );
279 ks
280 });
281 for k in &target_keys {
282 let pinned =
283 crate::builtins::own_prop_facts(&target, k).is_some_and(|(_, _, conf, _)| !conf);
284 if pinned && !out.contains(k) {
285 return Err(invariant(&format!(
286 "'ownKeys' on proxy: trap result did not include '{k}'"
287 )));
288 }
289 }
290 if !with_host(|h| h.is_extensible(&target)) {
292 for k in &target_keys {
293 if !out.contains(k) {
294 return Err(invariant(&format!(
295 "'ownKeys' on proxy: trap result did not include '{k}'"
296 )));
297 }
298 }
299 for k in &out {
300 if !target_keys.contains(k) {
301 return Err(invariant(
302 "'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible",
303 ));
304 }
305 }
306 }
307 return Ok(Some(out));
308 }
309 match no_trap(v, "ownKeys")? {
310 Some(target) => {
311 let mut keys = with_host(|h| h.own_key_names(&target, false));
312 keys.extend(with_host(|h| {
313 h.own_symbol_keys(&target)
314 .iter()
315 .map(|s| h.property_key(s))
316 .collect::<Vec<_>>()
317 }));
318 Ok(Some(keys))
319 }
320 None => Ok(None),
321 }
322}
323
324pub fn get_own_descriptor(v: &Value, key: &str) -> Result<Option<Value>, String> {
326 if let Some((t, target, handler)) = trap(v, "getOwnPropertyDescriptor")? {
327 let k = key_value(key);
328 let d = call(&t, &handler, vec![target.clone(), k])?;
329 if matches!(d, Value::Undef) {
331 if let Some((_, _, configurable, _)) = crate::builtins::own_prop_facts(&target, key) {
332 if !configurable {
333 return Err(invariant(&format!(
334 "'getOwnPropertyDescriptor' on proxy: trap returned undefined for property '{key}' which is non-configurable in the proxy target"
335 )));
336 }
337 }
338 }
339 return Ok(Some(d));
340 }
341 match no_trap(v, "getOwnPropertyDescriptor")? {
342 Some(target) => {
343 let k = key_value(key);
344 crate::builtins::own_descriptor_pub(&target, k).map(Some)
345 }
346 None => Ok(None),
347 }
348}
349
350pub fn define_property(v: &Value, key: &str, desc: &Value) -> Result<bool, String> {
352 if let Some((t, target, handler)) = trap(v, "defineProperty")? {
353 let k = key_value(key);
354 let r = call(&t, &handler, vec![target.clone(), k, desc.clone()])?;
355 if !with_host(|h| h.truthy(&r)) {
361 return Ok(false);
362 }
363 if crate::builtins::own_prop_facts(&target, key).is_none()
365 && !with_host(|h| h.is_extensible(&target))
366 {
367 return Err(invariant(&format!(
368 "'defineProperty' on proxy: trap returned truish for adding property '{key}' to the non-extensible proxy target"
369 )));
370 }
371 return Ok(true);
372 }
373 match no_trap(v, "defineProperty")? {
374 Some(target) => {
375 let k = key_value(key);
376 crate::builtins::define_property_pub(&target, k, desc.clone())?;
377 Ok(true)
378 }
379 None => Ok(false),
380 }
381}
382
383pub fn get_prototype_of(v: &Value) -> Result<Option<Value>, String> {
385 if let Some((t, target, handler)) = trap(v, "getPrototypeOf")? {
386 let reported = call(&t, &handler, vec![target.clone()])?;
387 if !with_host(|h| h.is_extensible(&target)) {
390 let actual = crate::builtins::prototype_of(&target);
391 if !with_host(|h| h.strict_eq(&reported, &actual)) {
392 return Err(invariant(
393 "'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype",
394 ));
395 }
396 }
397 return Ok(Some(reported));
398 }
399 match no_trap(v, "getPrototypeOf")? {
400 Some(target) => Ok(Some(crate::builtins::prototype_of(&target))),
401 None => Ok(None),
402 }
403}
404
405pub fn set_prototype_of(v: &Value, proto: &Value) -> Result<bool, String> {
407 if let Some((t, target, handler)) = trap(v, "setPrototypeOf")? {
408 call(&t, &handler, vec![target, proto.clone()])?;
409 return Ok(true);
410 }
411 match no_trap(v, "setPrototypeOf")? {
412 Some(target) => {
413 with_host(|h| h.set_proto(&target, proto.clone()));
414 Ok(true)
415 }
416 None => Ok(false),
417 }
418}
419
420pub fn is_extensible(v: &Value) -> Result<Option<bool>, String> {
422 if let Some((t, target, handler)) = trap(v, "isExtensible")? {
423 let reported = call(&t, &handler, vec![target.clone()])?;
426 let reported = with_host(|h| h.truthy(&reported));
427 if reported != with_host(|h| h.is_extensible(&target)) {
428 return Err(invariant(
429 "'isExtensible' on proxy: trap result does not reflect extensibility of proxy target",
430 ));
431 }
432 return Ok(Some(reported));
433 }
434 match no_trap(v, "isExtensible")? {
435 Some(target) => Ok(Some(with_host(|h| h.is_extensible(&target)))),
436 None => Ok(None),
437 }
438}
439
440pub fn prevent_extensions(v: &Value) -> Result<bool, String> {
442 if let Some((t, target, handler)) = trap(v, "preventExtensions")? {
443 call(&t, &handler, vec![target])?;
444 return Ok(true);
445 }
446 match no_trap(v, "preventExtensions")? {
447 Some(target) => {
448 with_host(|h| h.prevent_extensions(&target));
449 Ok(true)
450 }
451 None => Ok(false),
452 }
453}
454
455pub fn apply(v: &Value, args: Vec<Value>, this: Option<Value>) -> Result<Option<Value>, String> {
457 if let Some((t, target, handler)) = trap(v, "apply")? {
458 let this_arg = this.unwrap_or(Value::Undef);
459 let list = with_host(|h| h.new_array(args));
460 return call(&t, &handler, vec![target, this_arg, list]).map(Some);
461 }
462 match no_trap(v, "apply")? {
463 Some(target) => host::invoke(&target, args, this).map(Some),
464 None => Ok(None),
465 }
466}
467
468pub fn construct(v: &Value, args: Vec<Value>, new_target: &Value) -> Result<Option<Value>, String> {
470 if let Some((t, target, handler)) = trap(v, "construct")? {
471 let list = with_host(|h| h.new_array(args));
472 return call(&t, &handler, vec![target, list, new_target.clone()]).map(Some);
473 }
474 match no_trap(v, "construct")? {
475 Some(target) => host::construct_nt(&target, args, new_target.clone()).map(Some),
476 None => Ok(None),
477 }
478}
479
480pub fn own_enum_string_keys(v: &Value) -> Result<Vec<String>, String> {
487 let Some(keys) = own_keys(v)? else {
488 return Ok(Vec::new());
489 };
490 let mut out = Vec::new();
491 for k in keys {
492 if host::is_symbol_key(&k) {
493 continue;
494 }
495 let Some(d) = get_own_descriptor(v, &k)? else {
496 continue;
497 };
498 let enumerable = with_host(|h| match h.get(&d) {
499 Some(JsObj::Object(p)) => p.get("enumerable").map(|e| h.truthy(e)).unwrap_or(false),
500 _ => false,
501 });
502 if enumerable {
503 out.push(k);
504 }
505 }
506 Ok(out)
507}
508
509pub fn own_enumerable(v: &Value, key: &str) -> Result<bool, String> {
515 let Some(d) = get_own_descriptor(v, key)? else {
516 return Ok(false);
517 };
518 Ok(with_host(|h| match h.get(&d) {
519 Some(JsObj::Object(p)) => p.get("enumerable").map(|e| h.truthy(e)).unwrap_or(false),
520 _ => false,
521 }))
522}
523
524pub fn own_enum_entries(v: &Value) -> Result<Vec<(String, Value)>, String> {
528 let keys = own_enum_string_keys(v)?;
529 let mut out = Vec::with_capacity(keys.len());
530 for k in keys {
531 let val = get(v, &k, v)?.unwrap_or(Value::Undef);
532 out.push((k, val));
533 }
534 Ok(out)
535}
536
537fn wraps_array(v: &Value) -> bool {
540 match ultimate_target(v) {
541 Some(t) => with_host(|h| matches!(h.get(&t), Some(JsObj::Array(_)))),
542 None => false,
543 }
544}
545
546pub fn iterate(v: &Value) -> Result<Option<Vec<Value>>, String> {
554 if parts(v).is_none() {
555 return Ok(None);
556 }
557 let array_backed = wraps_array(v);
558 let iter_fn = get(v, "@@iterator", v)?.unwrap_or(Value::Undef);
559 let default_array_iter =
567 array_backed && with_host(|h| matches!(h.get(&iter_fn), Some(JsObj::BoundMethod { .. })));
568 if !default_array_iter && with_host(|h| host::is_callable(h, &iter_fn)) {
569 let iterator = host::invoke(&iter_fn, Vec::new(), Some(v.clone()))?;
570 return host::drain_iterator(&iterator).map(Some);
571 }
572 if array_backed {
573 let len_v = get(v, "length", v)?.unwrap_or(Value::Undef);
574 let len = with_host(|h| h.to_number(&len_v));
575 let len = if len.is_finite() && len > 0.0 {
576 len as usize
577 } else {
578 0
579 };
580 let mut out = Vec::with_capacity(len);
581 for i in 0..len {
582 out.push(get(v, &i.to_string(), v)?.unwrap_or(Value::Undef));
583 }
584 return Ok(Some(out));
585 }
586 let target = no_trap(v, "get")?.expect("checked it is a proxy");
587 host::iter_all(&target).map(Some)
588}
589
590pub fn json_snapshot(v: &Value) -> Result<Value, String> {
594 if wraps_array(v) {
595 let items = iterate(v)?.unwrap_or_default();
596 return Ok(with_host(|h| h.new_array(items)));
597 }
598 let entries = own_enum_entries(v)?;
599 Ok(with_host(|h| {
600 let mut m = indexmap::IndexMap::new();
601 for (k, val) in entries {
602 m.insert(k, val);
603 }
604 h.new_object(m)
605 }))
606}
607
608pub fn create(args: &[Value]) -> Result<Value, String> {
612 let target = args.first().cloned().unwrap_or(Value::Undef);
613 let handler = args.get(1).cloned().unwrap_or(Value::Undef);
614 let ok = |v: &Value| {
615 with_host(|h| matches!(v, Value::Obj(_)) && !h.is_null(v) && !host::is_primitive(h, v))
616 };
617 if !ok(&target) || !ok(&handler) {
618 return Err(host::type_error(
619 "Cannot create proxy with a non-object as target or handler",
620 ));
621 }
622 Ok(with_host(|h| {
623 h.alloc(JsObj::Proxy {
624 target,
625 handler,
626 revoked: false,
627 })
628 }))
629}
630
631pub fn revocable(args: &[Value]) -> Result<Value, String> {
635 let proxy = create(args)?;
636 let idx = match proxy {
637 Value::Obj(i) => i,
638 _ => unreachable!("create returns a heap object"),
639 };
640 let revoke = with_host(|h| h.alloc(JsObj::Builtin(format!("@@prevoke:{idx}"))));
641 Ok(with_host(|h| {
642 let mut m = indexmap::IndexMap::new();
643 m.insert("proxy".to_string(), proxy);
644 m.insert("revoke".to_string(), revoke);
645 h.new_object(m)
646 }))
647}
648
649pub fn revoke(idx: u32) -> Value {
657 with_host(|h| {
658 if let Some(JsObj::Proxy { revoked, .. }) = h.get_mut(&Value::Obj(idx)) {
659 *revoked = true;
660 }
661 });
662 Value::Undef
663}