diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index ca00380784..8cf416e2c0 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -36,6 +36,7 @@ Possible compile time enabled feature types: - JERRY_FEATURE_SNAPSHOT_SAVE - saving snapshot files - JERRY_FEATURE_SNAPSHOT_EXEC - executing snapshot files - JERRY_FEATURE_DEBUGGER - debugging + - JERRY_FEATURE_VM_EXEC_STOP - stopping ECMAScript execution ## jerry_char_t @@ -234,6 +235,28 @@ typedef bool (*jerry_object_property_foreach_t) (const jerry_value_t property_na void *user_data_p); ``` +## jerry_vm_exec_stop_callback_t + +**Summary** + +Callback which tells whether the ECMAScript execution should be stopped. +If it returns with undefined value the ECMAScript execution continues. +Otherwise the result is thrown by the engine (if the error flag is not +set for the returned value the engine automatically sets it). The +callback function might be called again even if it threw an error. +In this case the function must throw the same error again. + +**Prototype** + +```c +typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p); +``` + +**See also** + +- [jerry_set_vm_exec_stop_callback](#jerry_set_vm_exec_stop_callback) + + # General engine functions ## jerry_init @@ -3896,3 +3919,81 @@ jerry_parse_and_save_literals (const jerry_char_t *source_p, - [jerry_init](#jerry_init) - [jerry_cleanup](#jerry_cleanup) - [jerry_register_magic_strings](#jerry_register_magic_strings) + + +# Miscellaneous functions + +## jerry_set_vm_exec_stop_callback + +**Summary** + +When JERRY_FEATURE_VM_EXEC_STOP is enabled a callback function can be +specified by this function. This callback is periodically called when +JerryScript executes an ECMAScript program. + +If the callback returns with undefined value the ECMAScript execution +continues. Otherwise the result is thrown by the engine (if the error +flag is not set for the returned value the engine automatically sets +it). The callback function might be called again even if it threw +an error. In this case the function must throw the same error again. + +To reduce the CPU overhead of constantly checking the termination +condition the callback is called when a backward jump is executed +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. + + +**Prototype** + +```c +void +jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, + void *user_p, + uint32_t frequency); +``` + +- `stop_cb` - periodically called callback (passing NULL disables this feature) +- `user_p` - user pointer passed to the `stop_cb` function +- `frequency` - frequency of calling the `stop_cb` function + +**Example** + +```c +static jerry_value_t +vm_exec_stop_callback (void *user_p) +{ + static int countdown = 10; + + while (countdown > 0) + { + countdown--; + return jerry_create_undefined (); + } + + // The error flag is added automatically. + return jerry_create_string ((const jerry_char_t *) "Abort script"); +} + +{ + jerry_init (JERRY_INIT_EMPTY); + + jerry_set_vm_exec_stop_callback (vm_exec_stop_callback, &countdown, 16); + + // Inifinte loop. + const char *src_p = "while(true) {}"; + + jerry_value_t src = jerry_parse ((jerry_char_t *) src_p, strlen (src_p), false); + jerry_release_value (jerry_run (src)); + jerry_release_value (src); + jerry_cleanup (); +} +``` + +**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) diff --git a/jerry-core/CMakeLists.txt b/jerry-core/CMakeLists.txt index f1d5f238b3..1649a0fd63 100644 --- a/jerry-core/CMakeLists.txt +++ b/jerry-core/CMakeLists.txt @@ -32,6 +32,7 @@ set(FEATURE_SNAPSHOT_SAVE OFF CACHE BOOL "Enable saving snapshot files? set(FEATURE_SYSTEM_ALLOCATOR OFF CACHE BOOL "Enable system allocator?") set(FEATURE_VALGRIND OFF CACHE BOOL "Enable Valgrind support?") set(FEATURE_VALGRIND_FREYA OFF CACHE BOOL "Enable Valgrind-Freya support?") +set(FEATURE_VM_EXEC_STOP OFF CACHE BOOL "Enable VM execution stopping?") set(MEM_HEAP_SIZE_KB "512" CACHE STRING "Size of memory heap, in kilobytes") if(FEATURE_SYSTEM_ALLOCATOR) @@ -54,6 +55,7 @@ message(STATUS "FEATURE_SNAPSHOT_SAVE " ${FEATURE_SNAPSHOT_SAVE}) message(STATUS "FEATURE_SYSTEM_ALLOCATOR " ${FEATURE_SYSTEM_ALLOCATOR}) message(STATUS "FEATURE_VALGRIND " ${FEATURE_VALGRIND}) message(STATUS "FEATURE_VALGRIND_FREYA " ${FEATURE_VALGRIND_FREYA}) +message(STATUS "FEATURE_VM_EXEC_STOP " ${FEATURE_VM_EXEC_STOP}) message(STATUS "MEM_HEAP_SIZE_KB " ${MEM_HEAP_SIZE_KB}) # Include directories @@ -238,6 +240,11 @@ if(FEATURE_VALGRIND_FREYA) set(INCLUDE_CORE ${INCLUDE_CORE} ${INCLUDE_THIRD_PARTY_VALGRIND}) endif() +# Enable VM execution stopping +if (FEATURE_VM_EXEC_STOP) + set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_VM_EXEC_STOP) +endif() + # Size of heap math(EXPR MEM_HEAP_AREA_SIZE "${MEM_HEAP_SIZE_KB} * 1024") set(DEFINES_JERRY ${DEFINES_JERRY} CONFIG_MEM_HEAP_AREA_SIZE=${MEM_HEAP_AREA_SIZE}) diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index 57d32be434..32f49b32ff 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -84,6 +84,14 @@ typedef struct uint8_t re_cache_idx; /**< evicted item index when regex cache is full (round-robin) */ #endif /* !CONFIG_DISABLE_REGEXP_BUILTIN */ +#ifdef JERRY_VM_EXEC_STOP + uint32_t vm_exec_stop_frequency; /**< reset value for vm_exec_stop_counter */ + uint32_t vm_exec_stop_counter; /**< down counter for reducing the calls of vm_exec_stop_cb */ + void *vm_exec_stop_user_p; /**< user pointer for vm_exec_stop_cb */ + jerry_vm_exec_stop_callback_t vm_exec_stop_cb; /**< user function which returns whether the + * ECMAScript execution should be stopped */ +#endif /* JERRY_VM_EXEC_STOP */ + #ifdef JERRY_DEBUGGER uint8_t debugger_send_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for sending messages */ uint8_t debugger_receive_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for receiving messages */ diff --git a/jerry-core/jerry.c b/jerry-core/jerry.c index 83b53908fa..fcca46bfd2 100644 --- a/jerry-core/jerry.c +++ b/jerry-core/jerry.c @@ -618,6 +618,9 @@ bool jerry_is_feature_enabled (const jerry_feature_t feature) #ifdef JERRY_DEBUGGER || feature == JERRY_FEATURE_DEBUGGER #endif /* JERRY_DEBUGGER */ +#ifdef JERRY_VM_EXEC_STOP + || feature == JERRY_FEATURE_VM_EXEC_STOP +#endif /* JERRY_VM_EXEC_STOP */ ); } /* jerry_is_feature_enabled */ @@ -2145,6 +2148,33 @@ jerry_is_valid_cesu8_string (const jerry_char_t *cesu8_buf_p, /**< CESU-8 string (lit_utf8_size_t) buf_size); } /* jerry_is_valid_cesu8_string */ +/** + * If JERRY_VM_EXEC_STOP is defined 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 +jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, /**< periodically called user function */ + void *user_p, /**< pointer passed to the function */ + uint32_t frequency) /**< frequency of the function call */ +{ +#ifdef JERRY_VM_EXEC_STOP + if (frequency == 0) + { + frequency = 1; + } + + 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; +#else /* !JERRY_VM_EXEC_STOP */ + JERRY_UNUSED (stop_cb); + JERRY_UNUSED (user_p); + JERRY_UNUSED (frequency); +#endif /* JERRY_VM_EXEC_STOP */ +} /* jerry_set_vm_exec_stop_callback */ + /** * @} */ diff --git a/jerry-core/jerryscript.h b/jerry-core/jerryscript.h index 154505d43d..be9d4a53e3 100644 --- a/jerry-core/jerryscript.h +++ b/jerry-core/jerryscript.h @@ -86,6 +86,7 @@ typedef enum JERRY_FEATURE_SNAPSHOT_SAVE, /**< saving snapshot files */ JERRY_FEATURE_SNAPSHOT_EXEC, /**< executing snapshot files */ JERRY_FEATURE_DEBUGGER, /**< debugging */ + JERRY_FEATURE_VM_EXEC_STOP, /**< stopping ECMAScript execution */ JERRY_FEATURE__COUNT /**< number of features. NOTE: must be at the end of the list */ } jerry_feature_t; @@ -174,6 +175,19 @@ typedef void (*jerry_object_free_callback_t) (const uintptr_t native_p); */ typedef void (*jerry_object_native_free_callback_t) (void *native_p); +/** + * Callback which tells whether the ECMAScript execution should be stopped. + * + * As long as the function returns with undefined the execution continues. + * When a non-undefined value is returned the execution stops and the value + * is thrown by the engine (if the error flag is not set for the returned + * value the engine automatically sets it). + * + * Note: if the function returns with a non-undefined value it + * must return with the same value for future calls. + */ +typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p); + /** * Function type applied for each data property of an object. */ @@ -388,6 +402,11 @@ jerry_value_t jerry_exec_snapshot (const uint32_t *snapshot_p, size_t snapshot_s size_t jerry_parse_and_save_literals (const jerry_char_t *source_p, size_t source_size, bool is_strict, uint32_t *buffer_p, size_t buffer_size, bool is_c_format); +/** + * Miscellaneous functions. + */ +void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, void *user_p, uint32_t frequency); + /** * @} */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index fd0d3fa39d..6f01ccb74a 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -847,6 +847,29 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ if (opcode_data & VM_OC_BACKWARD_BRANCH) { +#ifdef JERRY_VM_EXEC_STOP + if (JERRY_CONTEXT (vm_exec_stop_cb) != NULL + && --JERRY_CONTEXT (vm_exec_stop_counter) == 0) + { + result = JERRY_CONTEXT (vm_exec_stop_cb) (JERRY_CONTEXT (vm_exec_stop_user_p)); + + if (ecma_is_value_undefined (result)) + { + JERRY_CONTEXT (vm_exec_stop_counter) = JERRY_CONTEXT (vm_exec_stop_frequency); + } + else + { + JERRY_CONTEXT (vm_exec_stop_counter) = 1; + + if (!ECMA_IS_VALUE_ERROR (result)) + { + result = ecma_make_error_value (result); + } + goto error; + } + } +#endif /* JERRY_VM_EXEC_STOP */ + branch_offset = -branch_offset; } } @@ -2595,6 +2618,33 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ JERRY_ASSERT (VM_GET_CONTEXT_TYPE (stack_top_p[-1]) == VM_CONTEXT_FINALLY_THROW); stack_top_p[-2] = result; } + +#ifdef JERRY_VM_EXEC_STOP + if (JERRY_CONTEXT (vm_exec_stop_cb) != NULL + && --JERRY_CONTEXT (vm_exec_stop_counter) == 0) + { + result = JERRY_CONTEXT (vm_exec_stop_cb) (JERRY_CONTEXT (vm_exec_stop_user_p)); + + if (ecma_is_value_undefined (result)) + { + JERRY_CONTEXT (vm_exec_stop_counter) = JERRY_CONTEXT (vm_exec_stop_frequency); + } + else + { + JERRY_CONTEXT (vm_exec_stop_counter) = 1; + + left_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED); + right_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED); + + if (!ECMA_IS_VALUE_ERROR (result)) + { + result = ecma_make_error_value (result); + } + goto error; + } + } +#endif /* JERRY_VM_EXEC_STOP */ + continue; } } diff --git a/tests/unit-core/test-api.c b/tests/unit-core/test-api.c index 7593b26733..1a3b50d001 100644 --- a/tests/unit-core/test-api.c +++ b/tests/unit-core/test-api.c @@ -171,6 +171,21 @@ handler_construct (const jerry_value_t func_obj_val, /**< function object */ return jerry_create_boolean (true); } /* handler_construct */ +static jerry_value_t +vm_exec_stop_callback (void *user_p) +{ + int *int_p = (int *) user_p; + + if (*int_p > 0) + { + (*int_p)--; + + return jerry_create_undefined (); + } + + return jerry_create_string ((const jerry_char_t *) "Abort script"); +} /* vm_exec_stop_callback */ + /** * Extended Magic Strings */ @@ -1016,6 +1031,50 @@ main (void) jerry_cleanup (); } + /* Test stopping an infinite loop. */ + if (jerry_is_feature_enabled (JERRY_FEATURE_VM_EXEC_STOP)) + { + jerry_init (JERRY_INIT_EMPTY); + + int countdown = 6; + jerry_set_vm_exec_stop_callback (vm_exec_stop_callback, &countdown, 16); + + const char *inf_loop_code_src_p = "while(true) {}"; + parsed_code_val = jerry_parse ((jerry_char_t *) inf_loop_code_src_p, strlen (inf_loop_code_src_p), false); + TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val)); + res = jerry_run (parsed_code_val); + TEST_ASSERT (countdown == 0); + + TEST_ASSERT (jerry_value_has_error_flag (res)); + + jerry_release_value (res); + jerry_release_value (parsed_code_val); + + /* A more complex example. Although the callback error is captured + * by the catch block, it is automatically thrown again. */ + + /* We keep the callback function, only the countdown is reset. */ + countdown = 6; + + inf_loop_code_src_p = ("function f() { while (true) ; }\n" + "try { f(); } catch(e) {}"); + + parsed_code_val = jerry_parse ((jerry_char_t *) inf_loop_code_src_p, strlen (inf_loop_code_src_p), false); + + TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val)); + res = jerry_run (parsed_code_val); + TEST_ASSERT (countdown == 0); + + /* The result must have an error flag which proves that + * the error is thrown again inside the catch block. */ + TEST_ASSERT (jerry_value_has_error_flag (res)); + + jerry_release_value (res); + jerry_release_value (parsed_code_val); + + jerry_cleanup (); + } + /* External Magic String */ jerry_init (JERRY_INIT_SHOW_OPCODES); diff --git a/tools/build.py b/tools/build.py index d6c1af63ce..e87d9bbade 100755 --- a/tools/build.py +++ b/tools/build.py @@ -108,6 +108,8 @@ def devhelp(helpstring): help='build unittests') parser.add_argument('-v', '--verbose', action='store_const', const='ON', default='OFF', help='increase verbosity') + parser.add_argument('--vm-exec-stop', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper, + help='enable VM execution stopping (%(choices)s; default: %(default)s)') devgroup = parser.add_argument_group('developer options') devgroup.add_argument('--link-map', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper, @@ -161,6 +163,7 @@ def generate_build_options(arguments): build_options.append('-DFEATURE_SYSTEM_ALLOCATOR=%s' % arguments.system_allocator) build_options.append('-DENABLE_STATIC_LINK=%s' % arguments.static_link) build_options.append('-DENABLE_STRIP=%s' % arguments.strip) + build_options.append('-DFEATURE_VM_EXEC_STOP=%s' % arguments.vm_exec_stop) if arguments.toolchain: build_options.append('-DCMAKE_TOOLCHAIN_FILE=%s' % arguments.toolchain) diff --git a/tools/run-tests.py b/tools/run-tests.py index fbbd313302..2ff6fe0c28 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -42,9 +42,9 @@ def get_binary_path(bin_dir_path): # Test options for unittests JERRY_UNITTESTS_OPTIONS = [ Options('unittests', - ['--unittests', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on']), + ['--unittests', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on']), Options('unittests-debug', - ['--unittests', '--debug', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on']) + ['--unittests', '--debug', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on']) ] # Test options for jerry-tests