From 3b77117a2e6f3b344bb977dceca91d1985f3fe90 Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Mon, 18 Jan 2021 16:56:38 +0100 Subject: [PATCH] Add a callback which is called when Error objects are created (#4465) JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 75 +++++++++++++++ jerry-core/api/jerry-snapshot.c | 4 +- jerry-core/api/jerry.c | 20 +++- jerry-core/ecma/base/ecma-globals.h | 1 + .../ecma-builtin-helpers-error.c | 5 +- jerry-core/ecma/operations/ecma-exceptions.c | 82 ++++++++-------- jerry-core/ecma/operations/ecma-exceptions.h | 3 +- jerry-core/include/jerryscript-core.h | 7 ++ jerry-core/jcontext/jcontext.h | 2 + jerry-core/lit/lit-magic-strings.inc.h | 3 + jerry-core/lit/lit-magic-strings.ini | 1 + jerry-core/vm/vm.c | 2 +- tests/unit-core/CMakeLists.txt | 1 + tests/unit-core/test-error-callback.c | 94 +++++++++++++++++++ 14 files changed, 249 insertions(+), 51 deletions(-) create mode 100644 tests/unit-core/test-error-callback.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index 65b20a7b5..7de3205cd 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -586,6 +586,33 @@ typedef void (*jerry_object_native_free_callback_t) (void *native_p); - [jerry_object_native_info_t](#jerry_object_native_info_t) - [jerry_create_arraybuffer_external](#jerry_create_arraybuffer_external) +## jerry_error_object_created_callback_t + +**Summary** + +Decorator callback for Error objects. This native callback is called every time when an Error +object is created and the decorator can create or update any properties of the newly created +Error object. + +*Note*: + - The callback function cannot be called recursively, so the Error objects created + when the callback is running are not updated. + +*New in version [[NEXT_RELEASE]]*. + +**Prototype** + +```c +typedef void (*jerry_error_object_created_callback_t) (const jerry_value_t error_object, void *user_p); +``` + +- `error_object` - the newly created Error object. +- `user_p` - pointer passed to [jerry_set_error_object_created_callback](#jerry_set_error_object_created_callback). + +**See also** + +- [jerry_set_error_object_created_callback](#jerry_set_error_object_created_callback) + ## jerry_object_native_info_t **Summary** @@ -2996,6 +3023,54 @@ jerry_get_value_from_error (jerry_value_t value, bool release) - [jerry_create_error_from_value](#jerry_create_error_from_value) - [jerry_create_abort_from_value](#jerry_create_abort_from_value) +## jerry_set_error_object_created_callback + +**Summary** + +Set the decorator callback for newly created Error objects. The operation of the callback +is described in [jerry_error_object_created_callback_t](#jerry_error_object_created_callback_t). + +**Prototype** + +```c +void jerry_set_error_object_created_callback (jerry_error_object_created_callback_t callback, void *user_p); +``` + +- `callback` - callback function, the previously set value is overwritten, and setting NULL + disables the operation +- `user_p` - pointer passed to the callback function, can be NULL + +*New in version [[NEXT_RELEASE]]*. + +**Example** + +```c +static void +error_object_created_callback (const jerry_value_t error_object) /**< new error object */ + void *user_p) /**< user pointer */ +{ + (void) error_object; + (void) user_p; + printf ("Notification: a new error is created\n"); +} /* error_object_created_callback */ + +void main(void) +{ + jerry_init (JERRY_INIT_EMPTY); + + jerry_set_error_object_created_callback (error_object_created_callback, NULL); + + jerry_release_value (jerry_create_error (JERRY_ERROR_COMMON, + (const jerry_char_t *) "Message")); + + jerry_cleanup (); +} /* main */ +``` + +**See also** + +- [jerry_error_object_created_callback_t](#jerry_error_object_created_callback_t) + # Getter functions of 'jerry_value_t' Get raw data from API values. diff --git a/jerry-core/api/jerry-snapshot.c b/jerry-core/api/jerry-snapshot.c index 83562582e..f5d1f8fc6 100644 --- a/jerry-core/api/jerry-snapshot.c +++ b/jerry-core/api/jerry-snapshot.c @@ -314,8 +314,8 @@ static_snapshot_error_unsupported_literal (snapshot_globals_t *globals_p, /**< s ecma_deref_ecma_string (literal_string_p); - ecma_object_t *error_object_p = ecma_new_standard_error_with_message (ECMA_ERROR_RANGE, - ecma_stringbuilder_finalize (&builder)); + ecma_object_t *error_object_p = ecma_new_standard_error (ECMA_ERROR_RANGE, + ecma_stringbuilder_finalize (&builder)); globals_p->snapshot_error = ecma_create_error_object_reference (error_object_p); } /* static_snapshot_error_unsupported_literal */ diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index e43f786ae..fef9a2ff3 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -1601,6 +1601,20 @@ jerry_get_value_from_error (jerry_value_t value, /**< api value */ return ret_val; } /* jerry_get_value_from_error */ +/** + * Set new decorator callback for Error objects. The decorator can + * create or update any properties of the newly created Error object. + */ +void +jerry_set_error_object_created_callback (jerry_error_object_created_callback_t callback, /**< new callback */ + void *user_p) /**< user pointer passed to the callback */ +{ + jerry_assert_api_available (); + + JERRY_CONTEXT (error_object_created_callback_p) = callback; + JERRY_CONTEXT (error_object_created_callback_user_p) = user_p; +} /* jerry_set_error_object_created_callback */ + /** * Return the type of the Error object if possible. * @@ -1990,15 +2004,15 @@ jerry_create_error_sz (jerry_error_t error_type, /**< type of error */ if (message_p == NULL || message_size == 0) { - return ecma_create_error_object_reference (ecma_new_standard_error ((ecma_standard_error_t) error_type)); + return ecma_create_error_object_reference (ecma_new_standard_error ((ecma_standard_error_t) error_type, NULL)); } else { ecma_string_t *message_string_p = ecma_new_ecma_string_from_utf8 ((lit_utf8_byte_t *) message_p, (lit_utf8_size_t) message_size); - ecma_object_t *error_object_p = ecma_new_standard_error_with_message ((ecma_standard_error_t) error_type, - message_string_p); + ecma_object_t *error_object_p = ecma_new_standard_error ((ecma_standard_error_t) error_type, + message_string_p); ecma_deref_ecma_string (message_string_p); diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index a15bdaa63..4ae65f4f7 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -72,6 +72,7 @@ typedef enum #endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */ 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 */ } ecma_status_flag_t; /** diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers-error.c b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers-error.c index dd5791471..a5e659185 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers-error.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers-error.c @@ -53,14 +53,13 @@ ecma_builtin_helper_error_dispatch_call (ecma_standard_error_t error_type, /**< return ECMA_VALUE_ERROR; } - ecma_object_t *new_error_object_p = ecma_new_standard_error_with_message (error_type, - message_string_p); + ecma_object_t *new_error_object_p = ecma_new_standard_error (error_type, message_string_p); ecma_deref_ecma_string (message_string_p); return ecma_make_object_value (new_error_object_p); } - ecma_object_t *new_error_object_p = ecma_new_standard_error (error_type); + ecma_object_t *new_error_object_p = ecma_new_standard_error (error_type, NULL); return ecma_make_object_value (new_error_object_p); } /* ecma_builtin_helper_error_dispatch_call */ diff --git a/jerry-core/ecma/operations/ecma-exceptions.c b/jerry-core/ecma/operations/ecma-exceptions.c index db3e2ca23..a33c175a9 100644 --- a/jerry-core/ecma/operations/ecma-exceptions.c +++ b/jerry-core/ecma/operations/ecma-exceptions.c @@ -69,6 +69,9 @@ const ecma_error_mapping_t ecma_error_mappings[] = * Standard ecma-error object constructor. * * Note: + * message_string_p can be NULL. + * + * Note: * calling with ECMA_ERROR_NONE does not make sense thus it will * cause a fault in the system. * @@ -76,7 +79,8 @@ const ecma_error_mapping_t ecma_error_mappings[] = * with reference counter set to one. */ ecma_object_t * -ecma_new_standard_error (ecma_standard_error_t error_type) /**< native error type */ +ecma_new_standard_error (ecma_standard_error_t error_type, /**< native error type */ + ecma_string_t *message_string_p) /**< message string */ { #if ENABLED (JERRY_BUILTIN_ERRORS) ecma_builtin_id_t prototype_id = ECMA_BUILTIN_ID__COUNT; @@ -140,23 +144,45 @@ ecma_new_standard_error (ecma_standard_error_t error_type) /**< native error typ ((ecma_extended_object_t *) new_error_obj_p)->u.class_prop.class_id = LIT_MAGIC_STRING_ERROR_UL; + if (message_string_p != NULL) + { + ecma_property_value_t *prop_value_p; + prop_value_p = ecma_create_named_data_property (new_error_obj_p, + ecma_get_magic_string (LIT_MAGIC_STRING_MESSAGE), + ECMA_PROPERTY_CONFIGURABLE_WRITABLE, + NULL); + + ecma_ref_ecma_string (message_string_p); + prop_value_p->value = ecma_make_string_value (message_string_p); + } + + /* Avoid calling the decorator function recursively. */ + if (JERRY_CONTEXT (error_object_created_callback_p) != NULL + && !(JERRY_CONTEXT (status_flags) & ECMA_STATUS_ERROR_UPDATE)) + { + JERRY_CONTEXT (status_flags) |= ECMA_STATUS_ERROR_UPDATE; + JERRY_CONTEXT (error_object_created_callback_p) (ecma_make_object_value (new_error_obj_p), + JERRY_CONTEXT (error_object_created_callback_user_p)); + JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_ERROR_UPDATE; + } + else + { #if ENABLED (JERRY_LINE_INFO) - /* The "stack" identifier is not a magic string. */ - const char * const stack_id_p = "stack"; + /* Default decorator when line info is enabled. */ + ecma_string_t *stack_str_p = ecma_get_magic_string (LIT_MAGIC_STRING_STACK); - ecma_string_t *stack_str_p = ecma_new_ecma_string_from_utf8 ((const lit_utf8_byte_t *) stack_id_p, 5); + ecma_property_value_t *prop_value_p = ecma_create_named_data_property (new_error_obj_p, + stack_str_p, + ECMA_PROPERTY_CONFIGURABLE_WRITABLE, + NULL); + ecma_deref_ecma_string (stack_str_p); - ecma_property_value_t *prop_value_p = ecma_create_named_data_property (new_error_obj_p, - stack_str_p, - ECMA_PROPERTY_CONFIGURABLE_WRITABLE, - NULL); - ecma_deref_ecma_string (stack_str_p); + ecma_value_t backtrace_value = vm_get_backtrace (0, NULL); - ecma_value_t backtrace_value = vm_get_backtrace (0, NULL); - - prop_value_p->value = backtrace_value; - ecma_deref_object (ecma_get_object_from_value (backtrace_value)); + prop_value_p->value = backtrace_value; + ecma_deref_object (ecma_get_object_from_value (backtrace_value)); #endif /* ENABLED (JERRY_LINE_INFO) */ + } return new_error_obj_p; } /* ecma_new_standard_error */ @@ -190,30 +216,6 @@ ecma_get_error_type (ecma_object_t *error_object) /**< possible error object */ return ECMA_ERROR_NONE; } /* ecma_get_error_type */ -/** - * Standard ecma-error object constructor. - * - * @return pointer to ecma-object representing specified error - * with reference counter set to one. - */ -ecma_object_t * -ecma_new_standard_error_with_message (ecma_standard_error_t error_type, /**< native error type */ - ecma_string_t *message_string_p) /**< message string */ -{ - ecma_object_t *new_error_obj_p = ecma_new_standard_error (error_type); - - ecma_property_value_t *prop_value_p; - prop_value_p = ecma_create_named_data_property (new_error_obj_p, - ecma_get_magic_string (LIT_MAGIC_STRING_MESSAGE), - ECMA_PROPERTY_CONFIGURABLE_WRITABLE, - NULL); - - ecma_ref_ecma_string (message_string_p); - prop_value_p->value = ecma_make_string_value (message_string_p); - - return new_error_obj_p; -} /* ecma_new_standard_error_with_message */ - /** * Raise a standard ecma-error with the given type and message. * @@ -230,12 +232,12 @@ ecma_raise_standard_error (ecma_standard_error_t error_type, /**< error type */ { ecma_string_t *error_msg_p = ecma_new_ecma_string_from_utf8 (msg_p, lit_zt_utf8_string_size (msg_p)); - error_obj_p = ecma_new_standard_error_with_message (error_type, error_msg_p); + error_obj_p = ecma_new_standard_error (error_type, error_msg_p); ecma_deref_ecma_string (error_msg_p); } else { - error_obj_p = ecma_new_standard_error (error_type); + error_obj_p = ecma_new_standard_error (error_type, NULL); } jcontext_raise_exception (ecma_make_object_value (error_obj_p)); @@ -320,7 +322,7 @@ ecma_raise_standard_error_with_format (ecma_standard_error_t error_type, /**< er ecma_string_t *builder_str_p = ecma_stringbuilder_finalize (&builder); - ecma_object_t *error_obj_p = ecma_new_standard_error_with_message (error_type, builder_str_p); + ecma_object_t *error_obj_p = ecma_new_standard_error (error_type, builder_str_p); ecma_deref_ecma_string (builder_str_p); diff --git a/jerry-core/ecma/operations/ecma-exceptions.h b/jerry-core/ecma/operations/ecma-exceptions.h index 0dc0122be..0d254449d 100644 --- a/jerry-core/ecma/operations/ecma-exceptions.h +++ b/jerry-core/ecma/operations/ecma-exceptions.h @@ -51,8 +51,7 @@ typedef enum } ecma_standard_error_t; ecma_standard_error_t ecma_get_error_type (ecma_object_t *error_object); -ecma_object_t *ecma_new_standard_error (ecma_standard_error_t error_type); -ecma_object_t *ecma_new_standard_error_with_message (ecma_standard_error_t error_type, ecma_string_t *message_string_p); +ecma_object_t *ecma_new_standard_error (ecma_standard_error_t error_type, ecma_string_t *message_string_p); #if ENABLED (JERRY_ERROR_MESSAGES) ecma_value_t ecma_raise_standard_error_with_format (ecma_standard_error_t error_type, const char *msg_p, ...); #endif /* ENABLED (JERRY_ERROR_MESSAGES) */ diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index b575afa57..b73193ccf 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -231,6 +231,12 @@ typedef jerry_value_t (*jerry_external_handler_t) (const jerry_value_t function_ */ typedef void (*jerry_object_native_free_callback_t) (void *native_p); +/** + * Decorator callback for Error objects. The decorator can create + * or update any properties of the newly created Error object. + */ +typedef void (*jerry_error_object_created_callback_t) (const jerry_value_t error_object, void *user_p); + /** * Callback which tells whether the ECMAScript execution should be stopped. * @@ -512,6 +518,7 @@ jerry_value_t jerry_binary_operation (jerry_binary_operation_t op, jerry_value_t jerry_create_abort_from_value (jerry_value_t value, bool release); jerry_value_t jerry_create_error_from_value (jerry_value_t value, bool release); jerry_value_t jerry_get_value_from_error (jerry_value_t value, bool release); +void jerry_set_error_object_created_callback (jerry_error_object_created_callback_t callback, void *user_p); /** * Error object function(s). diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index 940431a11..072fe6a60 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -156,6 +156,8 @@ struct jerry_context_t vm_frame_ctx_t *vm_top_context_p; /**< top (current) interpreter context */ jerry_context_data_header_t *context_data_p; /**< linked list of user-provided context-specific pointers */ + void *error_object_created_callback_user_p; /**< user pointer for error_object_update_callback_p */ + jerry_error_object_created_callback_t error_object_created_callback_p; /**< decorator callback for Error objects */ size_t ecma_gc_objects_number; /**< number of currently allocated objects */ size_t ecma_gc_new_objects; /**< number of newly allocated objects since last GC session */ size_t jmem_heap_allocated_size; /**< size of allocated regions */ diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index d0f0b23c4..0cf75ef35 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -300,6 +300,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_SLICE, "slice") || ENABLED (JERRY_ESNEXT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_SPLIT, "split") #endif +#if ENABLED (JERRY_LINE_INFO) +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_STACK, "stack") +#endif #if ENABLED (JERRY_ESNEXT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_THROW, "throw") #endif diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index 848433b1d..bec5d3d79 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -129,6 +129,7 @@ LIT_MAGIC_STRING_ROUND = "round" LIT_MAGIC_STRING_SHIFT = "shift" LIT_MAGIC_STRING_SLICE = "slice" LIT_MAGIC_STRING_SPLIT = "split" +LIT_MAGIC_STRING_STACK = "stack" LIT_MAGIC_STRING_THROW = "throw" LIT_MAGIC_STRING_TRUNC = "trunc" LIT_MAGIC_STRING_VALUE = "value" diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index d891f3662..f03aa98b6 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -1714,7 +1714,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ case VM_OC_THROW_SYNTAX_ERROR: { ecma_string_t *msg_p = ecma_get_string_from_value (left_value); - ecma_object_t *error_obj_p = ecma_new_standard_error_with_message (ECMA_ERROR_SYNTAX, msg_p); + ecma_object_t *error_obj_p = ecma_new_standard_error (ECMA_ERROR_SYNTAX, msg_p); jcontext_raise_exception (ecma_make_object_value (error_obj_p)); result = ECMA_VALUE_ERROR; goto error; diff --git a/tests/unit-core/CMakeLists.txt b/tests/unit-core/CMakeLists.txt index 6981df452..a57438672 100644 --- a/tests/unit-core/CMakeLists.txt +++ b/tests/unit-core/CMakeLists.txt @@ -69,6 +69,7 @@ set(SOURCE_UNIT_TEST_MAIN_MODULES test-proxy.c test-realm.c test-regexp-dotall-unicode.c + test-error-callback.c test-regexp.c test-regression-3588.c test-resource-name.c diff --git a/tests/unit-core/test-error-callback.c b/tests/unit-core/test-error-callback.c new file mode 100644 index 000000000..00b036e80 --- /dev/null +++ b/tests/unit-core/test-error-callback.c @@ -0,0 +1,94 @@ +/* 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 bool error_object_created_callback_is_running = false; +static int error_object_created_callback_count = 0; + +static void +error_object_created_callback (const jerry_value_t error_object_t, /**< new error object */ + void *user_p) /**< user pointer */ +{ + TEST_ASSERT (!error_object_created_callback_is_running); + TEST_ASSERT (user_p == (void *) &error_object_created_callback_count); + + error_object_created_callback_is_running = true; + error_object_created_callback_count++; + + jerry_value_t name = jerry_create_string ((const jerry_char_t *) "message"); + jerry_value_t message = jerry_create_string ((const jerry_char_t *) "Replaced message!"); + + jerry_value_t result = jerry_set_property (error_object_t, name, message); + TEST_ASSERT (jerry_value_is_boolean (result) && jerry_get_boolean_value (result) == true); + jerry_release_value (result); + + /* This SyntaxError must not trigger a recusrsive call of the this callback. */ + const char *source_p = "Syntax Error in JS!"; + result = jerry_eval ((const jerry_char_t *) source_p, strlen (source_p), 0); + TEST_ASSERT (jerry_value_is_error (result)); + + jerry_release_value (result); + jerry_release_value (message); + jerry_release_value (name); + + error_object_created_callback_is_running = false; +} /* error_object_created_callback */ + +static void +run_test (const char *source_p) +{ + /* Run the code 5 times. */ + for (int i = 0; i < 5; i++) + { + jerry_value_t result = jerry_eval ((const jerry_char_t *) source_p, strlen (source_p), 0); + TEST_ASSERT (jerry_value_is_boolean (result) && jerry_get_boolean_value (result) == true); + jerry_release_value (result); + } +} /* run_test */ + +/** + * Unit test's main function. + */ +int +main (void) +{ + TEST_INIT (); + + jerry_init (JERRY_INIT_EMPTY); + + jerry_set_error_object_created_callback (error_object_created_callback, + (void *) &error_object_created_callback_count); + + run_test ("var result = false\n" + "try {\n" + " ref_error;\n" + "} catch(e) {\n" + " result = (e.message === 'Replaced message!')\n" + "}\n" + "result\n"); + + run_test ("var error = new Error()\n" + "error.message === 'Replaced message!'\n"); + + jerry_release_value (jerry_create_error (JERRY_ERROR_COMMON, (const jerry_char_t *) "Message")); + + TEST_ASSERT (error_object_created_callback_count == 11); + + jerry_cleanup (); + return 0; +} /* main */