diff --git a/src/ant.c b/src/ant.c index 00a87a6..8a394b9 100644 --- a/src/ant.c +++ b/src/ant.c @@ -2,6 +2,8 @@ #pragma GCC optimize("O3,inline") #endif +#include + #include // IWYU pragma: keep #include "ant.h" @@ -5622,6 +5624,9 @@ ant_value_t extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_arg ant_value_t len_result = proxy_aware_length(js, arr, &raw_len); if (is_err(len_result)) return len_result; } + if (raw_len > (ant_offset_t)INT_MAX) { + return js_mkerr_typed(js, JS_ERR_RANGE, "Argument list length exceeds engine limit"); + } int len = (int)raw_len; if (len <= 0) return js_mkundef(); diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 6d9f8a9..6db8c67 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -2396,7 +2396,7 @@ void compile_delete(sv_compiler_t *c, sv_ast_t *node) { compile_expr(c, arg->right); emit_op(c, OP_DELETE); } else if (arg->type == N_IDENT) { - emit_atom_op(c, OP_DELETE_VAR, arg->str, arg->len); + emit_atom_op(c, c->with_depth > 0 ? OP_WITH_DEL_VAR : OP_DELETE_VAR, arg->str, arg->len); } else { compile_expr(c, arg); emit_op(c, OP_POP); diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index e31d831..8b09864 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -448,14 +448,23 @@ static inline ant_value_t sv_op_with_put_var( sv_atom_t *a = &func->atoms[atom_idx]; ant_value_t val = vm->stack[--vm->sp]; - if (vtype(frame->with_obj) != T_UNDEF) if (lkp(js, frame->with_obj, a->str, a->len) != 0) { - ant_value_t out = js_mkundef(); - bool abrupt = false; - if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; - if (abrupt) return out; - ant_value_t key = js_mkstr(js, a->str, a->len); - js_setprop(js, frame->with_obj, key, val); - return js_mkundef(); + if (vtype(frame->with_obj) != T_UNDEF) { + bool has_binding = false; + if (is_proxy(js_as_obj(frame->with_obj))) { + ant_value_t has = js_proxy_has(js, frame->with_obj, a->str, a->len); + if (is_err(has)) return has; + has_binding = js_truthy(js, has); + } else has_binding = lkp(js, frame->with_obj, a->str, a->len) != 0; + + if (has_binding) { + ant_value_t out = js_mkundef(); + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; + if (abrupt) return out; + ant_value_t key = js_mkstr(js, a->str, a->len); + js_setprop(js, frame->with_obj, key, val); + return js_mkundef(); + } } fallback: @@ -473,19 +482,30 @@ static inline ant_value_t sv_op_with_del_var( uint32_t atom_idx = sv_get_u32(ip + 1); sv_atom_t *a = &func->atoms[atom_idx]; - if (vtype(frame->with_obj) != T_UNDEF) if (lkp(js, frame->with_obj, a->str, a->len) != 0) { - ant_value_t out = js_mkundef(); - bool abrupt = false; - if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; - if (abrupt) return out; - ant_value_t result = js_delete_prop(js, frame->with_obj, a->str, a->len); - vm->stack[vm->sp++] = result; - return js_mkundef(); + if (vtype(frame->with_obj) != T_UNDEF) { + bool has_binding = false; + if (is_proxy(js_as_obj(frame->with_obj))) { + ant_value_t has = js_proxy_has(js, frame->with_obj, a->str, a->len); + if (is_err(has)) return has; + has_binding = js_truthy(js, has); + } else has_binding = lkp(js, frame->with_obj, a->str, a->len) != 0; + + if (has_binding) { + ant_value_t out = js_mkundef(); + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; + if (abrupt) return out; + ant_value_t result = js_delete_prop(js, frame->with_obj, a->str, a->len); + if (is_err(result)) return result; + vm->stack[vm->sp++] = result; + return js_mkundef(); + } } fallback: ant_value_t result = js_delete_prop(js, js->global, a->str, a->len); - bool ok = !is_err(result) && js_truthy(js, result); + if (is_err(result)) return result; + bool ok = js_truthy(js, result); vm->stack[vm->sp++] = mkval(T_BOOL, ok); return js_mkundef(); } diff --git a/tests/test_apply_argument_length_limit.cjs b/tests/test_apply_argument_length_limit.cjs new file mode 100644 index 0000000..03fa7a4 --- /dev/null +++ b/tests/test_apply_argument_length_limit.cjs @@ -0,0 +1,27 @@ +function assert(condition, message) { + if (!condition) { + console.log("FAIL: " + message); + throw new Error(message); + } +} + +const tooLong = { length: 2147483648 }; + +let applyThrew = false; +try { + (function () {}).apply(null, tooLong); +} catch (e) { + applyThrew = e instanceof RangeError; +} + +let reflectThrew = false; +try { + Reflect.apply(function () {}, null, tooLong); +} catch (e) { + reflectThrew = e instanceof RangeError; +} + +assert(applyThrew, "Function.prototype.apply should reject oversized argument lists"); +assert(reflectThrew, "Reflect.apply should reject oversized argument lists"); + +console.log("OK"); diff --git a/tests/test_with_proxy_write_delete.cjs b/tests/test_with_proxy_write_delete.cjs new file mode 100644 index 0000000..d9c8436 --- /dev/null +++ b/tests/test_with_proxy_write_delete.cjs @@ -0,0 +1,67 @@ +function assert(condition, message) { + if (!condition) { + console.log("FAIL: " + message); + throw new Error(message); + } +} + +globalThis.x = 1; +const events = []; +const target = {}; +const proxy = new Proxy(target, { + has(_target, key) { + events.push("has:" + String(key)); + return key === "x"; + }, + set(_target, key, value) { + events.push("set:" + String(key) + ":" + value); + target[key] = value; + return true; + }, + deleteProperty(_target, key) { + events.push("delete:" + String(key)); + delete target[key]; + return true; + }, +}); + +with (proxy) { + x = 2; +} + +assert(globalThis.x === 1, "proxy with assignment should not fall back to global"); +assert(target.x === 2, "proxy with assignment should write through proxy"); +assert(events.indexOf("has:x") >= 0, "proxy has trap should be used for assignment"); +assert(events.indexOf("set:x:2") >= 0, "proxy set trap should be used for assignment"); + +const deleteResult = (function () { + with (proxy) { + return delete x; + } +})(); + +assert(deleteResult === true, "proxy with delete should return delete result"); +assert(!("x" in target), "proxy with delete should delete through proxy"); +assert(events.indexOf("delete:x") >= 0, "proxy delete trap should be used"); + +const throwingProxy = new Proxy({}, { + has(_target, key) { + return key === "x"; + }, + deleteProperty() { + throw new Error("delete boom"); + }, +}); + +let propagated = false; +try { + with (throwingProxy) { + delete x; + } +} catch (e) { + propagated = e.message === "delete boom"; +} + +assert(propagated, "proxy with delete should propagate thrown delete errors"); + +console.log("OK");