Skip to content

Commit e8c7c1e

Browse files
committed
Implement Proxy constructor with revocable support
- Add JSProxy struct and Value::Proxy variant - Implement proxy wrapper objects with __proxy__ property - Add Proxy constructor and Proxy.revocable static method - Implement get and set traps for property access - Add special handling for revoke function calls - Update all relevant functions to handle Proxy values - Add comprehensive tests for basic proxy and revocable functionality
1 parent 553382c commit e8c7c1e

File tree

12 files changed

+391
-9
lines changed

12 files changed

+391
-9
lines changed

examples/js.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ fn print_eval_result(result: &Value) {
7373
Value::WeakSet(_) => println!("[object WeakSet]"),
7474
Value::GeneratorFunction(_, _, _) => println!("[GeneratorFunction]"),
7575
Value::Generator(_) => println!("[object Generator]"),
76+
Value::Proxy(_) => println!("[object Proxy]"),
7677
}
7778
}
7879

src/core.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ pub(crate) fn filter_input_script(script: &str) -> String {
383383
}
384384

385385
/// Initialize global built-in constructors in the environment
386-
pub(crate) fn initialize_global_constructors(env: &JSObjectDataPtr) -> Result<(), JSError> {
386+
pub fn initialize_global_constructors(env: &JSObjectDataPtr) -> Result<(), JSError> {
387387
let mut env_borrow = env.borrow_mut();
388388

389389
// Object constructor (object with static methods) and Object.prototype
@@ -504,6 +504,12 @@ pub(crate) fn initialize_global_constructors(env: &JSObjectDataPtr) -> Result<()
504504
Rc::new(RefCell::new(Value::Function("Set".to_string()))),
505505
);
506506

507+
// Proxy constructor
508+
env_borrow.insert(
509+
PropertyKey::String("Proxy".to_string()),
510+
Rc::new(RefCell::new(Value::Function("Proxy".to_string()))),
511+
);
512+
507513
// WeakMap constructor
508514
env_borrow.insert(
509515
PropertyKey::String("WeakMap".to_string()),

src/core/eval.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,6 +2038,16 @@ fn evaluate_var(env: &JSObjectDataPtr, name: &str) -> Result<Value, JSError> {
20382038
let v = Value::Function("Promise".to_string());
20392039
log::trace!("evaluate_var - {} -> {:?}", name, v);
20402040
Ok(v)
2041+
} else if name == "Proxy" {
2042+
// Return the Proxy constructor (we store it in the global environment)
2043+
if let Some(val_rc) = obj_get_value(env, &"Proxy".into())? {
2044+
let resolved = val_rc.borrow().clone();
2045+
log::trace!("evaluate_var - {} -> {:?}", name, resolved);
2046+
return Ok(resolved);
2047+
}
2048+
let v = Value::Function("Proxy".to_string());
2049+
log::trace!("evaluate_var - {} -> {:?}", name, v);
2050+
Ok(v)
20412051
} else if name == "new" {
20422052
let v = Value::Function("new".to_string());
20432053
log::trace!("evaluate_var - {} -> {:?}", name, v);
@@ -2835,6 +2845,7 @@ fn evaluate_typeof(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSError>
28352845
Value::WeakMap(_) => "object",
28362846
Value::WeakSet(_) => "object",
28372847
Value::Generator(_) => "object",
2848+
Value::Proxy(_) => "object",
28382849
};
28392850
Ok(Value::String(utf8_to_utf16(type_str)))
28402851
}
@@ -2850,7 +2861,7 @@ fn evaluate_delete(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSError>
28502861
let obj_val = evaluate_expr(env, obj)?;
28512862
match obj_val {
28522863
Value::Object(obj_map) => {
2853-
let deleted = obj_delete(&obj_map, &prop.into());
2864+
let deleted = obj_delete(&obj_map, &prop.into())?;
28542865
Ok(Value::Boolean(deleted))
28552866
}
28562867
_ => Ok(Value::Boolean(false)),
@@ -2863,17 +2874,17 @@ fn evaluate_delete(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSError>
28632874
match (obj_val, idx_val) {
28642875
(Value::Object(obj_map), Value::String(s)) => {
28652876
let key = PropertyKey::String(String::from_utf16_lossy(&s));
2866-
let deleted = obj_delete(&obj_map, &key);
2877+
let deleted = obj_delete(&obj_map, &key)?;
28672878
Ok(Value::Boolean(deleted))
28682879
}
28692880
(Value::Object(obj_map), Value::Number(n)) => {
28702881
let key = PropertyKey::String(n.to_string());
2871-
let deleted = obj_delete(&obj_map, &key);
2882+
let deleted = obj_delete(&obj_map, &key)?;
28722883
Ok(Value::Boolean(deleted))
28732884
}
28742885
(Value::Object(obj_map), Value::Symbol(sym)) => {
28752886
let key = PropertyKey::Symbol(Rc::new(RefCell::new(Value::Symbol(sym))));
2876-
let deleted = obj_delete(&obj_map, &key);
2887+
let deleted = obj_delete(&obj_map, &key)?;
28772888
Ok(Value::Boolean(deleted))
28782889
}
28792890
_ => Ok(Value::Boolean(false)),
@@ -3762,6 +3773,8 @@ fn evaluate_property(env: &JSObjectDataPtr, obj: &Expr, prop: &str) -> Result<Va
37623773
"Property not found for Symbol constructor property: {prop}"
37633774
)))
37643775
});
3776+
} else if func_name == "Proxy" && prop == "revocable" {
3777+
return Ok(Value::Function("Proxy.revocable".to_string()));
37653778
}
37663779

37673780
Err(raise_eval_error!(format!("Property not found for prop={prop}")))
@@ -3891,6 +3904,14 @@ fn evaluate_call(env: &JSObjectDataPtr, func_expr: &Expr, args: &[Expr]) -> Resu
38913904
return handle_symbol_static_method(method_name, args, env);
38923905
}
38933906

3907+
// Special case for Proxy static methods
3908+
if let Expr::Var(var_name) = &**obj_expr
3909+
&& var_name == "Proxy"
3910+
&& method_name == "revocable"
3911+
{
3912+
return crate::js_proxy::handle_proxy_revocable(args, env);
3913+
}
3914+
38943915
let obj_val = evaluate_expr(env, obj_expr)?;
38953916
log::trace!("evaluate_call - object evaluated");
38963917
match (obj_val, method_name.as_str()) {
@@ -4150,6 +4171,11 @@ fn evaluate_call(env: &JSObjectDataPtr, func_expr: &Expr, args: &[Expr]) -> Resu
41504171
// Regular function call
41514172
let func_val = evaluate_expr(env, func_expr)?;
41524173
match func_val {
4174+
Value::Proxy(proxy) => {
4175+
// Special case: calling a proxy directly (assumed to be revoke function)
4176+
proxy.borrow_mut().revoked = true;
4177+
Ok(Value::Undefined)
4178+
}
41534179
Value::Function(func_name) => crate::js_function::handle_global_function(&func_name, args, env),
41544180
Value::GeneratorFunction(params, body, captured_env) => {
41554181
// Generator function call - return a generator object

src/core/ffi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,7 @@ pub unsafe fn JS_Eval(_ctx: *mut JSContext, input: *const i8, input_len: usize,
949949
Ok(Value::WeakSet(_)) => JS_UNDEFINED, // For now
950950
Ok(Value::GeneratorFunction(_, _, _)) => JS_UNDEFINED, // For now
951951
Ok(Value::Generator(_)) => JS_UNDEFINED, // For now
952+
Ok(Value::Proxy(_)) => JS_UNDEFINED, // For now
952953
Err(_) => JS_UNDEFINED,
953954
}
954955
}

src/core/value.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
js_array::is_array,
77
js_class::ClassDefinition,
88
js_promise::JSPromise,
9-
raise_type_error,
9+
raise_eval_error, raise_type_error,
1010
};
1111

1212
#[derive(Clone, Debug)]
@@ -37,6 +37,13 @@ pub struct JSGenerator {
3737
pub state: GeneratorState,
3838
}
3939

40+
#[derive(Clone, Debug)]
41+
pub struct JSProxy {
42+
pub target: Value, // The target object being proxied
43+
pub handler: Value, // The handler object with traps
44+
pub revoked: bool, // Whether this proxy has been revoked
45+
}
46+
4047
#[derive(Clone, Debug)]
4148
pub enum GeneratorState {
4249
NotStarted,
@@ -136,6 +143,7 @@ pub enum Value {
136143
WeakMap(Rc<RefCell<JSWeakMap>>), // WeakMap object
137144
WeakSet(Rc<RefCell<JSWeakSet>>), // WeakSet object
138145
Generator(Rc<RefCell<JSGenerator>>), // Generator object
146+
Proxy(Rc<RefCell<JSProxy>>), // Proxy object
139147
}
140148

141149
impl std::fmt::Debug for Value {
@@ -162,6 +170,7 @@ impl std::fmt::Debug for Value {
162170
Value::WeakMap(wm) => write!(f, "WeakMap({:p})", Rc::as_ptr(wm)),
163171
Value::WeakSet(ws) => write!(f, "WeakSet({:p})", Rc::as_ptr(ws)),
164172
Value::Generator(g) => write!(f, "Generator({:p})", Rc::as_ptr(g)),
173+
Value::Proxy(p) => write!(f, "Proxy({:p})", Rc::as_ptr(p)),
165174
}
166175
}
167176
}
@@ -203,6 +212,7 @@ pub fn is_truthy(val: &Value) -> bool {
203212
Value::WeakMap(_) => true,
204213
Value::WeakSet(_) => true,
205214
Value::Generator(_) => true,
215+
Value::Proxy(_) => true,
206216
}
207217
}
208218

@@ -247,6 +257,7 @@ pub fn value_to_string(val: &Value) -> String {
247257
Value::WeakMap(_) => "[object WeakMap]".to_string(),
248258
Value::WeakSet(_) => "[object WeakSet]".to_string(),
249259
Value::Generator(_) => "[object Generator]".to_string(),
260+
Value::Proxy(_) => "[object Proxy]".to_string(),
250261
}
251262
}
252263

@@ -346,11 +357,19 @@ pub fn value_to_sort_string(val: &Value) -> String {
346357
Value::WeakMap(_) => "[object WeakMap]".to_string(),
347358
Value::WeakSet(_) => "[object WeakSet]".to_string(),
348359
Value::Generator(_) => "[object Generator]".to_string(),
360+
Value::Proxy(_) => "[object Proxy]".to_string(),
349361
}
350362
}
351363

352364
// Helper accessors for objects and environments
353365
pub fn obj_get_value(js_obj: &JSObjectDataPtr, key: &PropertyKey) -> Result<Option<Rc<RefCell<Value>>>, JSError> {
366+
// Check if this object is a proxy wrapper
367+
if let Some(proxy_val) = js_obj.borrow().get(&"__proxy__".into())
368+
&& let Value::Proxy(proxy) = &*proxy_val.borrow()
369+
{
370+
return crate::js_proxy::proxy_get_property(proxy, key);
371+
}
372+
354373
// Search own properties and then walk the prototype chain until we find
355374
// a matching property or run out of prototypes.
356375
let mut current: Option<JSObjectDataPtr> = Some(js_obj.clone());
@@ -690,6 +709,17 @@ pub fn obj_get_value(js_obj: &JSObjectDataPtr, key: &PropertyKey) -> Result<Opti
690709
}
691710

692711
pub fn obj_set_value(js_obj: &JSObjectDataPtr, key: &PropertyKey, val: Value) -> Result<(), JSError> {
712+
// Check if this object is a proxy wrapper
713+
if let Some(proxy_val) = js_obj.borrow().get(&"__proxy__".into())
714+
&& let Value::Proxy(proxy) = &*proxy_val.borrow()
715+
{
716+
let success = crate::js_proxy::proxy_set_property(proxy, key, val)?;
717+
if !success {
718+
return Err(raise_eval_error!("Proxy set trap returned false"));
719+
}
720+
return Ok(());
721+
}
722+
693723
// Check if there's a setter for this property
694724
let existing_opt = js_obj.borrow().get(key);
695725
if let Some(existing) = existing_opt {
@@ -731,9 +761,16 @@ pub fn obj_set_rc(map: &JSObjectDataPtr, key: &PropertyKey, val_rc: Rc<RefCell<V
731761
map.borrow_mut().insert(key.clone(), val_rc);
732762
}
733763

734-
pub fn obj_delete(map: &JSObjectDataPtr, key: &PropertyKey) -> bool {
764+
pub fn obj_delete(map: &JSObjectDataPtr, key: &PropertyKey) -> Result<bool, JSError> {
765+
// Check if this object is a proxy wrapper
766+
if let Some(proxy_val) = map.borrow().get(&"__proxy__".into())
767+
&& let Value::Proxy(proxy) = &*proxy_val.borrow()
768+
{
769+
return crate::js_proxy::proxy_delete_property(proxy, key);
770+
}
771+
735772
map.borrow_mut().remove(key);
736-
true // In JavaScript, delete always returns true
773+
Ok(true) // In JavaScript, delete always returns true
737774
}
738775

739776
pub fn env_get<T: AsRef<str>>(env: &JSObjectDataPtr, key: T) -> Option<Rc<RefCell<Value>>> {

src/js_class.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ pub(crate) fn evaluate_new(env: &JSObjectDataPtr, constructor: &Expr, args: &[Ex
170170
}
171171
"Map" => return crate::js_map::handle_map_constructor(args, env),
172172
"Set" => return crate::js_set::handle_set_constructor(args, env),
173+
"Proxy" => return crate::js_proxy::handle_proxy_constructor(args, env),
173174
"WeakMap" => return crate::js_weakmap::handle_weakmap_constructor(args, env),
174175
"WeakSet" => return crate::js_weakset::handle_weakset_constructor(args, env),
175176
"MockIntlConstructor" => {
@@ -674,6 +675,7 @@ pub(crate) fn handle_string_constructor(args: &[Expr], env: &JSObjectDataPtr) ->
674675
Value::WeakSet(_) => utf8_to_utf16("[object WeakSet]"),
675676
Value::GeneratorFunction(_, _, _) => utf8_to_utf16("[GeneratorFunction]"),
676677
Value::Generator(_) => utf8_to_utf16("[object Generator]"),
678+
Value::Proxy(_) => utf8_to_utf16("[object Proxy]"),
677679
}
678680
};
679681

src/js_console.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ pub fn handle_console_method(method: &str, args: &[Expr], env: &JSObjectDataPtr)
111111
Value::WeakSet(_) => print!("[object WeakSet]"),
112112
Value::GeneratorFunction(_, _, _) => print!("[GeneratorFunction]"),
113113
Value::Generator(_) => print!("[object Generator]"),
114+
Value::Proxy(_) => print!("[object Proxy]"),
114115
}
115116
if i < count - 1 {
116117
print!(" ");

src/js_function.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub fn handle_global_function(func_name: &str, args: &[Expr], env: &JSObjectData
8383
Value::WeakSet(_) => Ok(Value::String(utf8_to_utf16("[object WeakSet]"))),
8484
Value::GeneratorFunction(_, _, _) => Ok(Value::String(utf8_to_utf16("[GeneratorFunction]"))),
8585
Value::Generator(_) => Ok(Value::String(utf8_to_utf16("[object Generator]"))),
86+
Value::Proxy(_) => Ok(Value::String(utf8_to_utf16("[object Proxy]"))),
8687
}
8788
} else {
8889
Ok(Value::String(Vec::new())) // String() with no args returns empty string

src/js_object.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ pub(crate) fn handle_to_string_method(obj_val: &Value, args: &[Expr]) -> Result<
299299
Value::WeakSet(_) => "WeakSet",
300300
Value::GeneratorFunction(_, _, _) => "GeneratorFunction",
301301
Value::Generator(_) => "Generator",
302+
Value::Proxy(_) => "Proxy",
302303
},
303304
args.len()
304305
)));
@@ -375,6 +376,7 @@ pub(crate) fn handle_to_string_method(obj_val: &Value, args: &[Expr]) -> Result<
375376
Value::WeakSet(_) => Ok(Value::String(utf8_to_utf16("[object WeakSet]"))),
376377
Value::GeneratorFunction(_, _, _) => Ok(Value::String(utf8_to_utf16("[GeneratorFunction]"))),
377378
Value::Generator(_) => Ok(Value::String(utf8_to_utf16("[object Generator]"))),
379+
Value::Proxy(_) => Ok(Value::String(utf8_to_utf16("[object Proxy]"))),
378380
}
379381
}
380382

@@ -403,6 +405,7 @@ pub(crate) fn handle_value_of_method(obj_val: &Value, args: &[Expr]) -> Result<V
403405
Value::WeakSet(_) => "WeakSet",
404406
&Value::GeneratorFunction(_, _, _) => "GeneratorFunction",
405407
&Value::Generator(_) => "Generator",
408+
&Value::Proxy(_) => "Proxy",
406409
},
407410
args.len()
408411
)));
@@ -445,5 +448,6 @@ pub(crate) fn handle_value_of_method(obj_val: &Value, args: &[Expr]) -> Result<V
445448
Value::WeakSet(weakset) => Ok(Value::WeakSet(weakset.clone())),
446449
Value::GeneratorFunction(params, body, env) => Ok(Value::GeneratorFunction(params.clone(), body.clone(), env.clone())),
447450
Value::Generator(generator) => Ok(Value::Generator(generator.clone())),
451+
Value::Proxy(proxy) => Ok(Value::Proxy(proxy.clone())),
448452
}
449453
}

0 commit comments

Comments
 (0)