From 5d916fb8d4cd944ff985bcbc89cfde017425744d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20G=C3=A1l?= Date: Tue, 17 Nov 2020 10:38:25 +0100 Subject: [PATCH] Correctly handle the Proxy IsCallable and IsConstructor information (#4264) The `IsCallable(target)` and `IsConstructor(target)` info can't be stored in the target/handler values. If the input for the ProxyCreate was a revocable Proxy the original target's callable/constructor information must be retained even after the Proxy was revoked. JerryScript-DCO-1.0-Signed-off-by: Peter Gal pgal.usz@partner.samsung.com --- jerry-core/ecma/base/ecma-gc.c | 14 +++- jerry-core/ecma/base/ecma-globals.h | 4 ++ .../ecma/operations/ecma-function-object.c | 4 +- .../ecma/operations/ecma-proxy-object.c | 14 ++++ tests/jerry/es.next/proxy_flags.js | 68 +++++++++++++++++++ tests/test262-esnext-excludelist.xml | 3 - 6 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 tests/jerry/es.next/proxy_flags.js diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 59ab05628..d35576b79 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -786,7 +786,11 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ case ECMA_OBJECT_TYPE_PROXY: { ecma_gc_mark_proxy_object (object_p); - break; + /* No need to free the properties of a proxy (there should be none). + * Aside from the tag bits every other bit should be zero, + */ + JERRY_ASSERT ((object_p->u1.property_list_cp & ~JMEM_TAG_MASK) == 0); + return; } #endif /* ENABLED (JERRY_BUILTIN_PROXY) */ case ECMA_OBJECT_TYPE_BOUND_FUNCTION: @@ -1499,8 +1503,12 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ #if ENABLED (JERRY_BUILTIN_PROXY) case ECMA_OBJECT_TYPE_PROXY: { - ext_object_size = sizeof (ecma_proxy_object_t); - break; + /* No need to free the properties of a proxy (there should be none). + * Aside from the tag bits every other bit should be zero, + */ + JERRY_ASSERT ((object_p->u1.property_list_cp & ~JMEM_TAG_MASK) == 0); + ecma_dealloc_extended_object (object_p, sizeof (ecma_proxy_object_t)); + return; } #endif /* ENABLED (JERRY_BUILTIN_PROXY) */ case ECMA_OBJECT_TYPE_FUNCTION: diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 1c3247dd0..b84a0eeaf 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -2128,6 +2128,10 @@ do \ #if ENABLED (JERRY_BUILTIN_PROXY) /** * Description of Proxy objects. + * + * A Proxy object's property list is used to store extra information: + * * The "header.u1.property_list_cp" 1st tag bit stores the IsCallable information. + * * The "header.u1.property_list_cp" 2nd tag bit stores the IsConstructor information. */ typedef struct { diff --git a/jerry-core/ecma/operations/ecma-function-object.c b/jerry-core/ecma/operations/ecma-function-object.c index ed004a9e6..c0e4f68a4 100644 --- a/jerry-core/ecma/operations/ecma-function-object.c +++ b/jerry-core/ecma/operations/ecma-function-object.c @@ -106,7 +106,7 @@ ecma_op_object_is_callable (ecma_object_t *obj_p) /**< ecma object */ #if ENABLED (JERRY_BUILTIN_PROXY) if (ECMA_OBJECT_TYPE_IS_PROXY (type)) { - return ecma_op_is_callable (((ecma_proxy_object_t *) obj_p)->target); + return ECMA_GET_FIRST_BIT_FROM_POINTER_TAG (obj_p->u1.property_list_cp) != 0; } #endif /* ENABLED (JERRY_BUILTIN_PROXY) */ @@ -159,7 +159,7 @@ ecma_object_is_constructor (ecma_object_t *obj_p) /**< ecma object */ #if ENABLED (JERRY_BUILTIN_PROXY) if (ECMA_OBJECT_TYPE_IS_PROXY (type)) { - return ecma_is_constructor (((ecma_proxy_object_t *) obj_p)->target); + return ECMA_GET_SECOND_BIT_FROM_POINTER_TAG (obj_p->u1.property_list_cp) != 0; } #endif /* ENABLED (JERRY_BUILTIN_PROXY) */ diff --git a/jerry-core/ecma/operations/ecma-proxy-object.c b/jerry-core/ecma/operations/ecma-proxy-object.c index f9ee4083e..8866bfa5a 100644 --- a/jerry-core/ecma/operations/ecma-proxy-object.c +++ b/jerry-core/ecma/operations/ecma-proxy-object.c @@ -66,6 +66,20 @@ ecma_proxy_create (ecma_value_t target, /**< proxy target */ ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; + /* ES2015: 7. */ + /* ES11+: 5. */ + if (ecma_op_is_callable (target)) + { + ECMA_SET_FIRST_BIT_TO_POINTER_TAG (obj_p->u1.property_list_cp); + + /* ES2015: 7.b. */ + /* ES11+: 5.b. */ + if (ecma_is_constructor (target)) + { + ECMA_SET_SECOND_BIT_TO_POINTER_TAG (obj_p->u1.property_list_cp); + } + } + /* ES2015: 8. */ /* ES11+: 6. */ proxy_obj_p->target = target; diff --git a/tests/jerry/es.next/proxy_flags.js b/tests/jerry/es.next/proxy_flags.js new file mode 100644 index 000000000..febd80ce7 --- /dev/null +++ b/tests/jerry/es.next/proxy_flags.js @@ -0,0 +1,68 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var target_called = 0; +function target_method() { + target_called++; +} + +var proxy_revocable_function = Proxy.revocable(target_method, {}) +var proxy_function = proxy_revocable_function.proxy; + +/* Test Proxy IsCallable(target) */ +/* The proxy target should have a function type. */ +assert(typeof(proxy_function) === "function"); + +/* The proxy can be called with new */ +var new_obj = new proxy_function() +assert(new_obj instanceof target_method); +assert(target_called === 1); + +/* Test Proxy IsConstructor(target) */ +/* Array.from tries to use the "this" value as constructor. */ +var array_result = Array.from.call(proxy_function, [1, 2, 3]); +assert(Array.isArray(array_result) === false); +assert(target_called === 2); + +proxy_revocable_function.revoke(); + +/* Test Proxy IsCallable(target) if the proxy is revoked. */ +/* After the proxy was revoked the type is still function. */ +assert(typeof(proxy_function) === "function"); + +/* After the proxy was revoked the constructor should not be called + * and an error should be reported + */ +try { + new proxy_function(); + assert(false); +} catch (ex) { + assert(ex instanceof TypeError); +} + +assert(target_called === 2); + +/* Test Proxy IsConstructor(target) if the proxy is revoked. */ +/* Array.from tries to use the "this" value as constructor and as the + * proxy is revoked the constructor call should fail. + * The IsConstructor(proxy_function) is still true. + */ +try { + Array.from.call(proxy_function, [1, 2, 3]); + assert(false); +} catch (ex) { + assert(ex instanceof TypeError); +} + +assert(target_called === 2); diff --git a/tests/test262-esnext-excludelist.xml b/tests/test262-esnext-excludelist.xml index 619e7b1c0..764aa808a 100644 --- a/tests/test262-esnext-excludelist.xml +++ b/tests/test262-esnext-excludelist.xml @@ -109,9 +109,7 @@ Test expects incorrect call order Test expects incorrect call order - - @@ -544,7 +542,6 @@ -