diff --git a/RELEASES.rst b/RELEASES.rst index 5a4521fb71..8632fc66a5 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -3396,6 +3396,11 @@ Planned 2.4.0 (XXXX-XX-XX) ------------------ +* Allow a Proxy object to have a Proxy as its target (i.e. Proxy chains) + (GH-1947) + +* Allow a Proxy handler object to be a Proxy (GH-1947) + 3.0.0 (XXXX-XX-XX) ------------------ diff --git a/src-input/duk_api_stack.c b/src-input/duk_api_stack.c index bc44ed711b..9c1fd61a08 100644 --- a/src-input/duk_api_stack.c +++ b/src-input/duk_api_stack.c @@ -5558,19 +5558,11 @@ DUK_EXTERNAL duk_idx_t duk_push_proxy(duk_hthread *thr, duk_uint_t proxy_flags) DUK__CHECK_SPACE(); #endif - /* Reject a proxy object as the target because it would need - * special handling in property lookups. (ES2015 has no such - * restriction.) - */ + /* Proxy target validation. */ h_target = duk_require_hobject_promote_mask(thr, -2, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); DUK_ASSERT(h_target != NULL); - if (DUK_HOBJECT_IS_PROXY(h_target)) { - goto fail_args; - } - /* Reject a proxy object as the handler because it would cause - * potentially unbounded recursion. (ES2015 has no such - * restriction.) + /* Proxy handler object validation. * * There's little practical reason to use a lightfunc or a plain * buffer as the handler table: one could only provide traps via @@ -5580,9 +5572,6 @@ DUK_EXTERNAL duk_idx_t duk_push_proxy(duk_hthread *thr, duk_uint_t proxy_flags) */ h_handler = duk_require_hobject_promote_mask(thr, -1, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); DUK_ASSERT(h_handler != NULL); - if (DUK_HOBJECT_IS_PROXY(h_handler)) { - goto fail_args; - } /* XXX: Proxy object currently has no prototype, so ToPrimitive() * coercion fails which is a bit confusing. @@ -5629,10 +5618,6 @@ DUK_EXTERNAL duk_idx_t duk_push_proxy(duk_hthread *thr, duk_uint_t proxy_flags) DUK_DD(DUK_DDPRINT("created Proxy: %!iT", duk_get_tval(thr, -1))); return (duk_idx_t) (thr->valstack_top - thr->valstack_bottom - 1); - - fail_args: - DUK_ERROR_TYPE_INVALID_ARGS(thr); - DUK_WO_NORETURN(return 0;); } #else /* DUK_USE_ES6_PROXY */ DUK_EXTERNAL duk_idx_t duk_push_proxy(duk_hthread *thr, duk_uint_t proxy_flags) { diff --git a/src-input/duk_bi_object.c b/src-input/duk_bi_object.c index ffd3a70b14..45e36fd60d 100644 --- a/src-input/duk_bi_object.c +++ b/src-input/duk_bi_object.c @@ -687,6 +687,8 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_hthread *thr) { DUK_ASSERT(magic >= 0 && magic < (duk_int_t) (sizeof(duk__object_keys_enum_flags) / sizeof(duk_small_uint_t))); enum_flags = duk__object_keys_enum_flags[magic]; + /* FIXME: Proxy as target? */ + duk_proxy_ownkeys_postprocess(thr, h_proxy_target, enum_flags); return 1; diff --git a/src-input/duk_hobject_enum.c b/src-input/duk_hobject_enum.c index a5bae93dd4..73d99506f5 100644 --- a/src-input/duk_hobject_enum.c +++ b/src-input/duk_hobject_enum.c @@ -254,6 +254,8 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_hthread *thr, duk_small_uint DUK_DDD(DUK_DDDPRINT("h_proxy_target=%!O", (duk_heaphdr *) h_proxy_target)); enum_target = h_proxy_target; + /* FIXME: recursive proxy case? */ + duk_push_hobject(thr, enum_target); /* -> [ ... enum_target res handler undefined target ] */ duk_put_prop_stridx_short(thr, -4, DUK_STRIDX_INT_TARGET); diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index 3cd01a223c..36e7da8340 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -456,7 +456,11 @@ DUK_LOCAL duk_bool_t duk__proxy_check_prop(duk_hthread *thr, duk_hobject *obj, d * are not realized now.) */ - /* XXX: C recursion limit if proxies are allowed as handler/target values */ + /* FIXME: C recursion limit if proxies are allowed as handler/target values? + * Or rely on later stack depth macro check (insert one here)? + */ + + /* FIXME: implement recursive Proxy target support directly here? */ duk_require_stack(thr, DUK__VALSTACK_PROXY_LOOKUP); duk_push_hobject(thr, h_handler); @@ -2372,6 +2376,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, * Coercion and fast path processing */ + recheck_object: switch (DUK_TVAL_GET_TAG(tv_obj)) { case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: { @@ -2552,6 +2557,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, curr = h_target; /* resume lookup from target */ DUK_TVAL_SET_OBJECT(tv_obj, curr); + goto recheck_object; } #endif /* DUK_USE_ES6_PROXY */ @@ -2891,6 +2897,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, DUK_ASSERT(obj != NULL); DUK_UNREF(arr_idx); + recheck_object: #if defined(DUK_USE_ES6_PROXY) if (DUK_UNLIKELY(DUK_HOBJECT_IS_PROXY(obj))) { duk_hobject *h_target; @@ -2936,6 +2943,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, } obj = h_target; /* resume check from proxy target */ + goto recheck_object; } #endif /* DUK_USE_ES6_PROXY */ @@ -3378,6 +3386,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, * Coercion and fast path processing. */ + recheck_object: switch (DUK_TVAL_GET_TAG(tv_obj)) { case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: { @@ -3539,6 +3548,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, orig = h_target; /* resume write to target */ DUK_TVAL_SET_OBJECT(tv_obj, orig); + goto recheck_object; } #endif /* DUK_USE_ES6_PROXY */ @@ -4451,6 +4461,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_push_tval(thr, tv_obj); duk_push_tval(thr, tv_key); + recheck_object: tv_obj = DUK_GET_TVAL_NEGIDX(thr, -2); if (DUK_TVAL_IS_OBJECT(tv_obj)) { duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_obj); @@ -4501,7 +4512,9 @@ DUK_INTERNAL duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, goto done_rc; } - obj = h_target; /* resume delete to target */ + duk_push_hobject(thr, h_target); /* resume delete to target */ + duk_replace(thr, -3); + goto recheck_object; } #endif /* DUK_USE_ES6_PROXY */ diff --git a/src-input/duk_js_ops.c b/src-input/duk_js_ops.c index 76d3d1b5d8..c9d7d79915 100644 --- a/src-input/duk_js_ops.c +++ b/src-input/duk_js_ops.c @@ -1158,9 +1158,10 @@ DUK_LOCAL duk_bool_t duk__js_instanceof_helper(duk_hthread *thr, duk_tval *tv_x, if (!val) { goto pop3_and_false; } - DUK_ASSERT(val != NULL); + #if defined(DUK_USE_ES6_PROXY) + /* FIXME: getPrototypeOf trap; testcase; comment */ val = duk_hobject_resolve_proxy_target(val); #endif diff --git a/tests/ecmascript/test-bi-proxy-handler-proxy-1.js b/tests/ecmascript/test-bi-proxy-handler-proxy-1.js new file mode 100644 index 0000000000..fb8df2ddc1 --- /dev/null +++ b/tests/ecmascript/test-bi-proxy-handler-proxy-1.js @@ -0,0 +1,28 @@ +/*=== +P1.get called true get true +get called true foo true +<<>> +P1.get called true get true +get called true bar true +<<>> +===*/ + +var T1 = {}; +var P1 = new Proxy(T1, { + get: function (targ, prop, recv) { + print('P1.get called', targ === T1, prop, recv === P1); + + if (prop === 'get') { + return function (targ, prop, recv) { + print('get called', targ === T2, prop, recv === P2); + return '<<<' + prop + '>>>' + }; + } + } +}); + +var T2 = {}; +var P2 = new Proxy(T2, P1); + +print(P2.foo); +print(P2.bar); diff --git a/tests/ecmascript/test-bi-proxy-handler-proxy-2.js b/tests/ecmascript/test-bi-proxy-handler-proxy-2.js new file mode 100644 index 0000000000..8a1cd88e47 --- /dev/null +++ b/tests/ecmascript/test-bi-proxy-handler-proxy-2.js @@ -0,0 +1,17 @@ +/*=== +P1.get called foo +aiee +===*/ + +var curr = { + get: function (targ, prop, recv) { + print('P1.get called', prop); + return 'aiee'; + } +}; +for (var i = 0; i < 1e6; i++) { + curr = new Proxy(curr, {}); +} + +var finalProxy = new Proxy({}, curr); +print(finalProxy.foo); diff --git a/tests/ecmascript/test-bi-proxy-target-proxy-1.js b/tests/ecmascript/test-bi-proxy-target-proxy-1.js new file mode 100644 index 0000000000..da06b9befd --- /dev/null +++ b/tests/ecmascript/test-bi-proxy-target-proxy-1.js @@ -0,0 +1,25 @@ +/*=== +bar +undefined +true +false +123 +true +undefined +true +===*/ + +var A = { foo: 'bar' }; +var P1 = new Proxy(A, {}); +var P2 = new Proxy(P1, {}); +var P3 = new Proxy(P2, {}); + +print(P3.foo); +print(P3.bar); +print('foo' in P3); +print('bar' in P3); +P3.quux = 123; +print(A.quux); +print(delete P3.quux); +print(A.quux); +print(delete P3.noSuch); diff --git a/tests/ecmascript/test-bi-proxy-target-proxy-2.js b/tests/ecmascript/test-bi-proxy-target-proxy-2.js new file mode 100644 index 0000000000..db4ea28289 --- /dev/null +++ b/tests/ecmascript/test-bi-proxy-target-proxy-2.js @@ -0,0 +1,11 @@ +/*=== +123 +===*/ + +var curr = { foo: 123 }; +for (var i = 0; i < 1e6; i++) { + curr = new Proxy(curr, {}); +} + +var finalProxy = curr; +print(finalProxy.foo);