From 9ca046b6703988e51067ccf443d13fca9fabf8c7 Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Thu, 4 Feb 2021 15:58:16 +0100 Subject: [PATCH] Add non-standard behaviour support for Proxies (#4562) JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 79 ++++++++++++- jerry-core/api/jerry.c | 45 +++++++- jerry-core/ecma/base/ecma-gc.c | 6 +- jerry-core/ecma/base/ecma-globals.h | 12 ++ .../ecma/builtin-objects/ecma-builtin-proxy.c | 3 +- .../ecma/operations/ecma-function-object.c | 4 +- .../ecma/operations/ecma-proxy-object.c | 39 +++++-- .../ecma/operations/ecma-proxy-object.h | 3 +- jerry-core/include/jerryscript-core.h | 11 ++ tests/unit-core/CMakeLists.txt | 1 + tests/unit-core/test-realm.c | 3 +- tests/unit-core/test-special-proxy.c | 107 ++++++++++++++++++ 12 files changed, 292 insertions(+), 21 deletions(-) create mode 100644 tests/unit-core/test-special-proxy.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index 6e0ab6780..a984753c4 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -83,6 +83,17 @@ Enum that contains JerryScript **iterator** value types: *New in version 2.4*. +## jerry_proxy_object_options_t + +These option bits allow specializing Proxies with non-standard behaviour. +These flags are recommended only for those trusted Proxies, whose handlers +produce correct results. + +- JERRY_PROXY_SKIP_GET_CHECKS - skip [[Get]] result checks +- JERRY_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS - skip [[GetOwnProperty]] result checks + +*New in version [[NEXT_RELEASE]]*. + ## jerry_property_filter_t Enum that contains JerryScript **property filter** options bits: @@ -2132,6 +2143,7 @@ jerry_value_is_proxy (const jerry_value_t value) - [jerry_release_value](#jerry_release_value) - [jerry_create_proxy](#jerry_create_proxy) +- [jerry_create_special_proxy](#jerry_create_special_proxy) ## jerry_value_is_string @@ -4641,6 +4653,7 @@ jerry_get_proxy_target (jerry_value_t proxy_value) **See also** - [jerry_create_proxy](#jerry_create_proxy) +- [jerry_create_special_proxy](#jerry_create_special_proxy) # Acquire and release API values @@ -5352,7 +5365,7 @@ Create a new Proxy object with the given target and handler. ```c jerry_value_t jerry_create_proxy (const jerry_value_t target, - const jerry_value_t handler) + const jerry_value_t handler); ``` - `target` - proxy target @@ -5392,6 +5405,70 @@ main (void) **See also** - [jerry_value_is_proxy](#jerry_value_is_proxy) +- [jerry_create_special_proxy](#jerry_create_special_proxy) +- [jerry_release_value](#jerry_release_value) + + +## jerry_create_special_proxy + +**Summary** + +Create a new Proxy object with the given target and handler. +The behaviour of the Proxy can be specialized with an options argument. + +*Note*: + - This API depends on the es.next profile. + - Returned value must be freed with [jerry_release_value](#jerry_release_value) + when it is no longer needed. + +**Prototype** + +```c +jerry_value_t +jerry_create_special_proxy (const jerry_value_t target, + const jerry_value_t handler, + uint32_t options); +``` + +- `target` - proxy target +- `handler` - proxy handler +- `options` - any combination of [jerry_proxy_object_options_t](#jerry_proxy_object_options_t) options +- return thrown error - if the Proxy construction fails + value of the newly created proxy object - otherwise + +*New in version [[NEXT_RELEASE]]*. + +**Example** + +[doctest]: # () + +```c +#include "jerryscript.h" + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + jerry_value_t target = jerry_create_object (); + jerry_value_t handler = jerry_create_object (); + jerry_value_t proxy = jerry_create_special_proxy (target, handler, JERRY_PROXY_SKIP_GET_CHECKS); + + jerry_release_value (target); + jerry_release_value (handler); + + // usage of the proxy + + jerry_release_value (proxy); + + jerry_cleanup (); +} +``` + +**See also** + +- [jerry_value_is_proxy](#jerry_value_is_proxy) +- [jerry_create_special_proxy](#jerry_create_special_proxy) - [jerry_release_value](#jerry_release_value) diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 8de0070bc..7265a55d7 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -2221,13 +2221,56 @@ jerry_create_proxy (const jerry_value_t target, /**< target argument */ } #if ENABLED (JERRY_BUILTIN_PROXY) - ecma_object_t *proxy_p = ecma_proxy_create (target, handler); + ecma_object_t *proxy_p = ecma_proxy_create (target, handler, 0); return jerry_return (proxy_p == NULL ? ECMA_VALUE_ERROR : ecma_make_object_value (proxy_p)); #else /* !ENABLED (JERRY_BUILTIN_PROXY) */ return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Proxy is not supported"))); #endif /* ENABLED (JERRY_BUILTIN_PROXY) */ } /* jerry_create_proxy */ +#if ENABLED (JERRY_BUILTIN_PROXY) + +JERRY_STATIC_ASSERT ((int) JERRY_PROXY_SKIP_GET_CHECKS == (int) ECMA_PROXY_SKIP_GET_CHECKS, + jerry_and_ecma_proxy_skip_get_checks_must_be_equal); +JERRY_STATIC_ASSERT ((int) JERRY_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS == (int) ECMA_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS, + jerry_and_ecma_proxy_skip_get_own_property_checks_must_be_equal); + +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ + +/** + * Create a new Proxy object with the given target, handler, and special options + * + * Note: + * returned value must be freed with jerry_release_value, when it is no longer needed. + * + * @return value of the created Proxy object + */ +jerry_value_t +jerry_create_special_proxy (const jerry_value_t target, /**< target argument */ + const jerry_value_t handler, /**< handler argument */ + uint32_t options) /**< jerry_proxy_object_options_t option bits */ +{ + jerry_assert_api_available (); + + if (ecma_is_value_error_reference (target) + || ecma_is_value_error_reference (handler)) + { + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (wrong_args_msg_p))); + } + +#if ENABLED (JERRY_BUILTIN_PROXY) + const uint32_t options_mask = (JERRY_PROXY_SKIP_GET_CHECKS + | JERRY_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS); + options &= options_mask; + + ecma_object_t *proxy_p = ecma_proxy_create (target, handler, options); + return jerry_return (proxy_p == NULL ? ECMA_VALUE_ERROR : ecma_make_object_value (proxy_p)); +#else /* !ENABLED (JERRY_BUILTIN_PROXY) */ + JERRY_UNUSED (options); + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Proxy is not supported"))); +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ +} /* jerry_create_special_proxy */ + /** * Create string from a valid UTF-8 string * diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 9485d67a4..32cb908a6 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -887,9 +887,7 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ case ECMA_OBJECT_TYPE_PROXY: { ecma_gc_mark_proxy_object (object_p); - /* The protoype of the proxy should be "empty" (aside from the tag bits every other bit should be zero). */ - JERRY_ASSERT ((object_p->u2.prototype_cp & ~JMEM_TAG_MASK) == 0); - /* Make sure that the prototype is not checked below. */ + /* Prototype of proxy object is a bit set. */ proto_cp = JMEM_CP_NULL; break; } @@ -1696,8 +1694,6 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ #if ENABLED (JERRY_BUILTIN_PROXY) case ECMA_OBJECT_TYPE_PROXY: { - /* The protoype of the proxy should be "empty" (aside from the tag bits every other bit should be zero). */ - JERRY_ASSERT ((object_p->u2.prototype_cp & ~JMEM_TAG_MASK) == 0); ext_object_size = sizeof (ecma_proxy_object_t); break; } diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 98d9de036..d98bcc813 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -2176,6 +2176,18 @@ do \ #define ECMA_PROPERTY_POINTER_ERROR ((ecma_property_t *) 0x01) #if ENABLED (JERRY_BUILTIN_PROXY) + +/** + * Proxy object flags. + */ +typedef enum +{ + ECMA_PROXY_SKIP_GET_CHECKS = (1u << 0), /**< skip [[Get]] result checks */ + ECMA_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS = (1u << 1), /**< skip [[GetOwnProperty]] result checks */ + ECMA_PROXY_IS_CALLABLE = (1u << 2), /**< proxy is callable */ + ECMA_PROXY_IS_CONSTRUCTABLE = (1u << 3), /**< proxy is constructable */ +} ecma_proxy_flag_types_t; + /** * Description of Proxy objects. * diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-proxy.c b/jerry-core/ecma/builtin-objects/ecma-builtin-proxy.c index a529b1f9c..91c54203f 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-proxy.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-proxy.c @@ -101,7 +101,8 @@ ecma_builtin_proxy_dispatch_construct (const ecma_value_t *arguments_list_p, /** /* 2. */ ecma_object_t *proxy_p = ecma_proxy_create (arguments_list_len > 0 ? arguments_list_p[0] : ECMA_VALUE_UNDEFINED, - arguments_list_len > 1 ? arguments_list_p[1] : ECMA_VALUE_UNDEFINED); + arguments_list_len > 1 ? arguments_list_p[1] : ECMA_VALUE_UNDEFINED, + 0); if (JERRY_UNLIKELY (proxy_p == NULL)) { diff --git a/jerry-core/ecma/operations/ecma-function-object.c b/jerry-core/ecma/operations/ecma-function-object.c index 0e61cbc5b..72fef38bd 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_GET_FIRST_BIT_FROM_POINTER_TAG (obj_p->u2.prototype_cp) != 0; + return (obj_p->u2.prototype_cp & ECMA_PROXY_IS_CALLABLE) != 0; } #endif /* ENABLED (JERRY_BUILTIN_PROXY) */ @@ -215,7 +215,7 @@ ecma_object_check_constructor (ecma_object_t *obj_p) /**< ecma object */ #if ENABLED (JERRY_BUILTIN_PROXY) if (ECMA_OBJECT_TYPE_IS_PROXY (type)) { - if (ECMA_GET_SECOND_BIT_FROM_POINTER_TAG (obj_p->u2.prototype_cp) == 0) + if (!(obj_p->u2.prototype_cp & ECMA_PROXY_IS_CONSTRUCTABLE)) { return ECMA_ERR_MSG ("Proxy target is not a constructor"); } diff --git a/jerry-core/ecma/operations/ecma-proxy-object.c b/jerry-core/ecma/operations/ecma-proxy-object.c index eeacc6595..c49dc5cd2 100644 --- a/jerry-core/ecma/operations/ecma-proxy-object.c +++ b/jerry-core/ecma/operations/ecma-proxy-object.c @@ -48,7 +48,8 @@ */ ecma_object_t * ecma_proxy_create (ecma_value_t target, /**< proxy target */ - ecma_value_t handler) /**< proxy handler */ + ecma_value_t handler, /**< proxy handler */ + uint32_t options) /**< ecma_proxy_flag_types_t option bits */ { /* ES2015: 1, 3. */ /* ES11+: 1 - 2. */ @@ -65,17 +66,19 @@ ecma_proxy_create (ecma_value_t target, /**< proxy target */ ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; + obj_p->u2.prototype_cp = (jmem_cpointer_t) options; + /* ES2015: 7. */ /* ES11+: 5. */ if (ecma_op_is_callable (target)) { - ECMA_SET_FIRST_BIT_TO_POINTER_TAG (obj_p->u2.prototype_cp); + obj_p->u2.prototype_cp |= ECMA_PROXY_IS_CALLABLE; /* ES2015: 7.b. */ /* ES11+: 5.b. */ if (ecma_is_constructor (target)) { - ECMA_SET_SECOND_BIT_TO_POINTER_TAG (obj_p->u2.prototype_cp); + obj_p->u2.prototype_cp |= ECMA_PROXY_IS_CONSTRUCTABLE; } } @@ -150,7 +153,7 @@ ecma_proxy_create_revocable (ecma_value_t target, /**< target argument */ ecma_value_t handler) /**< handler argument */ { /* 1. */ - ecma_object_t *proxy_p = ecma_proxy_create (target, handler); + ecma_object_t *proxy_p = ecma_proxy_create (target, handler, 0); /* 2. */ if (proxy_p == NULL) @@ -688,6 +691,25 @@ ecma_proxy_object_get_own_property_descriptor (ecma_object_t *obj_p, /**< proxy return ecma_raise_type_error (ECMA_ERR_MSG ("Trap is neither an object nor undefined")); } + if (obj_p->u2.prototype_cp & ECMA_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS) + { + if (ecma_is_value_undefined (trap_result)) + { + return ECMA_VALUE_FALSE; + } + + ecma_value_t result_val = ecma_op_to_property_descriptor (trap_result, prop_desc_p); + ecma_free_value (trap_result); + + if (ECMA_IS_VALUE_ERROR (result_val)) + { + return result_val; + } + + ecma_op_to_complete_property_descriptor (prop_desc_p); + return ECMA_VALUE_TRUE; + } + /* 12. */ ecma_property_descriptor_t target_desc; ecma_value_t target_status = ecma_op_object_get_own_property_descriptor (target_obj_p, prop_name_p, &target_desc); @@ -1118,7 +1140,8 @@ ecma_proxy_object_get (ecma_object_t *obj_p, /**< proxy object */ ecma_deref_object (func_obj_p); /* 10. */ - if (ECMA_IS_VALUE_ERROR (trap_result)) + if (ECMA_IS_VALUE_ERROR (trap_result) + || (obj_p->u2.prototype_cp & ECMA_PROXY_SKIP_GET_CHECKS)) { return trap_result; } @@ -1147,9 +1170,9 @@ ecma_proxy_object_get (ecma_object_t *obj_p, /**< proxy object */ ret_value = ecma_raise_type_error (ECMA_ERR_MSG ("Incorrect value is returned by a Proxy 'get' trap")); } else if (!(target_desc.flags & ECMA_PROP_IS_CONFIGURABLE) - && (target_desc.flags & (ECMA_PROP_IS_GET_DEFINED | ECMA_PROP_IS_SET_DEFINED)) - && target_desc.get_p == NULL - && !ecma_is_value_undefined (trap_result)) + && (target_desc.flags & (ECMA_PROP_IS_GET_DEFINED | ECMA_PROP_IS_SET_DEFINED)) + && target_desc.get_p == NULL + && !ecma_is_value_undefined (trap_result)) { ret_value = ecma_raise_type_error (ECMA_ERR_MSG ("Property of a Proxy is non-configurable and " "does not have a getter function")); diff --git a/jerry-core/ecma/operations/ecma-proxy-object.h b/jerry-core/ecma/operations/ecma-proxy-object.h index 0ad66a7c6..139a074fe 100644 --- a/jerry-core/ecma/operations/ecma-proxy-object.h +++ b/jerry-core/ecma/operations/ecma-proxy-object.h @@ -29,7 +29,8 @@ ecma_object_t * ecma_proxy_create (ecma_value_t target, - ecma_value_t handler); + ecma_value_t handler, + uint32_t options); ecma_object_t * ecma_proxy_create_revocable (ecma_value_t target, diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index fe7d2ff2e..05cf0ae41 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -471,6 +471,15 @@ typedef enum JERRY_ITERATOR_TYPE_SET, /**< Set iterator */ } jerry_iterator_type_t; +/** + * JerryScript special Proxy object options. + */ +typedef enum +{ + JERRY_PROXY_SKIP_GET_CHECKS = (1u << 0), /**< skip [[Get]] result checks */ + JERRY_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS = (1u << 1), /**< skip [[GetOwnProperty]] result checks */ +} jerry_proxy_object_options_t; + /** * JerryScript object property filter options. */ @@ -597,6 +606,8 @@ jerry_value_t jerry_create_null (void); jerry_value_t jerry_create_object (void); jerry_value_t jerry_create_promise (void); jerry_value_t jerry_create_proxy (const jerry_value_t target, const jerry_value_t handler); +jerry_value_t jerry_create_special_proxy (const jerry_value_t target, const jerry_value_t handler, + uint32_t options); jerry_value_t jerry_create_regexp (const jerry_char_t *pattern, uint16_t flags); jerry_value_t jerry_create_regexp_sz (const jerry_char_t *pattern, jerry_size_t pattern_size, uint16_t flags); jerry_value_t jerry_create_string_from_utf8 (const jerry_char_t *str_p); diff --git a/tests/unit-core/CMakeLists.txt b/tests/unit-core/CMakeLists.txt index a57438672..50cba1131 100644 --- a/tests/unit-core/CMakeLists.txt +++ b/tests/unit-core/CMakeLists.txt @@ -74,6 +74,7 @@ set(SOURCE_UNIT_TEST_MAIN_MODULES test-regression-3588.c test-resource-name.c test-snapshot.c + test-special-proxy.c test-string-to-number.c test-stringbuilder.c test-strings.c diff --git a/tests/unit-core/test-realm.c b/tests/unit-core/test-realm.c index 28e469477..ecdf05f14 100644 --- a/tests/unit-core/test-realm.c +++ b/tests/unit-core/test-realm.c @@ -13,8 +13,7 @@ * limitations under the License. */ -#include "ecma-globals.h" -#include "ecma-helpers.h" +#include "jerryscript.h" #include "test-common.h" diff --git a/tests/unit-core/test-special-proxy.c b/tests/unit-core/test-special-proxy.c new file mode 100644 index 000000000..bf8ca955c --- /dev/null +++ b/tests/unit-core/test-special-proxy.c @@ -0,0 +1,107 @@ +/* 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. + */ + +#include "jerryscript.h" + +#include "test-common.h" + +static jerry_value_t +create_special_proxy_handler (const jerry_value_t function_obj, /**< function object */ + const jerry_value_t this_val, /**< this value */ + const jerry_value_t args_p[], /**< argument list */ + const jerry_length_t args_count) /**< argument count */ +{ + JERRY_UNUSED (function_obj); + JERRY_UNUSED (this_val); + + if (args_count < 2) + { + return jerry_create_undefined (); + } + + const uint32_t options = (JERRY_PROXY_SKIP_GET_CHECKS + | JERRY_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS); + + return jerry_create_special_proxy (args_p[0], args_p[1], options); +} /* create_special_proxy_handler */ + +static void +run_eval (const char *source_p) +{ + jerry_value_t result = jerry_eval ((const jerry_char_t *) source_p, strlen (source_p), 0); + + TEST_ASSERT (!jerry_value_is_error (result)); + jerry_release_value (result); +} /* run_eval */ + +/** + * Unit test's main function. + */ +int +main (void) +{ + TEST_INIT (); + + if (!jerry_is_feature_enabled (JERRY_FEATURE_PROXY)) + { + printf ("Skipping test, Proxy not enabled\n"); + return 0; + } + + jerry_init (JERRY_INIT_EMPTY); + + jerry_value_t global = jerry_get_global_object (); + + jerry_value_t function = jerry_create_external_function (create_special_proxy_handler); + jerry_value_t name = jerry_create_string ((const jerry_char_t *) "create_special_proxy"); + jerry_value_t result = jerry_set_property (global, name, function); + TEST_ASSERT (!jerry_value_is_error (result)); + + jerry_release_value (result); + jerry_release_value (name); + jerry_release_value (function); + + jerry_release_value (global); + + run_eval ("function assert (v) {\n" + " if (v !== true)\n" + " throw 'Assertion failed!'\n" + "}"); + + /* This test fails unless JERRY_PROXY_SKIP_GET_CHECKS is set. */ + run_eval ("var o = {}\n" + "Object.defineProperty(o, 'prop', { value:4 })\n" + "var proxy = create_special_proxy(o, {\n" + " get(target, key) { return 5 }\n" + "})\n" + "assert(proxy.prop === 5)"); + + /* This test fails unless JERRY_PROXY_SKIP_GET_OWN_PROPERTY_CHECKS is set. */ + run_eval ("var o = {}\n" + "Object.defineProperty(o, 'prop', { value:4, enumerable:true })\n" + "var proxy = create_special_proxy(o, {\n" + " getOwnPropertyDescriptor(target, key) {\n" + " return { value:5, configurable:true, writable:true }\n" + " }\n" + "})\n" + "var desc = Object.getOwnPropertyDescriptor(proxy, 'prop')\n" + "assert(desc.value === 5)\n" + "assert(desc.configurable === true)\n" + "assert(desc.enumerable === false)\n" + "assert(desc.writable === true)\n"); + + jerry_cleanup (); + return 0; +} /* main */