diff --git a/.github/workflows/gh-actions.yml b/.github/workflows/gh-actions.yml index 5260514d7d..baeb548e7c 100644 --- a/.github/workflows/gh-actions.yml +++ b/.github/workflows/gh-actions.yml @@ -54,8 +54,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v2 - # FIXME: regression-test-issue-3785.js currently are deadloop on OSX. - - run: $RUNNER -q --jerry-tests --skip-list=regression-test-issue-3785.js + - run: $RUNNER -q --jerry-tests - run: $RUNNER -q --unittests OSX_x86-64_Build_Correctness_Unit_Tests_Debug: @@ -152,7 +151,7 @@ jobs: - run: >- $RUNNER -q --jerry-tests --buildoptions=--stack-limit=0,--compile-flag=-fsanitize=address,--compile-flag=-m32,--compile-flag=-fno-omit-frame-pointer,--compile-flag=-fno-common,--compile-flag=-O2,--debug,--system-allocator=on,--linker-flag=-fuse-ld=gold - --skip-list=parser-oom.js,parser-oom2.js,stack-limit.js,regression-test-issue-2190.js,regression-test-issue-2258-2963.js,regression-test-issue-2448.js,regression-test-issue-2905.js,regression-test-issue-3785.js + --skip-list=parser-oom.js,parser-oom2.js,stack-limit.js,regression-test-issue-2190.js,regression-test-issue-2258-2963.js,regression-test-issue-2448.js,regression-test-issue-2905.js,regression-test-issue-3785.js,proxy-evil-recursion.js ASAN_Tests_Debug: runs-on: ubuntu-latest @@ -165,7 +164,7 @@ jobs: - run: >- $RUNNER -q --jerry-tests --build-debug --buildoptions=--stack-limit=0,--compile-flag=-fsanitize=address,--compile-flag=-m32,--compile-flag=-fno-omit-frame-pointer,--compile-flag=-fno-common,--compile-flag=-O2,--debug,--system-allocator=on,--linker-flag=-fuse-ld=gold - --skip-list=parser-oom.js,parser-oom2.js,stack-limit.js,regression-test-issue-2190.js,regression-test-issue-2258-2963.js,regression-test-issue-2448.js,regression-test-issue-2905.js,regression-test-issue-3785.js + --skip-list=parser-oom.js,parser-oom2.js,stack-limit.js,regression-test-issue-2190.js,regression-test-issue-2258-2963.js,regression-test-issue-2448.js,regression-test-issue-2905.js,regression-test-issue-3785.js,proxy-evil-recursion.js UBSAN_Tests: runs-on: ubuntu-latest diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 99bf1d3839..7bfe0e984a 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -2268,16 +2268,29 @@ typedef struct #if (JERRY_STACK_LIMIT != 0) /** * Check the current stack usage. If the limit is reached a RangeError is raised. + * The macro argument specifies the return value which is usally ECMA_VALUE_ERROR or NULL. */ -#define ECMA_CHECK_STACK_USAGE() \ +#define ECMA_CHECK_STACK_USAGE_RETURN(RETURN_VALUE) \ do \ { \ if (ecma_get_current_stack_usage () > CONFIG_MEM_STACK_LIMIT) \ { \ - return ecma_raise_range_error (ECMA_ERR_MSG ("Maximum call stack size exceeded")); \ + ecma_raise_range_error (ECMA_ERR_MSG ("Maximum call stack size exceeded")); \ + return RETURN_VALUE; \ } \ } while (0) + +/** + * Specialized version of ECMA_CHECK_STACK_USAGE_RETURN which returns ECMA_VALUE_ERROR. + * This version should be used in most cases. + */ +#define ECMA_CHECK_STACK_USAGE() ECMA_CHECK_STACK_USAGE_RETURN(ECMA_VALUE_ERROR) #else /* JERRY_STACK_LIMIT == 0) */ +/** + * If the stack limit is unlimited, this check is an empty macro. + */ +#define ECMA_CHECK_STACK_USAGE_RETURN(RETURN_VALUE) + /** * If the stack limit is unlimited, this check is an empty macro. */ diff --git a/jerry-core/ecma/operations/ecma-proxy-object.c b/jerry-core/ecma/operations/ecma-proxy-object.c index bd75ea4760..5fcc1dcb41 100644 --- a/jerry-core/ecma/operations/ecma-proxy-object.c +++ b/jerry-core/ecma/operations/ecma-proxy-object.c @@ -263,6 +263,7 @@ ecma_value_t ecma_proxy_object_get_prototype_of (ecma_object_t *obj_p) /**< proxy object */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -284,7 +285,9 @@ ecma_proxy_object_get_prototype_of (ecma_object_t *obj_p) /**< proxy object */ /* 7. */ if (ecma_is_value_undefined (trap)) { - return ecma_builtin_object_object_get_prototype_of (target_obj_p); + ecma_value_t result = ecma_builtin_object_object_get_prototype_of (target_obj_p); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -373,6 +376,7 @@ ecma_proxy_object_set_prototype_of (ecma_object_t *obj_p, /**< proxy object */ ecma_value_t proto) /**< new prototype object */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); /* 1. */ JERRY_ASSERT (ecma_is_value_object (proto) || ecma_is_value_null (proto)); @@ -399,10 +403,14 @@ ecma_proxy_object_set_prototype_of (ecma_object_t *obj_p, /**< proxy object */ { if (ECMA_OBJECT_IS_PROXY (target_obj_p)) { - return ecma_proxy_object_set_prototype_of (target_obj_p, proto); + ecma_value_t result = ecma_proxy_object_set_prototype_of (target_obj_p, proto); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } - return ecma_op_ordinary_object_set_prototype_of (target_obj_p, proto); + ecma_value_t result = ecma_op_ordinary_object_set_prototype_of (target_obj_p, proto); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -488,6 +496,7 @@ ecma_value_t ecma_proxy_object_is_extensible (ecma_object_t *obj_p) /**< proxy object */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -509,7 +518,9 @@ ecma_proxy_object_is_extensible (ecma_object_t *obj_p) /**< proxy object */ /* 7. */ if (ecma_is_value_undefined (trap)) { - return ecma_builtin_object_object_is_extensible (target_obj_p); + ecma_value_t result = ecma_builtin_object_object_is_extensible (target_obj_p); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -577,6 +588,7 @@ ecma_value_t ecma_proxy_object_prevent_extensions (ecma_object_t *obj_p) /**< proxy object */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -666,6 +678,8 @@ ecma_proxy_object_get_own_property_descriptor (ecma_object_t *obj_p, /**< proxy ecma_property_descriptor_t *prop_desc_p) /**< [out] property * descriptor */ { + ECMA_CHECK_STACK_USAGE (); + ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; /* 2. */ @@ -687,7 +701,9 @@ ecma_proxy_object_get_own_property_descriptor (ecma_object_t *obj_p, /**< proxy /* 8. */ if (ecma_is_value_undefined (trap)) { - return ecma_op_object_get_own_property_descriptor (target_obj_p, prop_name_p, prop_desc_p); + ecma_value_t result = ecma_op_object_get_own_property_descriptor (target_obj_p, prop_name_p, prop_desc_p); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -868,6 +884,7 @@ ecma_proxy_object_define_own_property (ecma_object_t *obj_p, /**< proxy object * const ecma_property_descriptor_t *prop_desc_p) /**< property descriptor */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -889,7 +906,9 @@ ecma_proxy_object_define_own_property (ecma_object_t *obj_p, /**< proxy object * /* 8. */ if (ecma_is_value_undefined (trap)) { - return ecma_op_object_define_own_property (target_obj_p, prop_name_p, prop_desc_p); + ecma_value_t result = ecma_op_object_define_own_property (target_obj_p, prop_name_p, prop_desc_p); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } /* 9. */ @@ -1050,7 +1069,9 @@ ecma_proxy_object_has (ecma_object_t *obj_p, /**< proxy object */ /* 8. */ if (ecma_is_value_undefined (trap)) { - return ecma_op_object_has_property (target_obj_p, prop_name_p); + ecma_value_t result = ecma_op_object_has_property (target_obj_p, prop_name_p); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -1153,7 +1174,9 @@ ecma_proxy_object_get (ecma_object_t *obj_p, /**< proxy object */ if (ecma_is_value_undefined (trap)) { ecma_object_t *target_obj_p = ecma_get_object_from_value (proxy_obj_p->target); - return ecma_op_object_get_with_receiver (target_obj_p, prop_name_p, receiver); + ecma_value_t result = ecma_op_object_get_with_receiver (target_obj_p, prop_name_p, receiver); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -1259,7 +1282,9 @@ ecma_proxy_object_set (ecma_object_t *obj_p, /**< proxy object */ /* 8. */ if (ecma_is_value_undefined (trap)) { - return ecma_op_object_put_with_receiver (target_obj_p, prop_name_p, value, receiver, is_strict); + ecma_value_t result = ecma_op_object_put_with_receiver (target_obj_p, prop_name_p, value, receiver, is_strict); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -1357,6 +1382,7 @@ ecma_proxy_object_delete_property (ecma_object_t *obj_p, /**< proxy object */ bool is_strict) /**< delete in strict mode? */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -1378,7 +1404,9 @@ ecma_proxy_object_delete_property (ecma_object_t *obj_p, /**< proxy object */ /* 8. */ if (ecma_is_value_undefined (trap)) { - return ecma_op_object_delete (target_obj_p, prop_name_p, is_strict); + ecma_value_t result = ecma_op_object_delete (target_obj_p, prop_name_p, is_strict); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -1579,6 +1607,7 @@ ecma_collection_t * ecma_proxy_object_own_property_keys (ecma_object_t *obj_p) /**< proxy object */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE_RETURN (NULL); ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -1599,7 +1628,9 @@ ecma_proxy_object_own_property_keys (ecma_object_t *obj_p) /**< proxy object */ /* 6. */ if (ecma_is_value_undefined (trap)) { - return ecma_op_object_own_property_keys (target_obj_p); + ecma_collection_t *result = ecma_op_object_own_property_keys (target_obj_p); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); @@ -1740,6 +1771,7 @@ ecma_proxy_object_call (ecma_object_t *obj_p, /**< proxy object */ uint32_t argc) /**< number of arguments */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -1761,7 +1793,9 @@ ecma_proxy_object_call (ecma_object_t *obj_p, /**< proxy object */ if (ecma_is_value_undefined (trap)) { ecma_object_t *target_obj_p = ecma_get_object_from_value (target); - return ecma_op_function_call (target_obj_p, this_argument, args_p, argc); + ecma_value_t result = ecma_op_function_call (target_obj_p, this_argument, args_p, argc); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } /* 8. */ @@ -1794,6 +1828,7 @@ ecma_proxy_object_construct (ecma_object_t *obj_p, /**< proxy object */ uint32_t argc) /**< number of arguments */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); + ECMA_CHECK_STACK_USAGE (); ecma_proxy_object_t * proxy_obj_p = (ecma_proxy_object_t *) obj_p; @@ -1817,7 +1852,9 @@ ecma_proxy_object_construct (ecma_object_t *obj_p, /**< proxy object */ { JERRY_ASSERT (ecma_object_is_constructor (target_obj_p)); - return ecma_op_function_construct (target_obj_p, new_target_p, args_p, argc); + ecma_value_t result = ecma_op_function_construct (target_obj_p, new_target_p, args_p, argc); + JERRY_BLOCK_TAIL_CALL_OPTIMIZATION (); + return result; } /* 8. */ diff --git a/jerry-core/jrt/jrt.h b/jerry-core/jrt/jrt.h index a87a01c357..e9bed7a0ac 100644 --- a/jerry-core/jrt/jrt.h +++ b/jerry-core/jrt/jrt.h @@ -153,4 +153,37 @@ void JERRY_ATTR_NORETURN jerry_fatal (jerry_fatal_code_t code); #define JERRY__LOG2_8(n) (((n) >= 1 << 8) ? (8 + JERRY__LOG2_4 ((n) >> 8)) : JERRY__LOG2_4 (n)) #define JERRY_LOG2(n) (((n) >= 1 << 16) ? (16 + JERRY__LOG2_8 ((n) >> 16)) : JERRY__LOG2_8 (n)) +/** + * JERRY_BLOCK_TAIL_CALL_OPTIMIZATION + * + * Adapted from abseil ( https://github.com/abseil/ ) + * + * Instructs the compiler to avoid optimizing tail-call recursion. This macro is + * useful when you wish to preserve the existing function order within a stack + * trace for logging, debugging, or profiling purposes. + * + * Example: + * + * int f() { + * int result = g(); + * JERRY_BLOCK_TAIL_CALL_OPTIMIZATION(); + * return result; + * } + * + * This macro is intentionally here as jerryscript-compiler.h is a public header and + * it does not make sense to expose this macro to the public. + */ +#if defined (__clang__) || defined (__GNUC__) +/* Clang/GCC will not tail call given inline volatile assembly. */ +#define JERRY_BLOCK_TAIL_CALL_OPTIMIZATION() __asm__ __volatile__ ("") +#else /* !defined(__clang__) && !defined(__GNUC__) */ +/* On GCC 10.x this version also works. */ +#define JERRY_BLOCK_TAIL_CALL_OPTIMIZATION() \ +do \ +{ \ + JERRY_CONTEXT (status_flags) |= ECMA_STATUS_API_AVAILABLE; \ +} \ +while (0) +#endif /* defined(__clang__) || defined (__GNUC__) */ + #endif /* !JRT_H */ diff --git a/tests/jerry/es.next/proxy-evil-recursion.js b/tests/jerry/es.next/proxy-evil-recursion.js new file mode 100644 index 0000000000..0c663a1d0c --- /dev/null +++ b/tests/jerry/es.next/proxy-evil-recursion.js @@ -0,0 +1,162 @@ +// 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. + +/* Stack limit check must be enabled to run this test correctly! */ + +function expect_error(error_type, method) { + try { + method(); + assert(false); + } catch (ex) { + assert (ex instanceof error_type); + } +} + +/** + * Create a proxy with recursion via prototype + */ +var proxy_one = new Proxy({length:2}, {}); + +Reflect.setPrototypeOf(proxy_one, proxy_one); /* [[SetPrototypeOf]] does not trigger recursion */ +// aka. proxy_one.__proto__ = proxy_one; /* [[SetPrototypeOf]] */ + +function test_proxy_internals_using_proto(proxy_obj) { + /* These methods can be recursive calls via prototype */ + expect_error(RangeError, function() { proxy_obj[1]; /* [[Get]] */ }); + expect_error(RangeError, function() { proxy_obj[1] = 2; /* [[Set]] */ }); + expect_error(RangeError, function() { 3 in proxy_obj; /* [[Has]] */ }); + expect_error(RangeError, function() { Reflect.has(proxy_obj, 3); /* [[Has]] */ }); +} + +test_proxy_internals_using_proto(proxy_one); + +function test_proxy_internals_not_using_proto(proxy_obj) { + /* The methods below should not trigger recursion via __proto__ */ + delete proxy_obj.none; /* [[Delete]] */ + delete proxy_obj["other"]; /* [[Delete]] */ + + Object.getPrototypeOf(proxy_obj); /* [[GetPrototypeOf]] */ + Reflect.isExtensible(proxy_obj); /* [[IsExtensible]] */ + Object.preventExtensions(proxy_obj); /* [[PreventExtensions]] */ + Reflect.getOwnPropertyDescriptor(proxy_obj, "key"); /* [[GetOwnProperty]] */ + Reflect.defineProperty(proxy_obj, "key2", { value: 4}); /* [[DefineOwnProperty]] */ + Reflect.ownKeys(proxy_obj); /* [[OwnPropertyKeys]] */ +} + +test_proxy_internals_not_using_proto(proxy_one); + +expect_error(TypeError, function() { proxy_one(3, 4); /* [[Call]] */ }); +expect_error(TypeError, function() { new proxy_one(4); /* [[Construct]] */ }); + +/* Special handler to trigger proxy recursion, WARNING DO NOT use in production code! */ +var handler = { + is_handler: true, + proxy_target: false, + counts: { + get: 0, + set: 0, + has: 0, + deleteProperty: 0, + getPrototypeOf: 0, + setPrototypeOf: 0, + isExtensible: 0, + preventExtensions: 0, + getOwnPropertyDescriptor: 0, + defineProperty: 0, + ownKeys: 0, + }, + get: function(target, key) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.get++; + return this.proxy_target.key; + }, + set: function(target, key, value) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.set++; + return this.proxy_target.key = value; + }, + has: function(target, key) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.has++; + return Reflect.has(this.proxy_target, key); + }, + deleteProperty: function(target, key) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.deleteProperty++; + return Reflect.deleteProperty(this.proxy_target, key); + }, + getPrototypeOf: function(target) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.getPrototypeOf++; + return Reflect.getPrototypeOf(this.proxy_target); + }, + setPrototypeOf: function(target, newproto) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.setPrototypeOf++; + return Reflect.setPrototypeOf(this.proxy_target, newproto); + }, + isExtensible: function(target) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.isExtensible++; + return Reflect.isExtensible(this.proxy_target); + }, + preventExtensions: function(target) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.preventExtensions++; + return Reflect.preventExtensions(this.proxy_target); + }, + getOwnPropertyDescriptor: function(target, key) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.getOwnPropertyDescriptor++; + return Reflect.getOwnPropertyDescriptor(this.proxy_target, key); + }, + defineProperty: function(target, key, attrs) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.defineProperty++; + return Reflect.defineProperty(this.proxy_target, key, attrs); + }, + ownKeys: function(target) { + assert(this.is_handler); + assert(this.proxy_target !== false); + this.counts.ownKeys++; + return Reflect.ownKeys(this.proxy_target); + } +}; + + +var proxy_two = new Proxy({length:2}, handler); + +handler.proxy_target = proxy_two; + +test_proxy_internals_using_proto(proxy_two); + +expect_error(RangeError, function() { delete proxy_two.none; /* [[Delete]] */ }); +expect_error(RangeError, function() { delete proxy_two["other"]; /* [[Delete]] */ }); +expect_error(RangeError, function() { Object.getPrototypeOf(proxy_two); /* [[GetPrototypeOf]] */ }); +expect_error(RangeError, function() { Object.setPrototypeOf(proxy_two, {}); /* [[GetPrototypeOf]] */ }); +expect_error(RangeError, function() { Reflect.isExtensible(proxy_two); /* [[IsExtensible]] */ }); +expect_error(RangeError, function() { Object.preventExtensions(proxy_two); /* [[PreventExtensions]] */ }); +expect_error(RangeError, function() { Reflect.getOwnPropertyDescriptor(proxy_two, "key"); /* [[GetOwnProperty]] */ }); +expect_error(RangeError, function() { Reflect.defineProperty(proxy_two, "key2", { value: 4}); /* [[DefineOwnProperty]] */ }); +expect_error(RangeError, function() { Reflect.ownKeys(proxy_two); /* [[OwnPropertyKeys]] */ });