From 3193e6d0dcd5459319bc3d50b7529ec17345a4b3 Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Tue, 12 Jan 2021 19:52:05 +0100 Subject: [PATCH] Implement jerry_get_backtrace_from API function (#4454) JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 138 +++++++++++++++++++ jerry-core/api/jerry.c | 45 +++++- jerry-core/ecma/operations/ecma-exceptions.c | 2 +- jerry-core/include/jerryscript-core.h | 1 + jerry-core/vm/vm-utils.c | 35 ++++- jerry-core/vm/vm.h | 2 +- tests/unit-core/test-backtrace.c | 96 ++++++++++++- 7 files changed, 313 insertions(+), 6 deletions(-) diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index 7c7140949..946f98a75 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -8876,6 +8876,7 @@ backtrace_handler (const jerry_value_t function_obj, if (!jerry_is_feature_enabled (JERRY_FEATURE_LINE_INFO)) { printf ("Line info disabled, no backtrace will be printed\n"); + return jerry_create_undefined (); } /* If the line info feature is disabled an empty array will be returned. */ @@ -8948,9 +8949,146 @@ main (void) **See also** +- [jerry_get_backtrace_from](#jerry_get_backtrace_from) - [jerry_create_external_function](#jerry_create_external_function) +## jerry_get_backtrace_from + +**Summary** + +Get backtrace. The backtrace is an array of strings where +each string contains the position of the corresponding frame. +The array length is zero if the backtrace is not available. + +Collecting the trace starts after the function specified in +the `ignored_function` parameter. This parameter can be used to +skip the helper function(s) which collects the backtrace from +the backtrace data. + +*Notes*: +- Returned value must be freed with [jerry_release_value](#jerry_release_value) when it +is no longer needed. +- This feature depends on build option (`JERRY_LINE_INFO`) and can be checked + in runtime with the `JERRY_FEATURE_LINE_INFO` feature enum value, + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). + +**Prototype** + +```c +jerry_value_t +jerry_get_backtrace_from (uint32_t max_depth, jerry_value_t ignored_function); +``` + +- `max_depth` - backtrace collection stops after reaching this value, 0 = unlimited +- `ignored_function` - if this function is present in the backtrace, the backtrace + only contains the stack frames after the topmost instance of + this function. Otherwise this parameter is ignored +- return value + - a newly constructed JS array + +*New in version [[NEXT_RELEASE]]*. + +**Example** + +[doctest]: # (name="02.API-REFERENCE-jsbacktracefrom.c") + +```c +#include +#include +#include "jerryscript.h" + +static jerry_value_t +backtrace_handler (const jerry_value_t function_obj, + const jerry_value_t this_val, + const jerry_value_t args_p[], + const jerry_length_t args_count) +{ + if (!jerry_is_feature_enabled (JERRY_FEATURE_LINE_INFO)) + { + printf ("Line info disabled, no backtrace will be printed\n"); + return jerry_create_undefined (); + } + + if (args_count < 1) + { + printf ("Ignored function is not specified\n"); + return jerry_create_undefined (); + } + + /* If the line info feature is disabled an empty array will be returned. */ + jerry_value_t backtrace_array = jerry_get_backtrace_from (0, args_p[0]); + uint32_t array_length = jerry_get_array_length (backtrace_array); + + for (uint32_t idx = 0; idx < array_length; idx++) + { + jerry_value_t property = jerry_get_property_by_index (backtrace_array, idx); + + jerry_char_t string_buffer[64]; + jerry_size_t copied_bytes = jerry_substring_to_char_buffer (property, + 0, + 63, + string_buffer, + 63); + string_buffer[copied_bytes] = '\0'; + printf(" %d: %s\n", idx, string_buffer); + + jerry_release_value (property); + } + + jerry_release_value (backtrace_array); + + return jerry_create_undefined (); +} /* backtrace_handler */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + jerry_value_t global = jerry_get_global_object (); + + /* Register the "dump_backtrace" method. */ + { + jerry_value_t func = jerry_create_external_function (backtrace_handler); + jerry_value_t name = jerry_create_string ((const jerry_char_t *) "backtrace"); + jerry_value_t result = jerry_set_property (global, name, func); + jerry_release_value (result); + jerry_release_value (name); + jerry_release_value (func); + } + + jerry_release_value (global); + + const char *source = ("function f() { g (); }\n" + "function g() { h (); }\n" + "function h() { backtrace (g); }\n" + "f ();\n"); + const char *resource = "demo_memoryjs"; + + jerry_value_t program = jerry_parse ((const jerry_char_t *) resource, + strlen (resource), + (const jerry_char_t *) source, + strlen (source), + JERRY_PARSE_NO_OPTS); + if (!jerry_value_is_error (program)) + { + jerry_value_t run_result = jerry_run (program); + jerry_release_value (run_result); + } + + jerry_release_value (program); + jerry_cleanup (); + + return 0; +} +``` + +**See also** + +- [jerry_get_backtrace](#jerry_get_backtrace) + + ## jerry_get_resource_name **Summary** diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 9a40e0817..5a7a8edc2 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -4523,9 +4523,52 @@ jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, /**< per jerry_value_t jerry_get_backtrace (uint32_t max_depth) /**< depth limit of the backtrace */ { - return vm_get_backtrace (max_depth); + return vm_get_backtrace (max_depth, NULL); } /* jerry_get_backtrace */ +/** + * Get backtrace. The backtrace is an array of strings where + * each string contains the position of the corresponding frame. + * The array length is zero if the backtrace is not available. + * + * @return array value + */ +jerry_value_t +jerry_get_backtrace_from (uint32_t max_depth, /**< depth limit of the backtrace */ + jerry_value_t ignored_function) /**< collect backtrace after this function */ +{ + ecma_object_t *ignored_function_p = NULL; + + if (ecma_is_value_object (ignored_function)) + { + ignored_function_p = ecma_get_object_from_value (ignored_function); + + while (true) + { + ecma_object_type_t type = ecma_get_object_type (ignored_function_p); + + if (type == ECMA_OBJECT_TYPE_FUNCTION || type == ECMA_OBJECT_TYPE_NATIVE_FUNCTION) + { + break; + } + + if (type == ECMA_OBJECT_TYPE_BOUND_FUNCTION) + { + ecma_bound_function_t *bound_func_p = (ecma_bound_function_t *) ignored_function_p; + jmem_cpointer_tag_t target_function = bound_func_p->header.u.bound_function.target_function; + + ignored_function_p = ECMA_GET_NON_NULL_POINTER_FROM_POINTER_TAG (ecma_object_t, target_function); + continue; + } + + ignored_function_p = NULL; + break; + } + } + + return vm_get_backtrace (max_depth, ignored_function_p); +} /* jerry_get_backtrace_from */ + /** * Get the resource name (usually a file name) of the currently executed script or the given function object * diff --git a/jerry-core/ecma/operations/ecma-exceptions.c b/jerry-core/ecma/operations/ecma-exceptions.c index bc0880c90..1cc2afa61 100644 --- a/jerry-core/ecma/operations/ecma-exceptions.c +++ b/jerry-core/ecma/operations/ecma-exceptions.c @@ -152,7 +152,7 @@ ecma_new_standard_error (ecma_standard_error_t error_type) /**< native error typ NULL); ecma_deref_ecma_string (stack_str_p); - ecma_value_t backtrace_value = vm_get_backtrace (0); + 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)); diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 8f44bf3a5..5a5097def 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -750,6 +750,7 @@ jerry_context_t *jerry_create_context (uint32_t heap_size, jerry_context_alloc_t */ void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, void *user_p, uint32_t frequency); jerry_value_t jerry_get_backtrace (uint32_t max_depth); +jerry_value_t jerry_get_backtrace_from (uint32_t max_depth, jerry_value_t ignored_function); jerry_value_t jerry_get_resource_name (const jerry_value_t value); jerry_value_t jerry_get_new_target (void); diff --git a/jerry-core/vm/vm-utils.c b/jerry-core/vm/vm-utils.c index 31a407910..0264ba26d 100644 --- a/jerry-core/vm/vm-utils.c +++ b/jerry-core/vm/vm-utils.c @@ -59,15 +59,45 @@ vm_is_direct_eval_form_call (void) * @return array ecma value */ ecma_value_t -vm_get_backtrace (uint32_t max_depth) /**< maximum backtrace depth, 0 = unlimited */ +vm_get_backtrace (uint32_t max_depth, /**< maximum backtrace depth, 0 = unlimited */ + ecma_object_t *ignored_function_p) /**< ignore functions up to this function */ { #if ENABLED (JERRY_LINE_INFO) + vm_frame_ctx_t *context_p = JERRY_CONTEXT (vm_top_context_p); + + if (ignored_function_p != NULL) + { + JERRY_ASSERT (ecma_get_object_type (ignored_function_p) == ECMA_OBJECT_TYPE_FUNCTION + || ecma_get_object_type (ignored_function_p) == ECMA_OBJECT_TYPE_NATIVE_FUNCTION); + + while (true) + { + if (context_p == NULL) + { + context_p = JERRY_CONTEXT (vm_top_context_p); + break; + } + + if (context_p->shared_p->status_flags & VM_FRAME_CTX_SHARED_HAS_ARG_LIST) + { + vm_frame_ctx_shared_args_t *shared_args_p = (vm_frame_ctx_shared_args_t *) context_p->shared_p; + + if (shared_args_p->function_object_p == ignored_function_p) + { + context_p = context_p->prev_context_p; + break; + } + } + + context_p = context_p->prev_context_p; + } + } + if (max_depth == 0) { max_depth = UINT32_MAX; } - vm_frame_ctx_t *context_p = JERRY_CONTEXT (vm_top_context_p); ecma_object_t *array_p = ecma_op_new_array_object (0); JERRY_ASSERT (ecma_op_object_is_fast_array (array_p)); uint32_t index = 0; @@ -108,6 +138,7 @@ vm_get_backtrace (uint32_t max_depth) /**< maximum backtrace depth, 0 = unlimite return ecma_make_object_value (array_p); #else /* !ENABLED (JERRY_LINE_INFO) */ JERRY_UNUSED (max_depth); + JERRY_UNUSED (ignored_function_p); return ecma_make_object_value (ecma_op_new_array_object (0)); #endif /* ENABLED (JERRY_LINE_INFO) */ diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 4def5c07e..c2287180f 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -490,7 +490,7 @@ ecma_value_t vm_execute (vm_frame_ctx_t *frame_ctx_p); bool vm_is_strict_mode (void); bool vm_is_direct_eval_form_call (void); -ecma_value_t vm_get_backtrace (uint32_t max_depth); +ecma_value_t vm_get_backtrace (uint32_t max_depth, ecma_object_t *ignored_function_p); /** * @} diff --git a/tests/unit-core/test-backtrace.c b/tests/unit-core/test-backtrace.c index 663d1be71..7f2b7656c 100644 --- a/tests/unit-core/test-backtrace.c +++ b/tests/unit-core/test-backtrace.c @@ -28,11 +28,16 @@ backtrace_handler (const jerry_value_t function_obj, /**< function object */ uint32_t max_depth = 0; - if (args_count > 0 && jerry_value_is_number (args_p[0])) + if (args_count >= 1 && jerry_value_is_number (args_p[0])) { max_depth = (uint32_t) jerry_get_number_value (args_p[0]); } + if (args_count >= 2) + { + return jerry_get_backtrace_from (max_depth, args_p[1]); + } + return jerry_get_backtrace (max_depth); } /* backtrace_handler */ @@ -153,6 +158,95 @@ test_get_backtrace_api_call (void) jerry_release_value (backtrace); + /* Ignore f and g this time. */ + + source = ("function f() {\n" + " return backtrace(0, g);\n" + "}\n" + "\n" + "function g() {\n" + " return f();\n" + "}\n" + "\n" + "function h() {\n" + " return g();\n" + "}\n" + "\n" + "h();\n"); + + backtrace = run ("something_ignore.js", source); + + TEST_ASSERT (!jerry_value_is_error (backtrace) + && jerry_value_is_array (backtrace)); + + TEST_ASSERT (jerry_get_array_length (backtrace) == 2); + + compare (backtrace, 0, "something_ignore.js:10"); + compare (backtrace, 1, "something_ignore.js:13"); + + jerry_release_value (backtrace); + + /* Use bound function this time. */ + + source = ("function f() {\n" + " return backtrace(0, i);\n" + "}\n" + "\n" + "function g(u, v) {\n" + " return v();\n" + "}\n" + "\n" + "var h = g.bind(null, 0)\n" + "var i = h.bind(null, f)\n" + "\n" + "function j() {\n" + " return i();\n" + "}\n" + "\n" + "j();\n"); + + backtrace = run ("something_bound.js", source); + + TEST_ASSERT (!jerry_value_is_error (backtrace) + && jerry_value_is_array (backtrace)); + + TEST_ASSERT (jerry_get_array_length (backtrace) == 2); + + compare (backtrace, 0, "something_bound.js:13"); + compare (backtrace, 1, "something_bound.js:16"); + + jerry_release_value (backtrace); + + /* Use invalid function this time. */ + + source = ("function f() {\n" + " return backtrace(0, ':)');\n" + "}\n" + "\n" + "function g() {\n" + " return f();\n" + "}\n" + "\n" + "function h() {\n" + " return g();\n" + "}\n" + "\n" + "h();\n"); + + backtrace = run ("nothing_ignore.js", source); + + TEST_ASSERT (!jerry_value_is_error (backtrace) + && jerry_value_is_array (backtrace)); + + TEST_ASSERT (jerry_get_array_length (backtrace) == 4); + + compare (backtrace, 0, "nothing_ignore.js:2"); + compare (backtrace, 1, "nothing_ignore.js:6"); + compare (backtrace, 2, "nothing_ignore.js:10"); + compare (backtrace, 3, "nothing_ignore.js:13"); + + jerry_release_value (backtrace); + jerry_cleanup (); } /* test_get_backtrace_api_call */