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
58fn trap(v: &Value, name: &str) -> Result<Option<(Value, Value, Value)>, String> {
64 let Some((target, handler)) = parts(v) else {
65 return Ok(None);
66 };
67 if revoked(v) {
68 return Err(revoked_err(name));
69 }
70 let t = crate::builtins::get_property(&handler, name)?;
71 if matches!(t, Value::Undef) || with_host(|h| h.is_null(&t)) {
72 return Ok(None);
73 }
74 if !with_host(|h| host::is_callable(h, &t)) {
75 return Err(host::type_error(&format!(
76 "'{}' returned for property '{name}' of object '#<Object>' is not a function",
77 with_host(|h| h.str_of(&t))
78 )));
79 }
80 Ok(Some((t, target, handler)))
81}
82
83fn no_trap(v: &Value, op: &str) -> Result<Option<Value>, String> {
86 match parts(v) {
87 None => Ok(None),
88 Some((target, _)) if !revoked(v) => Ok(Some(target)),
89 Some(_) => Err(revoked_err(op)),
90 }
91}
92
93pub fn key_value(k: &str) -> Value {
97 with_host(|h| {
98 if let Some(s) = h.symbol_of_key(k) {
99 return s;
100 }
101 match k.strip_prefix("@@") {
102 Some(name) if host::WELL_KNOWN_SYMBOLS.contains(&name) => h.well_known_symbol(name),
103 _ => h.new_str(k),
104 }
105 })
106}
107
108fn call(t: &Value, handler: &Value, args: Vec<Value>) -> Result<Value, String> {
109 host::invoke(t, args, Some(handler.clone()))
110}
111
112pub fn get(v: &Value, key: &str, receiver: &Value) -> Result<Option<Value>, String> {
116 if let Some((t, target, handler)) = trap(v, "get")? {
117 let k = key_value(key);
118 return call(&t, &handler, vec![target, k, receiver.clone()]).map(Some);
119 }
120 match no_trap(v, "get")? {
121 Some(target) => crate::builtins::get_property_recv(&target, key, receiver).map(Some),
122 None => Ok(None),
123 }
124}
125
126pub fn set(v: &Value, key: &str, val: &Value, receiver: &Value) -> Result<bool, String> {
128 if let Some((t, target, handler)) = trap(v, "set")? {
129 let k = key_value(key);
130 call(&t, &handler, vec![target, k, val.clone(), receiver.clone()])?;
131 return Ok(true);
132 }
133 match no_trap(v, "set")? {
134 Some(target) => {
135 crate::builtins::set_property_pub(&target, key, val.clone())?;
136 Ok(true)
137 }
138 None => Ok(false),
139 }
140}
141
142pub fn has(v: &Value, key: &str) -> Result<Option<bool>, String> {
144 if let Some((t, target, handler)) = trap(v, "has")? {
145 let k = key_value(key);
146 let r = call(&t, &handler, vec![target, k])?;
147 return Ok(Some(with_host(|h| h.truthy(&r))));
148 }
149 match no_trap(v, "has")? {
150 Some(target) => crate::builtins::has_property(&target, key).map(Some),
151 None => Ok(None),
152 }
153}
154
155pub fn delete(v: &Value, key: &str) -> Result<Option<bool>, String> {
157 if let Some((t, target, handler)) = trap(v, "deleteProperty")? {
158 let k = key_value(key);
159 let r = call(&t, &handler, vec![target, k])?;
160 return Ok(Some(with_host(|h| h.truthy(&r))));
161 }
162 match no_trap(v, "deleteProperty")? {
163 Some(target) => crate::builtins::delete_property(&target, key).map(Some),
164 None => Ok(None),
165 }
166}
167
168pub fn own_keys(v: &Value) -> Result<Option<Vec<String>>, String> {
171 if let Some((t, target, handler)) = trap(v, "ownKeys")? {
172 let r = call(&t, &handler, vec![target])?;
173 let items = with_host(|h| h.iter_vec(&r))?;
174 let mut out = Vec::with_capacity(items.len());
175 for k in items {
176 out.push(host::to_property_key(&k)?);
177 }
178 return Ok(Some(out));
179 }
180 match no_trap(v, "ownKeys")? {
181 Some(target) => {
182 let mut keys = with_host(|h| h.own_key_names(&target, false));
183 keys.extend(with_host(|h| {
184 h.own_symbol_keys(&target)
185 .iter()
186 .map(|s| h.property_key(s))
187 .collect::<Vec<_>>()
188 }));
189 Ok(Some(keys))
190 }
191 None => Ok(None),
192 }
193}
194
195pub fn get_own_descriptor(v: &Value, key: &str) -> Result<Option<Value>, String> {
197 if let Some((t, target, handler)) = trap(v, "getOwnPropertyDescriptor")? {
198 let k = key_value(key);
199 return call(&t, &handler, vec![target, k]).map(Some);
200 }
201 match no_trap(v, "getOwnPropertyDescriptor")? {
202 Some(target) => {
203 let k = key_value(key);
204 crate::builtins::own_descriptor_pub(&target, k).map(Some)
205 }
206 None => Ok(None),
207 }
208}
209
210pub fn define_property(v: &Value, key: &str, desc: &Value) -> Result<bool, String> {
212 if let Some((t, target, handler)) = trap(v, "defineProperty")? {
213 let k = key_value(key);
214 call(&t, &handler, vec![target, k, desc.clone()])?;
215 return Ok(true);
216 }
217 match no_trap(v, "defineProperty")? {
218 Some(target) => {
219 let k = key_value(key);
220 crate::builtins::define_property_pub(&target, k, desc.clone())?;
221 Ok(true)
222 }
223 None => Ok(false),
224 }
225}
226
227pub fn get_prototype_of(v: &Value) -> Result<Option<Value>, String> {
229 if let Some((t, target, handler)) = trap(v, "getPrototypeOf")? {
230 return call(&t, &handler, vec![target]).map(Some);
231 }
232 match no_trap(v, "getPrototypeOf")? {
233 Some(target) => Ok(Some(crate::builtins::prototype_of(&target))),
234 None => Ok(None),
235 }
236}
237
238pub fn set_prototype_of(v: &Value, proto: &Value) -> Result<bool, String> {
240 if let Some((t, target, handler)) = trap(v, "setPrototypeOf")? {
241 call(&t, &handler, vec![target, proto.clone()])?;
242 return Ok(true);
243 }
244 match no_trap(v, "setPrototypeOf")? {
245 Some(target) => {
246 with_host(|h| h.set_proto(&target, proto.clone()));
247 Ok(true)
248 }
249 None => Ok(false),
250 }
251}
252
253pub fn is_extensible(v: &Value) -> Result<Option<bool>, String> {
255 if let Some((t, target, handler)) = trap(v, "isExtensible")? {
256 let r = call(&t, &handler, vec![target])?;
257 return Ok(Some(with_host(|h| h.truthy(&r))));
258 }
259 match no_trap(v, "isExtensible")? {
260 Some(target) => Ok(Some(with_host(|h| h.is_extensible(&target)))),
261 None => Ok(None),
262 }
263}
264
265pub fn prevent_extensions(v: &Value) -> Result<bool, String> {
267 if let Some((t, target, handler)) = trap(v, "preventExtensions")? {
268 call(&t, &handler, vec![target])?;
269 return Ok(true);
270 }
271 match no_trap(v, "preventExtensions")? {
272 Some(target) => {
273 with_host(|h| h.prevent_extensions(&target));
274 Ok(true)
275 }
276 None => Ok(false),
277 }
278}
279
280pub fn apply(v: &Value, args: Vec<Value>, this: Option<Value>) -> Result<Option<Value>, String> {
282 if let Some((t, target, handler)) = trap(v, "apply")? {
283 let this_arg = this.unwrap_or(Value::Undef);
284 let list = with_host(|h| h.new_array(args));
285 return call(&t, &handler, vec![target, this_arg, list]).map(Some);
286 }
287 match no_trap(v, "apply")? {
288 Some(target) => host::invoke(&target, args, this).map(Some),
289 None => Ok(None),
290 }
291}
292
293pub fn construct(v: &Value, args: Vec<Value>, new_target: &Value) -> Result<Option<Value>, String> {
295 if let Some((t, target, handler)) = trap(v, "construct")? {
296 let list = with_host(|h| h.new_array(args));
297 return call(&t, &handler, vec![target, list, new_target.clone()]).map(Some);
298 }
299 match no_trap(v, "construct")? {
300 Some(target) => host::construct_nt(&target, args, new_target.clone()).map(Some),
301 None => Ok(None),
302 }
303}
304
305pub fn own_enum_string_keys(v: &Value) -> Result<Vec<String>, String> {
312 let Some(keys) = own_keys(v)? else {
313 return Ok(Vec::new());
314 };
315 let mut out = Vec::new();
316 for k in keys {
317 if host::is_symbol_key(&k) {
318 continue;
319 }
320 let Some(d) = get_own_descriptor(v, &k)? else {
321 continue;
322 };
323 let enumerable = with_host(|h| match h.get(&d) {
324 Some(JsObj::Object(p)) => p.get("enumerable").map(|e| h.truthy(e)).unwrap_or(false),
325 _ => false,
326 });
327 if enumerable {
328 out.push(k);
329 }
330 }
331 Ok(out)
332}
333
334pub fn own_enum_entries(v: &Value) -> Result<Vec<(String, Value)>, String> {
338 let keys = own_enum_string_keys(v)?;
339 let mut out = Vec::with_capacity(keys.len());
340 for k in keys {
341 let val = get(v, &k, v)?.unwrap_or(Value::Undef);
342 out.push((k, val));
343 }
344 Ok(out)
345}
346
347fn wraps_array(v: &Value) -> bool {
350 match ultimate_target(v) {
351 Some(t) => with_host(|h| matches!(h.get(&t), Some(JsObj::Array(_)))),
352 None => false,
353 }
354}
355
356pub fn iterate(v: &Value) -> Result<Option<Vec<Value>>, String> {
364 if parts(v).is_none() {
365 return Ok(None);
366 }
367 let array_backed = wraps_array(v);
368 let iter_fn = get(v, "@@iterator", v)?.unwrap_or(Value::Undef);
369 let default_array_iter =
377 array_backed && with_host(|h| matches!(h.get(&iter_fn), Some(JsObj::BoundMethod { .. })));
378 if !default_array_iter && with_host(|h| host::is_callable(h, &iter_fn)) {
379 let iterator = host::invoke(&iter_fn, Vec::new(), Some(v.clone()))?;
380 return host::drain_iterator(&iterator).map(Some);
381 }
382 if array_backed {
383 let len_v = get(v, "length", v)?.unwrap_or(Value::Undef);
384 let len = with_host(|h| h.to_number(&len_v));
385 let len = if len.is_finite() && len > 0.0 {
386 len as usize
387 } else {
388 0
389 };
390 let mut out = Vec::with_capacity(len);
391 for i in 0..len {
392 out.push(get(v, &i.to_string(), v)?.unwrap_or(Value::Undef));
393 }
394 return Ok(Some(out));
395 }
396 let target = no_trap(v, "get")?.expect("checked it is a proxy");
397 host::iter_all(&target).map(Some)
398}
399
400pub fn json_snapshot(v: &Value) -> Result<Value, String> {
404 if wraps_array(v) {
405 let items = iterate(v)?.unwrap_or_default();
406 return Ok(with_host(|h| h.new_array(items)));
407 }
408 let entries = own_enum_entries(v)?;
409 Ok(with_host(|h| {
410 let mut m = indexmap::IndexMap::new();
411 for (k, val) in entries {
412 m.insert(k, val);
413 }
414 h.new_object(m)
415 }))
416}
417
418pub fn create(args: &[Value]) -> Result<Value, String> {
422 let target = args.first().cloned().unwrap_or(Value::Undef);
423 let handler = args.get(1).cloned().unwrap_or(Value::Undef);
424 let ok = |v: &Value| {
425 with_host(|h| matches!(v, Value::Obj(_)) && !h.is_null(v) && !host::is_primitive(h, v))
426 };
427 if !ok(&target) || !ok(&handler) {
428 return Err(host::type_error(
429 "Cannot create proxy with a non-object as target or handler",
430 ));
431 }
432 Ok(with_host(|h| {
433 h.alloc(JsObj::Proxy {
434 target,
435 handler,
436 revoked: false,
437 })
438 }))
439}
440
441pub fn revocable(args: &[Value]) -> Result<Value, String> {
445 let proxy = create(args)?;
446 let idx = match proxy {
447 Value::Obj(i) => i,
448 _ => unreachable!("create returns a heap object"),
449 };
450 let revoke = with_host(|h| h.alloc(JsObj::Builtin(format!("@@prevoke:{idx}"))));
451 Ok(with_host(|h| {
452 let mut m = indexmap::IndexMap::new();
453 m.insert("proxy".to_string(), proxy);
454 m.insert("revoke".to_string(), revoke);
455 h.new_object(m)
456 }))
457}
458
459pub fn revoke(idx: u32) -> Value {
467 with_host(|h| {
468 if let Some(JsObj::Proxy { revoked, .. }) = h.get_mut(&Value::Obj(idx)) {
469 *revoked = true;
470 }
471 });
472 Value::Undef
473}