From 444806d78a5c7ccc22106cf2b00e2cc5c527638f Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Tue, 10 Aug 2021 15:50:26 +0200 Subject: [PATCH] Implement vm_throw callback (#4726) Slightly improve the description of vm_exec_stop callback. JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 116 +++++++++- jerry-core/CMakeLists.txt | 9 +- jerry-core/api/jerry.c | 25 ++- jerry-core/config.h | 19 +- jerry-core/ecma/base/ecma-globals.h | 3 + jerry-core/include/jerryscript-core.h | 1 + jerry-core/include/jerryscript-types.h | 10 + jerry-core/jcontext/jcontext.c | 7 +- jerry-core/jcontext/jcontext.h | 5 + jerry-core/vm/vm.c | 17 ++ tests/unit-core/CMakeLists.txt | 3 +- .../{test-exec-stop.c => test-vm-exec-stop.c} | 0 tests/unit-core/test-vm-throw.c | 199 ++++++++++++++++++ tools/build.py | 5 +- tools/run-tests.py | 2 +- 15 files changed, 399 insertions(+), 22 deletions(-) rename tests/unit-core/{test-exec-stop.c => test-vm-exec-stop.c} (100%) create mode 100644 tests/unit-core/test-vm-throw.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index be70db7a9..211206ffb 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -153,6 +153,7 @@ Possible compile time enabled feature types: - JERRY_FEATURE_SNAPSHOT_EXEC - executing snapshot files - JERRY_FEATURE_DEBUGGER - debugging - JERRY_FEATURE_VM_EXEC_STOP - stopping ECMAScript execution + - JERRY_FEATURE_VM_THROW - capturing ECMAScript throws - JERRY_FEATURE_JSON - JSON support - JERRY_FEATURE_PROMISE - promise support - JERRY_FEATURE_TYPEDARRAY - Typedarray support @@ -1122,6 +1123,32 @@ typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p); - [jerry_set_vm_exec_stop_callback](#jerry_set_vm_exec_stop_callback) +## jerry_vm_throw_callback_t + +**Summary** + +Callback which is called when a value is thrown in an ECMAScript code. The callback +should not change the `error_value`. The callback is not called again until the value +is caught. + +Note: + - The engine considers errors thrown by external functions as never caught. The + application can maintain a status flag to ignore the next call of the callback + if necessary. + See: [jerry_create_external_function](#jerry_create_external_function) + +**Prototype** + +```c +typedef void (*jerry_vm_throw_callback_t) (const jerry_value_t error_value, void *user_p); +``` + +*New in [[NEXT_RELEASE]]*. + +**See also** + +- [jerry_set_vm_throw_callback](#jerry_set_vm_throw_callback) + ## jerry_promise_state_t Enum which describes the state of a Promise. @@ -11329,8 +11356,7 @@ backtrace_callback (jerry_backtrace_frame_t *frame_p, **Summary** -When JERRY_FEATURE_VM_EXEC_STOP is enabled a callback function can be -specified by this function. This callback is periodically called when +The callback passed to this function is periodically called when JerryScript executes an ECMAScript program. If the callback returns with undefined value the ECMAScript execution @@ -11345,6 +11371,10 @@ or an exception is caught. Setting the `frequency` to a greater than `1` value reduces this overhead further. If its value is N only every Nth event (backward jump, etc.) trigger the next check. +*Notes*: +- This API depends on a build option (`JERRY_VM_EXEC_STOP`) and can be checked + in runtime with the `JERRY_FEATURE_VM_EXEC_STOP` feature enum value, + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). **Prototype** @@ -11368,14 +11398,14 @@ jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, ```c #include "jerryscript.h" -static int countdown = 10; - static jerry_value_t vm_exec_stop_callback (void *user_p) { - while (countdown > 0) + int *countdown_p = (int *) user_p; + + while (*countdown_p > 0) { - countdown--; + (*countdown_p)--; return jerry_create_undefined (); } @@ -11388,6 +11418,7 @@ main (void) { jerry_init (JERRY_INIT_EMPTY); + int countdown = 10; jerry_set_vm_exec_stop_callback (vm_exec_stop_callback, &countdown, 16); // Infinite loop. @@ -11402,12 +11433,77 @@ main (void) **See also** -- [jerry_init](#jerry_init) -- [jerry_cleanup](#jerry_cleanup) -- [jerry_parse](#jerry_parse) -- [jerry_run](#jerry_run) - [jerry_vm_exec_stop_callback_t](#jerry_vm_exec_stop_callback_t) +## jerry_set_vm_throw_callback + +**Summary** + +The callback passed to this function is called when an error is thrown +in ECMAScript code. The callback is not called again until the value is +caught. See: [jerry_vm_throw_callback_t](#jerry_vm_throw_callback_t). + +*Notes*: +- This API depends on a build option (`JERRY_VM_THROW`) and can be checked + in runtime with the `JERRY_FEATURE_VM_THROW` feature enum value, + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). + +**Prototype** + +```c +void +jerry_set_vm_throw_callback (jerry_vm_throw_callback_t throw_cb, + void *user_p); +``` + +- `throw_cb` - callback which is called on throws (passing NULL disables this feature) +- `user_p` - user pointer passed to the `throw_cb` function + +*New in version [[NEXT_RELEASE]]*. + +**Example** + +[doctest]: # (test="compile") + +```c +#include "jerryscript.h" + +static void +vm_throw_callback (const jerry_value_t error_value, /**< captured error */ + void *user_p) /**< user pointer */ +{ + (void) error_value; + + /* Counts the number of throws. */ + int *counter_p = (int *) user_p; + (*counter_p)++; +} + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + int counter = 0; + jerry_set_vm_throw_callback (vm_throw_callback, &counter); + + const jerry_char_t script[] = "try { throw new Error('1') } catch (e) { throw new Error('2') }"; + + jerry_value_t parsed_code = jerry_parse (script, sizeof (script) - 1, NULL); + jerry_release_value (jerry_run (parsed_code)); + jerry_release_value (parsed_code); + + /* The counter contains 2. */ + + jerry_cleanup (); + return 0; +} +``` + +**See also** + +- [jerry_vm_throw_callback_t](#jerry_vm_throw_callback_t) + ## jerry_get_resource_name **Summary** diff --git a/jerry-core/CMakeLists.txt b/jerry-core/CMakeLists.txt index b988da780..cb3c22853 100644 --- a/jerry-core/CMakeLists.txt +++ b/jerry-core/CMakeLists.txt @@ -37,7 +37,8 @@ set(JERRY_SNAPSHOT_EXEC OFF CACHE BOOL "Enable executing set(JERRY_SNAPSHOT_SAVE OFF CACHE BOOL "Enable saving snapshot files?") set(JERRY_SYSTEM_ALLOCATOR OFF CACHE BOOL "Enable system allocator?") set(JERRY_VALGRIND OFF CACHE BOOL "Enable Valgrind support?") -set(JERRY_VM_EXEC_STOP OFF CACHE BOOL "Enable VM execution stopping?") +set(JERRY_VM_EXEC_STOP OFF CACHE BOOL "Enable VM execution stop callback?") +set(JERRY_VM_THROW OFF CACHE BOOL "Enable VM throw callback?") set(JERRY_GLOBAL_HEAP_SIZE "(512)" CACHE STRING "Size of memory heap, in kilobytes") set(JERRY_GC_LIMIT "(0)" CACHE STRING "Heap usage limit to trigger garbage collection") set(JERRY_STACK_LIMIT "(0)" CACHE STRING "Maximum stack usage size, in kilobytes") @@ -96,6 +97,7 @@ message(STATUS "JERRY_SNAPSHOT_SAVE " ${JERRY_SNAPSHOT_SAVE} ${JERRY_ message(STATUS "JERRY_SYSTEM_ALLOCATOR " ${JERRY_SYSTEM_ALLOCATOR}) message(STATUS "JERRY_VALGRIND " ${JERRY_VALGRIND}) message(STATUS "JERRY_VM_EXEC_STOP " ${JERRY_VM_EXEC_STOP}) +message(STATUS "JERRY_VM_THROW " ${JERRY_VM_THROW}) message(STATUS "JERRY_GLOBAL_HEAP_SIZE " ${JERRY_GLOBAL_HEAP_SIZE}) message(STATUS "JERRY_GC_LIMIT " ${JERRY_GC_LIMIT}) message(STATUS "JERRY_STACK_LIMIT " ${JERRY_STACK_LIMIT}) @@ -627,9 +629,12 @@ if(JERRY_VALGRIND) set(INCLUDE_CORE_PRIVATE ${INCLUDE_CORE_PRIVATE} ${INCLUDE_THIRD_PARTY_VALGRIND}) endif() -# Enable VM execution stopping +# Enable VM execution stop callback jerry_add_define01(JERRY_VM_EXEC_STOP) +# Enable VM throw callback +jerry_add_define01(JERRY_VM_THROW) + # Size of heap set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_GLOBAL_HEAP_SIZE=${JERRY_GLOBAL_HEAP_SIZE}) diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 44bb1649c..9797f9042 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -1800,6 +1800,9 @@ jerry_is_feature_enabled (const jerry_feature_t feature) /**< feature to check * #if JERRY_VM_EXEC_STOP || feature == JERRY_FEATURE_VM_EXEC_STOP #endif /* JERRY_VM_EXEC_STOP */ +#if JERRY_VM_THROW + || feature == JERRY_FEATURE_VM_THROW +#endif /* JERRY_VM_THROW */ #if JERRY_BUILTIN_JSON || feature == JERRY_FEATURE_JSON #endif /* JERRY_BUILTIN_JSON */ @@ -5202,8 +5205,8 @@ jerry_create_context (uint32_t heap_size, /**< the size of heap */ } /* jerry_create_context */ /** - * If JERRY_VM_EXEC_STOP is enabled the callback passed to this function is - * periodically called with the user_p argument. If frequency is greater + * When JERRY_VM_EXEC_STOP is enabled, the callback passed to this function + * is periodically called with the user_p argument. If frequency is greater * than 1, the callback is only called at every frequency ticks. */ void @@ -5219,8 +5222,8 @@ jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, /**< per JERRY_CONTEXT (vm_exec_stop_frequency) = frequency; JERRY_CONTEXT (vm_exec_stop_counter) = frequency; - JERRY_CONTEXT (vm_exec_stop_user_p) = user_p; JERRY_CONTEXT (vm_exec_stop_cb) = stop_cb; + JERRY_CONTEXT (vm_exec_stop_user_p) = user_p; #else /* !JERRY_VM_EXEC_STOP */ JERRY_UNUSED (stop_cb); JERRY_UNUSED (user_p); @@ -5228,6 +5231,22 @@ jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, /**< per #endif /* JERRY_VM_EXEC_STOP */ } /* jerry_set_vm_exec_stop_callback */ +/** + * When JERRY_VM_THROW is enabled, the callback passed to this + * function is called when an error is thrown in ECMAScript code. + */ +void jerry_set_vm_throw_callback (jerry_vm_throw_callback_t throw_cb, /**< callback which is called on throws */ + void *user_p) /**< pointer passed to the function */ +{ +#if JERRY_VM_THROW + JERRY_CONTEXT (vm_throw_callback_p) = throw_cb; + JERRY_CONTEXT (vm_throw_callback_user_p) = user_p; +#else /* !JERRY_VM_THROW */ + JERRY_UNUSED (throw_cb); + JERRY_UNUSED (user_p); +#endif /* JERRY_VM_THROW */ +} /* jerry_set_vm_throw_callback */ + /** * Get backtrace. The backtrace is an array of strings where * each string contains the position of the corresponding frame. diff --git a/jerry-core/config.h b/jerry-core/config.h index d5b262c58..72592f3c5 100644 --- a/jerry-core/config.h +++ b/jerry-core/config.h @@ -430,13 +430,24 @@ * Enable/Disable the vm execution stop callback function. * * Allowed values: - * 0: Disable vm exec stop callbacks. - * 1: Enable vm exec stop callback functionality. + * 0: Disable vm exec stop callback support. + * 1: Enable vm exec stop callback support. */ #ifndef JERRY_VM_EXEC_STOP # define JERRY_VM_EXEC_STOP 0 #endif /* !defined (JERRY_VM_EXEC_STOP) */ +/** + * Enable/Disable the vm throw callback function. + * + * Allowed values: + * 0: Disable vm throw callback support. + * 1: Enable vm throw callback support. + */ +#ifndef JERRY_VM_THROW +# define JERRY_VM_THROW 0 +#endif /* !defined (JERRY_VM_THROW) */ + /** * Advanced section configurations. */ @@ -680,6 +691,10 @@ || ((JERRY_VM_EXEC_STOP != 0) && (JERRY_VM_EXEC_STOP != 1)) # error "Invalid value for 'JERRY_VM_EXEC_STOP' macro." #endif +#if !defined (JERRY_VM_THROW) \ +|| ((JERRY_VM_THROW != 0) && (JERRY_VM_THROW != 1)) +# error "Invalid value for 'JERRY_VM_THROW' macro." +#endif /** * Cross component requirements check. diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 717df03cc..134b173b5 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -63,6 +63,9 @@ typedef enum ECMA_STATUS_EXCEPTION = (1u << 3), /**< last exception is a normal exception */ ECMA_STATUS_ABORT = (1u << 4), /**< last exception is an abort */ ECMA_STATUS_ERROR_UPDATE = (1u << 5), /**< the error_object_created_callback_p is called */ +#if JERRY_VM_THROW + ECMA_STATUS_ERROR_THROWN = (1u << 6), /**< the vm_throw_callback_p is called */ +#endif /* JERRY_VM_THROW */ } ecma_status_flag_t; /** diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 6aa6ae510..0e5e9f494 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -360,6 +360,7 @@ bool jerry_backtrace_is_strict (jerry_backtrace_frame_t *frame_p); * Miscellaneous functions. */ void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, void *user_p, uint32_t frequency); +void jerry_set_vm_throw_callback (jerry_vm_throw_callback_t throw_cb, void *user_p); jerry_value_t jerry_get_resource_name (const jerry_value_t value); jerry_value_t jerry_get_user_value (const jerry_value_t value); diff --git a/jerry-core/include/jerryscript-types.h b/jerry-core/include/jerryscript-types.h index 7eccef4e6..7635597d8 100644 --- a/jerry-core/include/jerryscript-types.h +++ b/jerry-core/include/jerryscript-types.h @@ -91,6 +91,7 @@ typedef enum JERRY_FEATURE_SNAPSHOT_EXEC, /**< executing snapshot files */ JERRY_FEATURE_DEBUGGER, /**< debugging */ JERRY_FEATURE_VM_EXEC_STOP, /**< stopping ECMAScript execution */ + JERRY_FEATURE_VM_THROW, /**< capturing ECMAScript throws */ JERRY_FEATURE_JSON, /**< JSON support */ JERRY_FEATURE_PROMISE, /**< promise support */ JERRY_FEATURE_TYPEDARRAY, /**< Typedarray support */ @@ -306,6 +307,15 @@ typedef void (*jerry_error_object_created_callback_t) (const jerry_value_t error */ typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p); +/** + * Callback function which is called when an error is thrown in an ECMAScript code. + * The callback should not change the error_value. The callback is not called again + * until the value is caught. + * + * Note: the engine considers errors thrown by external functions as never caught. + */ +typedef void (*jerry_vm_throw_callback_t) (const jerry_value_t error_value, void *user_p); + /** * Function type applied for each data property of an object. */ diff --git a/jerry-core/jcontext/jcontext.c b/jerry-core/jcontext/jcontext.c index 487bd3516..7f92a332a 100644 --- a/jerry-core/jcontext/jcontext.c +++ b/jerry-core/jcontext/jcontext.c @@ -113,8 +113,11 @@ jcontext_take_exception (void) { JERRY_ASSERT (jcontext_has_pending_exception ()); - jcontext_set_abort_flag (false); - jcontext_set_exception_flag (false); + JERRY_CONTEXT (status_flags) &= (uint32_t) ~(ECMA_STATUS_EXCEPTION +#if JERRY_VM_THROW + | ECMA_STATUS_ERROR_THROWN +#endif /* JERRY_VM_THROW */ + | ECMA_STATUS_ABORT); return JERRY_CONTEXT (error_value); } /* jcontext_take_exception */ diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index 0b21c465e..5ecb17cfc 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -200,6 +200,11 @@ struct jerry_context_t * ECMAScript execution should be stopped */ #endif /* JERRY_VM_EXEC_STOP */ +#if JERRY_VM_THROW + void *vm_throw_callback_user_p; /**< user pointer for vm_throw_callback_p */ + jerry_vm_throw_callback_t vm_throw_callback_p; /**< callback for capturing throws */ +#endif /* JERRY_VM_THROW */ + #if (JERRY_STACK_LIMIT != 0) uintptr_t stack_base; /**< stack base marker */ #endif /* (JERRY_STACK_LIMIT != 0) */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 2422c4eb1..bfa251a81 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -4457,6 +4457,9 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ if (context_type == VM_CONTEXT_FINALLY_THROW) { jcontext_raise_exception (*stack_top_p); +#if JERRY_VM_THROW + JERRY_CONTEXT (status_flags) |= ECMA_STATUS_ERROR_THROWN; +#endif /* JERRY_VM_THROW */ result = ECMA_VALUE_ERROR; #if JERRY_DEBUGGER @@ -4789,6 +4792,20 @@ error: ecma_fast_free_value (stack_item); } +#if JERRY_VM_THROW + if (!(JERRY_CONTEXT (status_flags) & ECMA_STATUS_ERROR_THROWN)) + { + JERRY_CONTEXT (status_flags) |= ECMA_STATUS_ERROR_THROWN; + + jerry_vm_throw_callback_t vm_throw_callback_p = JERRY_CONTEXT (vm_throw_callback_p); + + if (vm_throw_callback_p != NULL) + { + vm_throw_callback_p (JERRY_CONTEXT (error_value), JERRY_CONTEXT (vm_throw_callback_user_p)); + } + } +#endif /* JERRY_VM_THROW */ + #if JERRY_DEBUGGER const uint32_t dont_stop = (JERRY_DEBUGGER_VM_IGNORE_EXCEPTION | JERRY_DEBUGGER_VM_IGNORE diff --git a/tests/unit-core/CMakeLists.txt b/tests/unit-core/CMakeLists.txt index f8f288bc8..6ec5a1c15 100644 --- a/tests/unit-core/CMakeLists.txt +++ b/tests/unit-core/CMakeLists.txt @@ -48,7 +48,6 @@ set(SOURCE_UNIT_TEST_MAIN_MODULES test-context-data.c test-dataview.c test-date-helpers.c - test-exec-stop.c test-external-string.c test-from-property-descriptor.c test-get-own-property.c @@ -91,6 +90,8 @@ set(SOURCE_UNIT_TEST_MAIN_MODULES test-to-property-descriptor.c test-typedarray.c test-unicode.c + test-vm-exec-stop.c + test-vm-throw.c ) # jerry_heap_stats_t.size == 0 if system allocator is used. diff --git a/tests/unit-core/test-exec-stop.c b/tests/unit-core/test-vm-exec-stop.c similarity index 100% rename from tests/unit-core/test-exec-stop.c rename to tests/unit-core/test-vm-exec-stop.c diff --git a/tests/unit-core/test-vm-throw.c b/tests/unit-core/test-vm-throw.c new file mode 100644 index 000000000..6eb50ccc7 --- /dev/null +++ b/tests/unit-core/test-vm-throw.c @@ -0,0 +1,199 @@ +/* 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 "config.h" +#include "jerryscript.h" + +#include "test-common.h" + +static int mode = 0; +static int counter = 0; + +static void +vm_throw_callback (const jerry_value_t error_value, /**< captured error */ + void *user_p) /**< user pointer */ +{ + TEST_ASSERT (user_p == (void *) &mode); + counter++; + + switch (mode) + { + case 0: + { + TEST_ASSERT (counter == 1); + TEST_ASSERT (jerry_value_is_number (error_value) && jerry_get_number_value (error_value) == -5.6); + break; + } + case 1: + { + TEST_ASSERT (counter == 1); + TEST_ASSERT (jerry_value_is_null (error_value)); + break; + } + case 2: + { + jerry_char_t string_buf[2]; + jerry_size_t size = sizeof (string_buf); + + string_buf[0] = '\0'; + string_buf[1] = '\0'; + + TEST_ASSERT (counter >= 1 && counter <= 3); + TEST_ASSERT (jerry_value_is_string (error_value)); + TEST_ASSERT (jerry_get_string_size (error_value) == size); + TEST_ASSERT (jerry_string_to_char_buffer (error_value, string_buf, size) == size); + TEST_ASSERT (string_buf[0] == 'e' && string_buf[1] == (char) ('0' + counter)); + break; + } + case 3: + { + TEST_ASSERT (counter == 1); + TEST_ASSERT (jerry_get_error_type (error_value) == JERRY_ERROR_RANGE); + break; + } + case 4: + { + TEST_ASSERT (mode == 4); + TEST_ASSERT (counter >= 1 && counter <= 2); + + jerry_error_t error = (counter == 1) ? JERRY_ERROR_REFERENCE : JERRY_ERROR_TYPE; + TEST_ASSERT (jerry_get_error_type (error_value) == error); + break; + } + case 5: + { + TEST_ASSERT (counter >= 1 && counter <= 2); + TEST_ASSERT (jerry_value_is_false (error_value)); + break; + } + default: + { + TEST_ASSERT (mode == 6 || mode == 7); + TEST_ASSERT (counter == 1); + TEST_ASSERT (jerry_value_is_true (error_value)); + break; + } + } +} /* vm_throw_callback */ + +static jerry_value_t +native_handler (const jerry_call_info_t *call_info_p, /**< call info */ + const jerry_value_t args_p[], /**< arguments */ + const jerry_length_t args_count) /**< arguments length */ +{ + (void) call_info_p; + (void) args_p; + TEST_ASSERT (args_count == 0); + + jerry_char_t source[] = TEST_STRING_LITERAL ("throw false"); + return jerry_eval (source, sizeof (source) - 1, JERRY_PARSE_NO_OPTS); +} /* native_handler */ + +static void +do_eval (const char *script_p, /**< script to evaluate */ + bool should_throw) /**< script throws an error */ +{ + jerry_value_t result = jerry_eval ((const jerry_char_t *) script_p, strlen (script_p), JERRY_PARSE_NO_OPTS); + TEST_ASSERT (jerry_value_is_error (result) == should_throw); + jerry_release_value (result); +} /* do_eval */ + +int +main (void) +{ + TEST_INIT (); + + /* Test stopping an infinite loop. */ + if (!jerry_is_feature_enabled (JERRY_FEATURE_VM_THROW)) + { + return 0; + } + + jerry_init (JERRY_INIT_EMPTY); + + jerry_set_vm_throw_callback (vm_throw_callback, (void *) &mode); + + mode = 0; + counter = 0; + do_eval (TEST_STRING_LITERAL ("throw -5.6"), + true); + TEST_ASSERT (counter == 1); + + mode = 1; + counter = 0; + do_eval (TEST_STRING_LITERAL ("function f() { throw null }\n" + "function g() { f() }\n" + "g()\n"), + true); + TEST_ASSERT (counter == 1); + + mode = 2; + counter = 0; + do_eval (TEST_STRING_LITERAL ("function f() { throw 'e1' }\n" + "function g() { try { f() } catch (e) { throw 'e2' } }\n" + "try { g() } catch (e) { throw 'e3' }\n"), + true); + TEST_ASSERT (counter == 3); + + mode = 3; + counter = 0; + do_eval (TEST_STRING_LITERAL ("function f() { throw new RangeError() }\n" + "function g() { try { f() } finally { } }\n" + "try { g() } finally { }\n"), + true); + TEST_ASSERT (counter == 1); + + mode = 4; + counter = 0; + do_eval (TEST_STRING_LITERAL ("function f() { unresolved }\n" + "function g() { try { f() } finally { null.member } }\n" + "try { g() } finally { }\n"), + true); + TEST_ASSERT (counter == 2); + + /* Native functions may trigger the call twice: */ + jerry_value_t global_object_value = jerry_get_global_object (); + jerry_value_t function_value = jerry_create_external_function (native_handler); + jerry_value_t function_name_value = jerry_create_string ((const jerry_char_t *) "native"); + + jerry_release_value (jerry_set_property (global_object_value, function_name_value, function_value)); + jerry_release_value (function_name_value); + jerry_release_value (function_value); + jerry_release_value (global_object_value); + + mode = 5; + counter = 0; + do_eval (TEST_STRING_LITERAL ("native()\n"), + true); + TEST_ASSERT (counter == 2); + + /* Built-in functions should not trigger the call twice: */ + mode = 6; + counter = 0; + do_eval (TEST_STRING_LITERAL ("function f() { eval('eval(\\'throw true\\')') }\n" + "f()\n"), + true); + TEST_ASSERT (counter == 1); + + mode = 7; + counter = 0; + do_eval (TEST_STRING_LITERAL ("function f() { [1].map(function() { throw true }) }\n" + "f()\n"), + true); + TEST_ASSERT (counter == 1); + + jerry_cleanup (); + return 0; +} /* main */ diff --git a/tools/build.py b/tools/build.py index fe772d412..5a38476c3 100755 --- a/tools/build.py +++ b/tools/build.py @@ -155,7 +155,9 @@ def get_arguments(): coregrp.add_argument('--valgrind', metavar='X', choices=['ON', 'OFF'], type=str.upper, help=devhelp('enable Valgrind support (%(choices)s)')) coregrp.add_argument('--vm-exec-stop', metavar='X', choices=['ON', 'OFF'], type=str.upper, - help='enable VM execution stopping (%(choices)s)') + help='enable VM execution stop callback (%(choices)s)') + coregrp.add_argument('--vm-throw', metavar='X', choices=['ON', 'OFF'], type=str.upper, + help='enable VM throw callback (%(choices)s)') maingrp = parser.add_argument_group('jerry-main options') maingrp.add_argument('--link-map', metavar='X', choices=['ON', 'OFF'], type=str.upper, @@ -223,6 +225,7 @@ def generate_build_options(arguments): build_options_append('JERRY_SYSTEM_ALLOCATOR', arguments.system_allocator) build_options_append('JERRY_VALGRIND', arguments.valgrind) build_options_append('JERRY_VM_EXEC_STOP', arguments.vm_exec_stop) + build_options_append('JERRY_VM_THROW', arguments.vm_throw) if arguments.gc_mark_limit is not None: build_options.append('-D%s=%s' % ('JERRY_GC_MARK_LIMIT', arguments.gc_mark_limit)) diff --git a/tools/run-tests.py b/tools/run-tests.py index 2f031c240..b7d2e934b 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -50,7 +50,7 @@ OPTIONS_DEBUG = ['--debug'] OPTIONS_SNAPSHOT = ['--snapshot-save=on', '--snapshot-exec=on', '--jerry-cmdline-snapshot=on'] OPTIONS_UNITTESTS = ['--unittests=on', '--jerry-cmdline=off', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on', - '--line-info=on', '--mem-stats=on'] + '--vm-throw=on', '--line-info=on', '--mem-stats=on'] OPTIONS_DOCTESTS = ['--doctests=on', '--jerry-cmdline=off', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on']