From 34c5e229ee35bd2002f2a33300086665c13e9cbe Mon Sep 17 00:00:00 2001 From: Robert Sipka Date: Mon, 29 Oct 2018 14:33:31 +0100 Subject: List scope chain levels and their variables to the selected stack frame. (#2557) It supports to list the scope chain of the current execution context and see which variables are available. JerryScript-DCO-1.0-Signed-off-by: Robert Sipka rsipka.uszeged@partner.samsung.com --- jerry-core/debugger/debugger.c | 351 +++++++++++++++++++++++++++++- jerry-core/debugger/debugger.h | 54 ++++- jerry-core/ecma/base/ecma-globals.h | 9 +- jerry-core/include/jerryscript-debugger.h | 2 +- jerry-core/vm/vm.c | 3 + jerry-debugger/jerry_client.py | 10 + jerry-debugger/jerry_client_ws.py | 151 ++++++++++++- tests/debugger/do_help.expected | 8 +- tests/debugger/do_scopes.cmd | 19 ++ tests/debugger/do_scopes.expected | 63 ++++++ tests/debugger/do_scopes.js | 41 ++++ tests/debugger/do_variables.cmd | 28 +++ tests/debugger/do_variables.expected | 128 +++++++++++ tests/debugger/do_variables.js | 60 +++++ 14 files changed, 914 insertions(+), 13 deletions(-) create mode 100644 tests/debugger/do_scopes.cmd create mode 100644 tests/debugger/do_scopes.expected create mode 100644 tests/debugger/do_scopes.js create mode 100644 tests/debugger/do_variables.cmd create mode 100644 tests/debugger/do_variables.expected create mode 100644 tests/debugger/do_variables.js diff --git a/jerry-core/debugger/debugger.c b/jerry-core/debugger/debugger.c index 3f9ed5b4..0a0676b5 100644 --- a/jerry-core/debugger/debugger.c +++ b/jerry-core/debugger/debugger.c @@ -18,6 +18,7 @@ #include "ecma-builtin-helpers.h" #include "ecma-conversion.h" #include "ecma-eval.h" +#include "ecma-function-object.h" #include "ecma-objects.h" #include "jcontext.h" #include "jerryscript-port.h" @@ -37,9 +38,9 @@ typedef struct * The number of message types in the debugger should reflect the * debugger versioning. */ -JERRY_STATIC_ASSERT (JERRY_DEBUGGER_MESSAGES_OUT_MAX_COUNT == 28 - && JERRY_DEBUGGER_MESSAGES_IN_MAX_COUNT == 19 - && JERRY_DEBUGGER_VERSION == 6, +JERRY_STATIC_ASSERT (JERRY_DEBUGGER_MESSAGES_OUT_MAX_COUNT == 32 + && JERRY_DEBUGGER_MESSAGES_IN_MAX_COUNT == 21 + && JERRY_DEBUGGER_VERSION == 7, debugger_version_correlates_to_message_type_count); /** @@ -195,6 +196,332 @@ jerry_debugger_send_backtrace (const uint8_t *recv_buffer_p) /**< pointer to the jerry_debugger_send (sizeof (jerry_debugger_send_type_t) + message_size); } /* jerry_debugger_send_backtrace */ +/** + * Send the scope chain types. + */ +static void +jerry_debugger_send_scope_chain (void) +{ + vm_frame_ctx_t *iter_frame_ctx_p = JERRY_CONTEXT (vm_top_context_p); + + const size_t max_byte_count = JERRY_DEBUGGER_SEND_MAX (uint8_t); + const size_t max_message_size = JERRY_DEBUGGER_SEND_SIZE (max_byte_count, uint8_t); + + JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_string_t, message_type_p); + message_type_p->type = JERRY_DEBUGGER_SCOPE_CHAIN; + + size_t buffer_pos = 0; + bool next_func_is_local = true; + ecma_object_t *lex_env_p = iter_frame_ctx_p->lex_env_p; + + while (true) + { + JERRY_ASSERT (ecma_is_lexical_environment (lex_env_p)); + + if (buffer_pos == max_byte_count) + { + if (!jerry_debugger_send (max_message_size)) + { + return; + } + + buffer_pos = 0; + } + + if (ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_DECLARATIVE) + { + if ((lex_env_p->type_flags_refs & ECMA_OBJECT_FLAG_NON_CLOSURE) != 0) + { + message_type_p->string[buffer_pos++] = JERRY_DEBUGGER_SCOPE_NON_CLOSURE; + } + else if (next_func_is_local) + { + message_type_p->string[buffer_pos++] = JERRY_DEBUGGER_SCOPE_LOCAL; + next_func_is_local = false; + } + else + { + message_type_p->string[buffer_pos++] = JERRY_DEBUGGER_SCOPE_CLOSURE; + } + } + else if (ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_THIS_OBJECT_BOUND) + { + if (ecma_get_lex_env_outer_reference (lex_env_p) == NULL) + { + message_type_p->string[buffer_pos++] = JERRY_DEBUGGER_SCOPE_GLOBAL; + break; + } + else + { + message_type_p->string[buffer_pos++] = JERRY_DEBUGGER_SCOPE_WITH; + } + } + + lex_env_p = ecma_get_lex_env_outer_reference (lex_env_p); + } + + message_type_p->type = JERRY_DEBUGGER_SCOPE_CHAIN_END; + + jerry_debugger_send (sizeof (jerry_debugger_send_type_t) + buffer_pos); +} /* jerry_debugger_send_scope_chain */ + +/** + * Get type of the scope variable property. + */ +static jerry_debugger_scope_variable_type_t +jerry_debugger_get_variable_type (ecma_value_t value) /**< input ecma value */ +{ + jerry_debugger_scope_variable_type_t ret_value = JERRY_DEBUGGER_VALUE_NONE; + + if (ecma_is_value_undefined (value)) + { + ret_value = JERRY_DEBUGGER_VALUE_UNDEFINED; + } + else if (ecma_is_value_null (value)) + { + ret_value = JERRY_DEBUGGER_VALUE_NULL; + } + else if (ecma_is_value_boolean (value)) + { + ret_value = JERRY_DEBUGGER_VALUE_BOOLEAN; + } + else if (ecma_is_value_number (value)) + { + ret_value = JERRY_DEBUGGER_VALUE_NUMBER; + } + else if (ecma_is_value_string (value)) + { + ret_value = JERRY_DEBUGGER_VALUE_STRING; + } + else + { + JERRY_ASSERT (ecma_is_value_object (value)); + + if (ecma_object_get_class_name (ecma_get_object_from_value (value)) == LIT_MAGIC_STRING_ARRAY_UL) + { + ret_value = JERRY_DEBUGGER_VALUE_ARRAY; + } + else + { + ret_value = ecma_op_is_callable (value) ? JERRY_DEBUGGER_VALUE_FUNCTION : JERRY_DEBUGGER_VALUE_OBJECT; + } + } + + JERRY_ASSERT (ret_value != JERRY_DEBUGGER_VALUE_NONE); + + return ret_value; +} /* jerry_debugger_get_variable_type */ + +/** + * Helper function for jerry_debugger_send_scope_variables. + * + * It will copies the given scope values type, length and value into the outgoing message string. + * + * @return true - if the copy was successfully + * false - otherwise + */ +static bool +jerry_debugger_copy_variables_to_string_message (jerry_debugger_scope_variable_type_t variable_type, /**< type */ + ecma_string_t *value_str, /**< property name or value string */ + jerry_debugger_send_string_t *message_string_p, /**< msg pointer */ + size_t *buffer_pos) /**< string data position of the message */ +{ + const size_t max_byte_count = JERRY_DEBUGGER_SEND_MAX (uint8_t); + const size_t max_message_size = JERRY_DEBUGGER_SEND_SIZE (max_byte_count, uint8_t); + + ECMA_STRING_TO_UTF8_STRING (value_str, str_buff, str_buff_size); + + size_t str_size = 0; + size_t str_limit = 255; + bool result = true; + + bool type_processed = false; + + while (true) + { + if (*buffer_pos == max_byte_count) + { + if (!jerry_debugger_send (max_message_size)) + { + result = false; + break; + } + + *buffer_pos = 0; + } + + if (!type_processed) + { + if (variable_type != JERRY_DEBUGGER_VALUE_NONE) + { + message_string_p->string[*buffer_pos] = variable_type; + *buffer_pos += 1; + } + type_processed = true; + continue; + } + + if (variable_type == JERRY_DEBUGGER_VALUE_FUNCTION) + { + str_size = 0; // do not copy function values + } + else + { + str_size = (str_buff_size > str_limit) ? str_limit : str_buff_size; + } + + message_string_p->string[*buffer_pos] = (uint8_t) str_size; + *buffer_pos += 1; + break; + } + + if (result) + { + size_t free_bytes = max_byte_count - *buffer_pos; + const uint8_t *string_p = str_buff; + + while (str_size > free_bytes) + { + memcpy (message_string_p->string + *buffer_pos, string_p, free_bytes); + + if (!jerry_debugger_send (max_message_size)) + { + result = false; + break; + } + + string_p += free_bytes; + str_size -= free_bytes; + free_bytes = max_byte_count; + *buffer_pos = 0; + } + + if (result) + { + memcpy (message_string_p->string + *buffer_pos, string_p, str_size); + *buffer_pos += str_size; + } + } + + ECMA_FINALIZE_UTF8_STRING (str_buff, str_buff_size); + + return result; +} /* jerry_debugger_copy_variables_to_string_message */ + +/** + * Send variables of the given scope chain level. + */ +static void +jerry_debugger_send_scope_variables (const uint8_t *recv_buffer_p) /**< pointer to the received data */ +{ + JERRY_DEBUGGER_RECEIVE_BUFFER_AS (jerry_debugger_receive_get_scope_variables_t, get_scope_variables_p); + + uint32_t chain_index; + memcpy (&chain_index, get_scope_variables_p->chain_index, sizeof (uint32_t)); + + vm_frame_ctx_t *iter_frame_ctx_p = JERRY_CONTEXT (vm_top_context_p); + ecma_object_t *lex_env_p = iter_frame_ctx_p->lex_env_p; + + while (chain_index != 0) + { + lex_env_p = ecma_get_lex_env_outer_reference (lex_env_p); + + if (JERRY_UNLIKELY (lex_env_p == NULL)) + { + jerry_debugger_send_type (JERRY_DEBUGGER_SCOPE_VARIABLES_END); + return; + } + + if ((ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_THIS_OBJECT_BOUND) + || (ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_DECLARATIVE)) + { + chain_index--; + } + } + + ecma_property_header_t *prop_iter_p; + + if (ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_DECLARATIVE) + { + prop_iter_p = ecma_get_property_list (lex_env_p); + } + else + { + JERRY_ASSERT (ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_THIS_OBJECT_BOUND); + ecma_object_t *binding_obj_p = ecma_get_lex_env_binding_object (lex_env_p); + prop_iter_p = ecma_get_property_list (binding_obj_p); + } + + JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_string_t, message_string_p); + message_string_p->type = JERRY_DEBUGGER_SCOPE_VARIABLES; + + size_t buffer_pos = 0; + + while (prop_iter_p != NULL) + { + JERRY_ASSERT (ECMA_PROPERTY_IS_PROPERTY_PAIR (prop_iter_p)); + + ecma_property_pair_t *prop_pair_p = (ecma_property_pair_t *) prop_iter_p; + + for (int i = 0; i < ECMA_PROPERTY_PAIR_ITEM_COUNT; i++) + { + if (ECMA_PROPERTY_IS_NAMED_PROPERTY (prop_iter_p->types[i])) + { + if (ECMA_PROPERTY_GET_NAME_TYPE (prop_iter_p->types[i]) == ECMA_DIRECT_STRING_MAGIC + && prop_pair_p->names_cp[i] >= LIT_NON_INTERNAL_MAGIC_STRING__COUNT) + { + continue; + } + + ecma_string_t *prop_name = ecma_string_from_property_name (prop_iter_p->types[i], + prop_pair_p->names_cp[i]); + + if (!jerry_debugger_copy_variables_to_string_message (JERRY_DEBUGGER_VALUE_NONE, + prop_name, + message_string_p, + &buffer_pos)) + { + ecma_deref_ecma_string (prop_name); + return; + } + + ecma_deref_ecma_string (prop_name); + + ecma_property_value_t prop_value_p = prop_pair_p->values[i]; + ecma_value_t property_value; + + jerry_debugger_scope_variable_type_t variable_type = jerry_debugger_get_variable_type (prop_value_p.value); + + if (variable_type == JERRY_DEBUGGER_VALUE_OBJECT) + { + property_value = ecma_builtin_json_string_from_object (prop_value_p.value); + } + else + { + property_value = ecma_op_to_string (prop_value_p.value); + } + + if (!jerry_debugger_copy_variables_to_string_message (variable_type, + ecma_get_string_from_value (property_value), + message_string_p, + &buffer_pos)) + { + ecma_free_value (property_value); + return; + } + + ecma_free_value (property_value); + } + } + + prop_iter_p = ECMA_GET_POINTER (ecma_property_header_t, + prop_iter_p->next_property_cp); + } + + message_string_p->type = JERRY_DEBUGGER_SCOPE_VARIABLES_END; + jerry_debugger_send (sizeof (jerry_debugger_send_type_t) + buffer_pos); +} /* jerry_debugger_send_scope_variables */ + /** * Send result of evaluated expression or throw an error. * @@ -525,6 +852,24 @@ jerry_debugger_process_message (const uint8_t *recv_buffer_p, /**< pointer to th return true; } + case JERRY_DEBUGGER_GET_SCOPE_CHAIN: + { + JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_type_t); + + jerry_debugger_send_scope_chain (); + + return true; + } + + case JERRY_DEBUGGER_GET_SCOPE_VARIABLES: + { + JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_get_scope_variables_t); + + jerry_debugger_send_scope_variables (recv_buffer_p); + + return true; + } + case JERRY_DEBUGGER_EXCEPTION_CONFIG: { JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_exception_config_t); diff --git a/jerry-core/debugger/debugger.h b/jerry-core/debugger/debugger.h index eb1bb287..731314df 100644 --- a/jerry-core/debugger/debugger.h +++ b/jerry-core/debugger/debugger.h @@ -152,7 +152,10 @@ typedef enum JERRY_DEBUGGER_WAIT_FOR_SOURCE = 25, /**< engine waiting for source code */ JERRY_DEBUGGER_OUTPUT_RESULT = 26, /**< output sent by the program to the debugger */ JERRY_DEBUGGER_OUTPUT_RESULT_END = 27, /**< last output result data */ - + JERRY_DEBUGGER_SCOPE_CHAIN = 28, /**< scope chain */ + JERRY_DEBUGGER_SCOPE_CHAIN_END = 29, /**< last output of scope chain */ + JERRY_DEBUGGER_SCOPE_VARIABLES = 30, /**< scope variables */ + JERRY_DEBUGGER_SCOPE_VARIABLES_END = 31, /**< last output of scope variables */ JERRY_DEBUGGER_MESSAGES_OUT_MAX_COUNT, /**< number of different type of output messages by the debugger */ /* Messages sent by the client to server. */ @@ -182,7 +185,8 @@ typedef enum JERRY_DEBUGGER_GET_BACKTRACE = 16, /**< get backtrace */ JERRY_DEBUGGER_EVAL = 17, /**< first message of evaluating a string */ JERRY_DEBUGGER_EVAL_PART = 18, /**< next message of evaluating a string */ - + JERRY_DEBUGGER_GET_SCOPE_CHAIN = 19, /**< get type names of the scope chain */ + JERRY_DEBUGGER_GET_SCOPE_VARIABLES = 20, /**< get variables of a scope */ JERRY_DEBUGGER_MESSAGES_IN_MAX_COUNT, /**< number of different type of input messages */ } jerry_debugger_header_type_t; @@ -229,6 +233,34 @@ typedef enum JERRY_DEBUGGER_OUTPUT_TRACE = 5, /**< output result, trace */ } jerry_debugger_output_subtype_t; +/** + * Types of scopes. + */ +typedef enum +{ + JERRY_DEBUGGER_SCOPE_WITH = 1, /**< with */ + JERRY_DEBUGGER_SCOPE_LOCAL = 2, /**< local */ + JERRY_DEBUGGER_SCOPE_CLOSURE = 3, /**< closure */ + JERRY_DEBUGGER_SCOPE_GLOBAL = 4, /**< global */ + JERRY_DEBUGGER_SCOPE_NON_CLOSURE = 5 /**< non closure */ +} jerry_debugger_scope_chain_type_t; + +/** + * Type of scope variables. + */ +typedef enum +{ + JERRY_DEBUGGER_VALUE_NONE = 1, + JERRY_DEBUGGER_VALUE_UNDEFINED = 2, + JERRY_DEBUGGER_VALUE_NULL = 3, + JERRY_DEBUGGER_VALUE_BOOLEAN = 4, + JERRY_DEBUGGER_VALUE_NUMBER = 5, + JERRY_DEBUGGER_VALUE_STRING = 6, + JERRY_DEBUGGER_VALUE_FUNCTION = 7, + JERRY_DEBUGGER_VALUE_ARRAY = 8, + JERRY_DEBUGGER_VALUE_OBJECT = 9 +} jerry_debugger_scope_variable_type_t; + /** * Byte data for evaluating expressions and receiving client source. */ @@ -364,6 +396,15 @@ typedef struct jerry_debugger_frame_t frames[]; /**< frames */ } jerry_debugger_send_backtrace_t; +/** + * Outgoing message: scope chain. + */ +typedef struct +{ + uint8_t type; /**< type of the message */ + uint8_t scope_types[]; /**< scope types */ +} jerry_debugger_send_scope_chain_t; + /** * Outgoing message: number of total frames in backtrace. */ @@ -411,6 +452,15 @@ typedef struct uint8_t eval_size[sizeof (uint32_t)]; /**< total size of the message */ } jerry_debugger_receive_eval_first_t; +/** + * Incoming message: get scope variables +*/ +typedef struct +{ + uint8_t type; /**< type of the message */ + uint8_t chain_index[sizeof (uint32_t)]; /**< index element of the scope */ +} jerry_debugger_receive_get_scope_variables_t; + /** * Incoming message: first message of client source. */ diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index bd78b6cb..7d6aaed2 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -700,6 +700,13 @@ typedef enum */ #define ECMA_OBJECT_FLAG_EXTENSIBLE 0x20 +/** + * Non closure flag for debugger. + */ +#ifdef JERRY_DEBUGGER +#define ECMA_OBJECT_FLAG_NON_CLOSURE 0x20 +#endif /* JERRY_DEBUGGER */ + /** * Value for increasing or decreasing the object reference counter. */ @@ -719,7 +726,7 @@ typedef struct /** type : 4 bit : ecma_object_type_t or ecma_lexical_environment_type_t depending on ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV flags : 2 bit : ECMA_OBJECT_FLAG_BUILT_IN_OR_LEXICAL_ENV, - ECMA_OBJECT_FLAG_EXTENSIBLE + ECMA_OBJECT_FLAG_EXTENSIBLE or ECMA_OBJECT_FLAG_NON_CLOSURE refs : 10 bit (max 1023) */ uint16_t type_flags_refs; diff --git a/jerry-core/include/jerryscript-debugger.h b/jerry-core/include/jerryscript-debugger.h index 90594512..26a07496 100644 --- a/jerry-core/include/jerryscript-debugger.h +++ b/jerry-core/include/jerryscript-debugger.h @@ -31,7 +31,7 @@ extern "C" /** * JerryScript debugger protocol version. */ -#define JERRY_DEBUGGER_VERSION (6) +#define JERRY_DEBUGGER_VERSION (7) /** * Types for the client source wait and run method. diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 7831fdfd..b8567bd4 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -3315,6 +3315,9 @@ error: } ecma_object_t *catch_env_p = ecma_create_decl_lex_env (frame_ctx_p->lex_env_p); +#ifdef JERRY_DEBUGGER + catch_env_p->type_flags_refs |= (uint16_t) ECMA_OBJECT_FLAG_NON_CLOSURE; +#endif /* JERRY_DEBUGGER */ ecma_string_t *catch_name_p = ecma_get_string_from_value (literal_start_p[literal_index]); ecma_op_create_mutable_binding (catch_env_p, catch_name_p, false); diff --git a/jerry-debugger/jerry_client.py b/jerry-debugger/jerry_client.py index 7e34b69c..cc177724 100755 --- a/jerry-debugger/jerry_client.py +++ b/jerry-debugger/jerry_client.py @@ -118,6 +118,11 @@ class DebuggerPrompt(Cmd): self.stop = True do_bt = do_backtrace + def do_variables(self, args): + """ Get scope variables from debugger """ + write(self.debugger.scope_variables(args)) + self.stop = True + def do_src(self, args): """ Get current source code """ if args: @@ -172,6 +177,11 @@ class DebuggerPrompt(Cmd): self.stop = True do_ms = do_memstats + def do_scopes(self, _): + """ Memory statistics """ + self.debugger.scope_chain() + self.stop = True + def do_abort(self, args): """ Throw an exception """ self.debugger.abort(args) diff --git a/jerry-debugger/jerry_client_ws.py b/jerry-debugger/jerry_client_ws.py index 246e3762..203590b7 100644 --- a/jerry-debugger/jerry_client_ws.py +++ b/jerry-debugger/jerry_client_ws.py @@ -24,7 +24,7 @@ import struct import sys # Expected debugger protocol version. -JERRY_DEBUGGER_VERSION = 6 +JERRY_DEBUGGER_VERSION = 7 # Messages sent by the server to client. JERRY_DEBUGGER_CONFIGURATION = 1 @@ -54,6 +54,10 @@ JERRY_DEBUGGER_EVAL_RESULT_END = 24 JERRY_DEBUGGER_WAIT_FOR_SOURCE = 25 JERRY_DEBUGGER_OUTPUT_RESULT = 26 JERRY_DEBUGGER_OUTPUT_RESULT_END = 27 +JERRY_DEBUGGER_SCOPE_CHAIN = 28 +JERRY_DEBUGGER_SCOPE_CHAIN_END = 29 +JERRY_DEBUGGER_SCOPE_VARIABLES = 30 +JERRY_DEBUGGER_SCOPE_VARIABLES_END = 31 # Debugger option flags JERRY_DEBUGGER_LITTLE_ENDIAN = 0x1 @@ -94,11 +98,28 @@ JERRY_DEBUGGER_FINISH = 15 JERRY_DEBUGGER_GET_BACKTRACE = 16 JERRY_DEBUGGER_EVAL = 17 JERRY_DEBUGGER_EVAL_PART = 18 +JERRY_DEBUGGER_GET_SCOPE_CHAIN = 19 +JERRY_DEBUGGER_GET_SCOPE_VARIABLES = 20 MAX_BUFFER_SIZE = 128 WEBSOCKET_BINARY_FRAME = 2 WEBSOCKET_FIN_BIT = 0x80 +JERRY_DEBUGGER_SCOPE_WITH = 1 +JERRY_DEBUGGER_SCOPE_LOCAL = 2 +JERRY_DEBUGGER_SCOPE_CLOSURE = 3 +JERRY_DEBUGGER_SCOPE_GLOBAL = 4 +JERRY_DEBUGGER_SCOPE_NON_CLOSURE = 5 + +JERRY_DEBUGGER_VALUE_NONE = 1 +JERRY_DEBUGGER_VALUE_UNDEFINED = 2 +JERRY_DEBUGGER_VALUE_NULL = 3 +JERRY_DEBUGGER_VALUE_BOOLEAN = 4 +JERRY_DEBUGGER_VALUE_NUMBER = 5 +JERRY_DEBUGGER_VALUE_STRING = 6 +JERRY_DEBUGGER_VALUE_FUNCTION = 7 +JERRY_DEBUGGER_VALUE_ARRAY = 8 +JERRY_DEBUGGER_VALUE_OBJECT = 9 def arguments_parse(): parser = argparse.ArgumentParser(description="JerryScript debugger client") @@ -264,6 +285,8 @@ class JerryDebugger(object): self.source_name = '' self.exception_string = '' self.frame_index = 0 + self.scope_vars = "" + self.scopes = "" self.client_sources = [] self.last_breakpoint_hit = None self.next_breakpoint_index = 0 @@ -394,6 +417,10 @@ class JerryDebugger(object): self.prompt = False self._exec_command(JERRY_DEBUGGER_MEMSTATS) + def scope_chain(self): + self.prompt = False + self._exec_command(JERRY_DEBUGGER_GET_SCOPE_CHAIN) + def set_break(self, args): if not args: return "Error: Breakpoint index expected" @@ -500,6 +527,28 @@ class JerryDebugger(object): self.prompt = False return "" + def scope_variables(self, args): + index = 0 + if args: + try: + index = int(args) + if index < 0: + print ("Error: A non negative integer number expected") + return + + except ValueError as val_errno: + return "Error: Non negative integer number expected, %s\n" % (val_errno) + + message = struct.pack(self.byte_order + "BBIB" + self.idx_format, + WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT, + WEBSOCKET_FIN_BIT + 1 + 4, + 0, + JERRY_DEBUGGER_GET_SCOPE_VARIABLES, + index) + self.send_message(message) + self.prompt = False + return "" + def eval(self, code): self._send_string(JERRY_DEBUGGER_EVAL_EVAL + code, JERRY_DEBUGGER_EVAL) self.prompt = False @@ -724,7 +773,6 @@ class JerryDebugger(object): while True: data = self.get_message(False) - if not self.non_interactive: if sys.stdin in select.select([sys.stdin], [], [], 0)[0]: sys.stdin.readline() @@ -848,6 +896,29 @@ class JerryDebugger(object): elif buffer_type == JERRY_DEBUGGER_WAIT_FOR_SOURCE: self.send_client_source() + + elif buffer_type in [JERRY_DEBUGGER_SCOPE_CHAIN, JERRY_DEBUGGER_SCOPE_CHAIN_END]: + self.scopes = data[3:] + + if buffer_type == JERRY_DEBUGGER_SCOPE_CHAIN_END: + result = self.process_scopes() + self.scopes = "" + + self.prompt = True + + return DebuggerAction(DebuggerAction.TEXT, result) + + elif buffer_type in [JERRY_DEBUGGER_SCOPE_VARIABLES, JERRY_DEBUGGER_SCOPE_VARIABLES_END]: + self.scope_vars += "".join(data[3:]) + + if buffer_type == JERRY_DEBUGGER_SCOPE_VARIABLES_END: + result = self.process_scope_variables() + self.scope_vars = "" + + self.prompt = True + + return DebuggerAction(DebuggerAction.TEXT, result) + else: raise Exception("Unknown message") @@ -1203,3 +1274,79 @@ class JerryDebugger(object): if subtype == JERRY_DEBUGGER_EVAL_ERROR: return "Uncaught exception: %s" % (message) return message + + def process_scope_variables(self): + buff_size = len(self.scope_vars) + buff_pos = 0 + + table = [['name', 'type', 'value']] + + while buff_pos != buff_size: + # Process name + name_length = ord(self.scope_vars[buff_pos:buff_pos + 1]) + buff_pos += 1 + name = self.scope_vars[buff_pos:buff_pos + name_length] + buff_pos += name_length + + # Process type + value_type = ord(self.scope_vars[buff_pos:buff_pos + 1]) + + buff_pos += 1 + + value_length = ord(self.scope_vars[buff_pos:buff_pos + 1]) + buff_pos += 1 + value = self.scope_vars[buff_pos: buff_pos + value_length] + buff_pos += value_length + + if value_type == JERRY_DEBUGGER_VALUE_UNDEFINED: + table.append([name, 'undefined', value]) + elif value_type == JERRY_DEBUGGER_VALUE_NULL: + table.append([name, 'Null', value]) + elif value_type == JERRY_DEBUGGER_VALUE_BOOLEAN: + table.append([name, 'Boolean', value]) + elif value_type == JERRY_DEBUGGER_VALUE_NUMBER: + table.append([name, 'Number', value]) + elif value_type == JERRY_DEBUGGER_VALUE_STRING: + table.append([name, 'String', value]) + elif value_type == JERRY_DEBUGGER_VALUE_FUNCTION: + table.append([name, 'Function', value]) + elif value_type == JERRY_DEBUGGER_VALUE_ARRAY: + table.append([name, 'Array', '[' + value + ']']) + elif value_type == JERRY_DEBUGGER_VALUE_OBJECT: + table.append([name, 'Object', value]) + + result = self.form_table(table) + + return result + + def process_scopes(self): + result = "" + table = [['level', 'type']] + + for i, level in enumerate(self.scopes): + if ord(level) == JERRY_DEBUGGER_SCOPE_WITH: + table.append([str(i), 'with']) + elif ord(level) == JERRY_DEBUGGER_SCOPE_GLOBAL: + table.append([str(i), 'global']) + elif ord(level) == JERRY_DEBUGGER_SCOPE_NON_CLOSURE: + # Currently it is only marks the catch closure. + table.append([str(i), 'catch']) + elif ord(level) == JERRY_DEBUGGER_SCOPE_LOCAL: + table.append([str(i), 'local']) + elif ord(level) == JERRY_DEBUGGER_SCOPE_CLOSURE: + table.append([str(i), 'closure']) + else: + raise Exception("Unexpected scope chain element") + + result = self.form_table(table) + + return result + + def form_table(self, table): + result = "" + col_width = [max(len(x) for x in col) for col in zip(*table)] + for line in table: + result += " | ".join("{:{}}".format(x, col_width[i]) + for i, x in enumerate(line)) + " \n" + + return result diff --git a/tests/debugger/do_help.expected b/tests/debugger/do_help.expected index 0d281948..60fb6543 100644 --- a/tests/debugger/do_help.expected +++ b/tests/debugger/do_help.expected @@ -4,9 +4,9 @@ Stopped at tests/debugger/do_help.js:15 Documented commands (type help ): ======================================== -abort bt display exception list next s step -b c dump f memstats quit scroll throw -backtrace continue e finish ms res source -break delete eval help n restart src +abort bt display exception list next s src +b c dump f memstats quit scopes step +backtrace continue e finish ms res scroll throw +break delete eval help n restart source variables (jerry-debugger) quit diff --git a/tests/debugger/do_scopes.cmd b/tests/debugger/do_scopes.cmd new file mode 100644 index 00000000..69a869c9 --- /dev/null +++ b/tests/debugger/do_scopes.cmd @@ -0,0 +1,19 @@ +scopes +b do_scopes.js:22 +c +scopes +c +scopes +b do_scopes.js:28 +c +scopes +b do_scopes.js:31 +c +scopes +b do_scopes.js:33 +c +scopes +b do_scopes.js:35 +c +scopes +c diff --git a/tests/debugger/do_scopes.expected b/tests/debugger/do_scopes.expected new file mode 100644 index 00000000..84e761d3 --- /dev/null +++ b/tests/debugger/do_scopes.expected @@ -0,0 +1,63 @@ +Connecting to: localhost:5001 +Stopped at tests/debugger/do_scopes.js:15 +(jerry-debugger) scopes +level | type +0 | global +(jerry-debugger) b do_scopes.js:22 +Breakpoint 1 at tests/debugger/do_scopes.js:22 (in f() at line:17, col:1) +(jerry-debugger) c +Exception throw detected (to disable automatic stop type exception 0) +Exception hint: error +Stopped at tests/debugger/do_scopes.js:19 (in f() at line:17, col:1) +(jerry-debugger) scopes +level | type +0 | local +1 | global +(jerry-debugger) c +Stopped at breakpoint:1 tests/debugger/do_scopes.js:22 (in f() at line:17, col:1) +(jerry-debugger) scopes +level | type +0 | catch +1 | local +2 | global +(jerry-debugger) b do_scopes.js:28 +Breakpoint 2 at tests/debugger/do_scopes.js:28 (in function() at line:27, col:4) +(jerry-debugger) c +Stopped at breakpoint:2 tests/debugger/do_scopes.js:28 (in function() at line:27, col:4) +(jerry-debugger) scopes +level | type +0 | local +1 | closure +2 | global +(jerry-debugger) b do_scopes.js:31 +Breakpoint 3 at tests/debugger/do_scopes.js:31 (in function() at line:27, col:4) +(jerry-debugger) c +Stopped at breakpoint:3 tests/debugger/do_scopes.js:31 (in function() at line:27, col:4) +(jerry-debugger) scopes +level | type +0 | with +1 | local +2 | closure +3 | global +(jerry-debugger) b do_scopes.js:33 +Breakpoint 4 at tests/debugger/do_scopes.js:33 (in function() at line:27, col:4) +(jerry-debugger) c +Stopped at breakpoint:4 tests/debugger/do_scopes.js:33 (in function() at line:27, col:4) +(jerry-debugger) scopes +level | type +0 | with +1 | with +2 | local +3 | closure +4 | global +(jerry-debugger) b do_scopes.js:35 +Breakpoint 5 at tests/debugger/do_scopes.js:35 (in function() at line:27, col:4) +(jerry-debugger) c +Stopped at breakpoint:5 tests/debugger/do_scopes.js:35 (in function() at line:27, col:4) +(jerry-debugger) scopes +level | type +0 | with +1 | local +2 | closure +3 | global +(jerry-debugger) c diff --git a/tests/debugger/do_scopes.js b/tests/debugger/do_scopes.js new file mode 100644 index 00000000..5c14b96f --- /dev/null +++ b/tests/debugger/do_scopes.js @@ -0,0 +1,41 @@ +// 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. + +var c = 4; + +function f() { + try { + throw "error"; + } + catch (err) { + var c = 10; + } + + var z = true; + var g = 0; + (function() { + var a = [1,2,3] + a.y = "abc"; + with (a) { + var h = [4,5,6] + with (h) { + h.d = "dfg" + } + a.d = g + c; + } + })(); +} + +f(); + diff --git a/tests/debugger/do_variables.cmd b/tests/debugger/do_variables.cmd new file mode 100644 index 00000000..a9b02082 --- /dev/null +++ b/tests/debugger/do_variables.cmd @@ -0,0 +1,28 @@ +scopes +variables +variables 1 +variables 0 +b tests/debugger/do_variables.js:20 +c +scopes +variables 0 +variables 1 +variables 2 +b tests/debugger/do_variables.js:30 +c +scopes +variables 1 +variables 0 +b tests/debugger/do_variables.js:33 +c +c +scopes +variables 0 +variables 1 +b tests/debugger/do_variables.js:50 +c +scopes +variables 0 +variables 1 +variables 2 +c diff --git a/tests/debugger/do_variables.expected b/tests/debugger/do_variables.expected new file mode 100644 index 00000000..a2b8a6c6 --- /dev/null +++ b/tests/debugger/do_variables.expected @@ -0,0 +1,128 @@ +Connecting to: localhost:5001 +Stopped at tests/debugger/do_variables.js:15 +(jerry-debugger) scopes +level | type +0 | global +(jerry-debugger) variables +name | type | value +f | Function | +addX | Function | +z | undefined | undefined +c | undefined | undefined +print | Function | +gc | Function | +assert | Function | +(jerry-debugger) variables 1 +name | type | value +(jerry-debugger) variables 0 +name | type | value +f | Function | +addX | Function | +z | undefined | undefined +c | undefined | undefined +print | Function | +gc | Function | +assert | Function | +(jerry-debugger) b tests/debugger/do_variables.js:20 +Breakpoint 1 at tests/debugger/do_variables.js:20 (in function() at line:19, col:10) +(jerry-debugger) c +Stopped at breakpoint:1 tests/debugger/do_variables.js:20 (in function() at line:19, col:10) +(jerry-debugger) scopes +level | type +0 | local +1 | closure +2 | global +(jerry-debugger) variables 0 +name | type | value +n | Number | 9 +b | undefined | undefined +(jerry-debugger) variables 1 +name | type | value +x | Number | 3 +(jerry-debugger) variables 2 +name | type | value +addThree | Function | +f | Function | +addX | Function | +z | Number | 5 +c | Number | 4 +print | Function | +gc | Function | +assert | Function | +(jerry-debugger) b tests/debugger/do_variables.js:30 +Breakpoint 2 at tests/debugger/do_variables.js:30 (in f() at line:28, col:1) +(jerry-debugger) c +Stopped at breakpoint:2 tests/debugger/do_variables.js:30 (in f() at line:28, col:1) +(jerry-debugger) scopes +level | type +0 | local +1 | global +(jerry-debugger) variables 1 +name | type | value +d | Number | 12 +addThree | Function | +f | Function | +addX | Function | +z | Number | 5 +c | Number | 4 +print | Function | +gc | Function | +assert | Function | +(jerry-debugger) variables 0 +name | type | value +b | undefined | undefined +x | undefined | undefined +user | undefined | undefined +m | undefined | undefined +c | undefined | undefined +(jerry-debugger) b tests/debugger/do_variables.js:33 +Breakpoint 3 at tests/debugger/do_variables.js:33 (in f() at line:28, col:1) +(jerry-debugger) c +Exception throw detected (to disable automatic stop type exception 0) +Exception hint: error +Stopped at breakpoint:2 tests/debugger/do_variables.js:30 (in f() at line:28, col:1) +(jerry-debugger) c +Stopped at breakpoint:3 tests/debugger/do_variables.js:33 (in f() at line:28, col:1) +(jerry-debugger) scopes +level | type +0 | catch +1 | local +2 | global +(jerry-debugger) variables 0 +name | type | value +err | String | error +(jerry-debugger) variables 1 +name | type | value +b | undefined | undefined +x | undefined | undefined +user | undefined | undefined +m | undefined | undefined +c | undefined | undefined +(jerry-debugger) b tests/debugger/do_variables.js:50 +Breakpoint 4 at tests/debugger/do_variables.js:50 (in function() at line:46, col:4) +(jerry-debugger) c +Stopped at breakpoint:4 tests/debugger/do_variables.js:50 (in function() at line:46, col:4) +(jerry-debugger) scopes +level | type +0 | with +1 | local +2 | closure +3 | global +(jerry-debugger) variables 0 +name | type | value +y | String | abc +2 | Number | 3 +1 | Number | 2 +0 | Number | 1 +(jerry-debugger) variables 1 +name | type | value +h | undefined | undefined +a | Array | [1,2,3] +(jerry-debugger) variables 2 +name | type | value +b | Number | 10 +x | Boolean | true +user | Object | {"name":"John","age":30} +m | Null | null +c | Number | 10 +(jerry-debugger) c diff --git a/tests/debugger/do_variables.js b/tests/debugger/do_variables.js new file mode 100644 index 00000000..ea769747 --- /dev/null +++ b/tests/debugger/do_variables.js @@ -0,0 +1,60 @@ +// 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. + +var c = 4; +var z = 5; + +function addX(x) { + return function(n) { + var b = 2; + return n + x; + } +} + +addThree = addX(3); +d = addThree(c+z); + +function f() { + try { + throw "error"; + } + catch (err) { + var c = 10; + } + + var m = null; + + var user = { + name: "John", + age: 30 + }; + + var x = true; + var b = 10; + + (function() { + var a = [1,2,3] + a.y = "abc"; + with (a) { + var h = [4,5,6] + with (h) { + h.d = "dfg" + } + a.d = x; + } + })(); +} + +f(); + -- cgit v1.2.3