Print exception hint in the debugger client when an exception is thrown. (#1841)
JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com
This commit is contained in:
@@ -16,12 +16,14 @@
|
|||||||
#ifdef JERRY_DEBUGGER
|
#ifdef JERRY_DEBUGGER
|
||||||
|
|
||||||
#include "byte-code.h"
|
#include "byte-code.h"
|
||||||
|
#include "ecma-builtin-helpers.h"
|
||||||
#include "ecma-conversion.h"
|
#include "ecma-conversion.h"
|
||||||
#include "ecma-eval.h"
|
#include "ecma-eval.h"
|
||||||
#include "ecma-objects.h"
|
#include "ecma-objects.h"
|
||||||
#include "jcontext.h"
|
#include "jcontext.h"
|
||||||
#include "jerry-debugger.h"
|
#include "jerry-debugger.h"
|
||||||
#include "jerryscript-port.h"
|
#include "jerryscript-port.h"
|
||||||
|
#include "lit-char-helpers.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type cast the debugger send buffer into a specific type.
|
* Type cast the debugger send buffer into a specific type.
|
||||||
@@ -715,4 +717,151 @@ jerry_debugger_send_memstats (void)
|
|||||||
jerry_debugger_send (sizeof (jerry_debugger_send_memstats_t));
|
jerry_debugger_send (sizeof (jerry_debugger_send_memstats_t));
|
||||||
} /* jerry_debugger_send_memstats */
|
} /* jerry_debugger_send_memstats */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts an standard error into a string.
|
||||||
|
*
|
||||||
|
* @return standard error string
|
||||||
|
*/
|
||||||
|
static ecma_string_t *
|
||||||
|
jerry_debugger_exception_object_to_string (ecma_value_t exception_obj_value) /**< exception object */
|
||||||
|
{
|
||||||
|
ecma_object_t *object_p = ecma_get_object_from_value (exception_obj_value);
|
||||||
|
|
||||||
|
ecma_object_t *prototype_p = ecma_get_object_prototype (object_p);
|
||||||
|
|
||||||
|
if (prototype_p == NULL
|
||||||
|
|| ecma_get_object_type (prototype_p) != ECMA_OBJECT_TYPE_GENERAL
|
||||||
|
|| !ecma_get_object_is_builtin (prototype_p))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lit_magic_string_id_t string_id;
|
||||||
|
|
||||||
|
switch (((ecma_extended_object_t *) prototype_p)->u.built_in.id)
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_DISABLE_ERROR_BUILTINS
|
||||||
|
case ECMA_BUILTIN_ID_EVAL_ERROR_PROTOTYPE:
|
||||||
|
{
|
||||||
|
string_id = LIT_MAGIC_STRING_EVAL_ERROR_UL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECMA_BUILTIN_ID_RANGE_ERROR_PROTOTYPE:
|
||||||
|
{
|
||||||
|
string_id = LIT_MAGIC_STRING_RANGE_ERROR_UL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECMA_BUILTIN_ID_REFERENCE_ERROR_PROTOTYPE:
|
||||||
|
{
|
||||||
|
string_id = LIT_MAGIC_STRING_REFERENCE_ERROR_UL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECMA_BUILTIN_ID_SYNTAX_ERROR_PROTOTYPE:
|
||||||
|
{
|
||||||
|
string_id = LIT_MAGIC_STRING_SYNTAX_ERROR_UL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECMA_BUILTIN_ID_TYPE_ERROR_PROTOTYPE:
|
||||||
|
{
|
||||||
|
string_id = LIT_MAGIC_STRING_TYPE_ERROR_UL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECMA_BUILTIN_ID_URI_ERROR_PROTOTYPE:
|
||||||
|
{
|
||||||
|
string_id = LIT_MAGIC_STRING_URI_ERROR_UL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif /* !CONFIG_DISABLE_ERROR_BUILTINS */
|
||||||
|
case ECMA_BUILTIN_ID_ERROR_PROTOTYPE:
|
||||||
|
{
|
||||||
|
string_id = LIT_MAGIC_STRING_ERROR_UL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lit_utf8_size_t size = lit_get_magic_string_size (string_id);
|
||||||
|
JERRY_ASSERT (size <= 14);
|
||||||
|
|
||||||
|
lit_utf8_byte_t data[16];
|
||||||
|
memcpy (data, lit_get_magic_string_utf8 (string_id), size);
|
||||||
|
|
||||||
|
ecma_string_t message_string;
|
||||||
|
ecma_init_ecma_magic_string (&message_string, LIT_MAGIC_STRING_MESSAGE);
|
||||||
|
|
||||||
|
ecma_property_t *property_p;
|
||||||
|
property_p = ecma_find_named_property (ecma_get_object_from_value (exception_obj_value),
|
||||||
|
&message_string);
|
||||||
|
|
||||||
|
if (property_p == NULL
|
||||||
|
|| ECMA_PROPERTY_GET_TYPE (*property_p) != ECMA_PROPERTY_TYPE_NAMEDDATA)
|
||||||
|
{
|
||||||
|
return ecma_new_ecma_string_from_utf8 (data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ecma_property_value_t *prop_value_p = ECMA_PROPERTY_VALUE_PTR (property_p);
|
||||||
|
|
||||||
|
if (!ecma_is_value_string (prop_value_p->value))
|
||||||
|
{
|
||||||
|
return ecma_new_ecma_string_from_utf8 (data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
data[size] = LIT_CHAR_COLON;
|
||||||
|
data[size + 1] = LIT_CHAR_SP;
|
||||||
|
|
||||||
|
ecma_string_t *type_string_p = ecma_new_ecma_string_from_utf8 (data, size + 2);
|
||||||
|
|
||||||
|
ecma_string_t *string_p = ecma_concat_ecma_strings (type_string_p,
|
||||||
|
ecma_get_string_from_value (prop_value_p->value));
|
||||||
|
ecma_deref_ecma_string (type_string_p);
|
||||||
|
return string_p;
|
||||||
|
} /* jerry_debugger_exception_object_to_string */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send string representation of exception to the client.
|
||||||
|
*
|
||||||
|
* @return true - if the data sent successfully to the debugger client,
|
||||||
|
* false - otherwise
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
jerry_debugger_send_exception_string (ecma_value_t exception_value) /**< error value */
|
||||||
|
{
|
||||||
|
ecma_string_t *string_p = NULL;
|
||||||
|
|
||||||
|
if (ecma_is_value_object (exception_value))
|
||||||
|
{
|
||||||
|
ecma_value_t object_value = ecma_get_value_from_error_value (exception_value);
|
||||||
|
|
||||||
|
string_p = jerry_debugger_exception_object_to_string (object_value);
|
||||||
|
if (string_p == NULL)
|
||||||
|
{
|
||||||
|
string_p = ecma_get_string_from_value (ecma_builtin_helper_object_to_string (object_value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ecma_is_value_string (exception_value))
|
||||||
|
{
|
||||||
|
string_p = ecma_get_string_from_value (exception_value);
|
||||||
|
ecma_ref_ecma_string (string_p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exception_value = ecma_op_to_string (exception_value);
|
||||||
|
string_p = ecma_get_string_from_value (exception_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ECMA_STRING_TO_UTF8_STRING (string_p, string_data_p, string_size);
|
||||||
|
|
||||||
|
bool result = jerry_debugger_send_string (JERRY_DEBUGGER_EXCEPTION_STR,
|
||||||
|
string_data_p,
|
||||||
|
string_size);
|
||||||
|
|
||||||
|
ECMA_FINALIZE_UTF8_STRING (string_data_p, string_size);
|
||||||
|
|
||||||
|
ecma_deref_ecma_string (string_p);
|
||||||
|
return result;
|
||||||
|
} /* jerry_debugger_send_exception_string */
|
||||||
|
|
||||||
#endif /* JERRY_DEBUGGER */
|
#endif /* JERRY_DEBUGGER */
|
||||||
|
|||||||
@@ -102,12 +102,14 @@ typedef enum
|
|||||||
JERRY_DEBUGGER_MEMSTATS_RECEIVE = 14, /**< memstats sent to the client*/
|
JERRY_DEBUGGER_MEMSTATS_RECEIVE = 14, /**< memstats sent to the client*/
|
||||||
JERRY_DEBUGGER_BREAKPOINT_HIT = 15, /**< notify breakpoint hit */
|
JERRY_DEBUGGER_BREAKPOINT_HIT = 15, /**< notify breakpoint hit */
|
||||||
JERRY_DEBUGGER_EXCEPTION_HIT = 16, /**< notify exception hit */
|
JERRY_DEBUGGER_EXCEPTION_HIT = 16, /**< notify exception hit */
|
||||||
JERRY_DEBUGGER_BACKTRACE = 17, /**< backtrace data */
|
JERRY_DEBUGGER_EXCEPTION_STR = 17, /**< exception string fragment */
|
||||||
JERRY_DEBUGGER_BACKTRACE_END = 18, /**< last backtrace data */
|
JERRY_DEBUGGER_EXCEPTION_STR_END = 18, /**< exception string last fragment */
|
||||||
JERRY_DEBUGGER_EVAL_RESULT = 19, /**< eval result */
|
JERRY_DEBUGGER_BACKTRACE = 19, /**< backtrace data */
|
||||||
JERRY_DEBUGGER_EVAL_RESULT_END = 20, /**< last part of eval result */
|
JERRY_DEBUGGER_BACKTRACE_END = 20, /**< last backtrace data */
|
||||||
JERRY_DEBUGGER_EVAL_ERROR = 21, /**< eval result when an error is occured */
|
JERRY_DEBUGGER_EVAL_RESULT = 21, /**< eval result */
|
||||||
JERRY_DEBUGGER_EVAL_ERROR_END = 22, /**< last part of eval result when an error is occured */
|
JERRY_DEBUGGER_EVAL_RESULT_END = 22, /**< last part of eval result */
|
||||||
|
JERRY_DEBUGGER_EVAL_ERROR = 23, /**< eval result when an error is occured */
|
||||||
|
JERRY_DEBUGGER_EVAL_ERROR_END = 24, /**< last part of eval result when an error is occured */
|
||||||
|
|
||||||
/* Messages sent by the client to server. */
|
/* Messages sent by the client to server. */
|
||||||
|
|
||||||
@@ -316,6 +318,7 @@ bool jerry_debugger_send_string (uint8_t message_type, const uint8_t *string_p,
|
|||||||
bool jerry_debugger_send_function_cp (jerry_debugger_header_type_t type, ecma_compiled_code_t *compiled_code_p);
|
bool jerry_debugger_send_function_cp (jerry_debugger_header_type_t type, ecma_compiled_code_t *compiled_code_p);
|
||||||
bool jerry_debugger_send_parse_function (uint32_t line, uint32_t column);
|
bool jerry_debugger_send_parse_function (uint32_t line, uint32_t column);
|
||||||
void jerry_debugger_send_memstats (void);
|
void jerry_debugger_send_memstats (void);
|
||||||
|
bool jerry_debugger_send_exception_string (ecma_value_t exception_value);
|
||||||
|
|
||||||
#endif /* JERRY_DEBUGGER */
|
#endif /* JERRY_DEBUGGER */
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -2550,7 +2550,10 @@ error:
|
|||||||
&& !(frame_ctx_p->bytecode_header_p->status_flags & CBC_CODE_FLAGS_DEBUGGER_IGNORE)
|
&& !(frame_ctx_p->bytecode_header_p->status_flags & CBC_CODE_FLAGS_DEBUGGER_IGNORE)
|
||||||
&& !(JERRY_CONTEXT (debugger_flags) & (JERRY_DEBUGGER_VM_IGNORE_EXCEPTION | JERRY_DEBUGGER_VM_IGNORE)))
|
&& !(JERRY_CONTEXT (debugger_flags) & (JERRY_DEBUGGER_VM_IGNORE_EXCEPTION | JERRY_DEBUGGER_VM_IGNORE)))
|
||||||
{
|
{
|
||||||
jerry_debugger_breakpoint_hit (JERRY_DEBUGGER_EXCEPTION_HIT);
|
if (jerry_debugger_send_exception_string (result))
|
||||||
|
{
|
||||||
|
jerry_debugger_breakpoint_hit (JERRY_DEBUGGER_EXCEPTION_HIT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif /* JERRY_DEBUGGER */
|
#endif /* JERRY_DEBUGGER */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,12 +51,14 @@ var JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 13;
|
|||||||
var JERRY_DEBUGGER_MEMSTATS_RECEIVE = 14;
|
var JERRY_DEBUGGER_MEMSTATS_RECEIVE = 14;
|
||||||
var JERRY_DEBUGGER_BREAKPOINT_HIT = 15;
|
var JERRY_DEBUGGER_BREAKPOINT_HIT = 15;
|
||||||
var JERRY_DEBUGGER_EXCEPTION_HIT = 16;
|
var JERRY_DEBUGGER_EXCEPTION_HIT = 16;
|
||||||
var JERRY_DEBUGGER_BACKTRACE = 17;
|
var JERRY_DEBUGGER_EXCEPTION_STR = 17;
|
||||||
var JERRY_DEBUGGER_BACKTRACE_END = 18;
|
var JERRY_DEBUGGER_EXCEPTION_STR_END = 18;
|
||||||
var JERRY_DEBUGGER_EVAL_RESULT = 19;
|
var JERRY_DEBUGGER_BACKTRACE = 19;
|
||||||
var JERRY_DEBUGGER_EVAL_RESULT_END = 20;
|
var JERRY_DEBUGGER_BACKTRACE_END = 20;
|
||||||
var JERRY_DEBUGGER_EVAL_ERROR = 21;
|
var JERRY_DEBUGGER_EVAL_RESULT = 21;
|
||||||
var JERRY_DEBUGGER_EVAL_ERROR_END = 22;
|
var JERRY_DEBUGGER_EVAL_RESULT_END = 22;
|
||||||
|
var JERRY_DEBUGGER_EVAL_ERROR = 23;
|
||||||
|
var JERRY_DEBUGGER_EVAL_ERROR_END = 24;
|
||||||
|
|
||||||
var JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1;
|
var JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1;
|
||||||
var JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2;
|
var JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2;
|
||||||
@@ -113,6 +115,7 @@ function DebuggerClient(address)
|
|||||||
var pendingBreakpoints = [ ];
|
var pendingBreakpoints = [ ];
|
||||||
var backtraceFrame = 0;
|
var backtraceFrame = 0;
|
||||||
var evalResult = null;
|
var evalResult = null;
|
||||||
|
var exceptionData = null;
|
||||||
|
|
||||||
function assert(expr)
|
function assert(expr)
|
||||||
{
|
{
|
||||||
@@ -830,6 +833,11 @@ function DebuggerClient(address)
|
|||||||
if (message[0] == JERRY_DEBUGGER_EXCEPTION_HIT)
|
if (message[0] == JERRY_DEBUGGER_EXCEPTION_HIT)
|
||||||
{
|
{
|
||||||
appendLog("Exception throw detected (to disable automatic stop type exception 0)");
|
appendLog("Exception throw detected (to disable automatic stop type exception 0)");
|
||||||
|
if (exceptionData)
|
||||||
|
{
|
||||||
|
appendLog("Exception hint: " + cesu8ToString(exceptionData));
|
||||||
|
exceptionData = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastBreakpointHit = breakpoint;
|
lastBreakpointHit = breakpoint;
|
||||||
@@ -847,6 +855,13 @@ function DebuggerClient(address)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case JERRY_DEBUGGER_EXCEPTION_STR:
|
||||||
|
case JERRY_DEBUGGER_EXCEPTION_STR_END:
|
||||||
|
{
|
||||||
|
exceptionData = concatUint8Arrays(exceptionData, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case JERRY_DEBUGGER_BACKTRACE:
|
case JERRY_DEBUGGER_BACKTRACE:
|
||||||
case JERRY_DEBUGGER_BACKTRACE_END:
|
case JERRY_DEBUGGER_BACKTRACE_END:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 13
|
|||||||
JERRY_DEBUGGER_MEMSTATS_RECEIVE = 14
|
JERRY_DEBUGGER_MEMSTATS_RECEIVE = 14
|
||||||
JERRY_DEBUGGER_BREAKPOINT_HIT = 15
|
JERRY_DEBUGGER_BREAKPOINT_HIT = 15
|
||||||
JERRY_DEBUGGER_EXCEPTION_HIT = 16
|
JERRY_DEBUGGER_EXCEPTION_HIT = 16
|
||||||
JERRY_DEBUGGER_BACKTRACE = 17
|
JERRY_DEBUGGER_EXCEPTION_STR = 17
|
||||||
JERRY_DEBUGGER_BACKTRACE_END = 18
|
JERRY_DEBUGGER_EXCEPTION_STR_END = 18
|
||||||
JERRY_DEBUGGER_EVAL_RESULT = 19
|
JERRY_DEBUGGER_BACKTRACE = 19
|
||||||
JERRY_DEBUGGER_EVAL_RESULT_END = 20
|
JERRY_DEBUGGER_BACKTRACE_END = 20
|
||||||
JERRY_DEBUGGER_EVAL_ERROR = 21
|
JERRY_DEBUGGER_EVAL_RESULT = 21
|
||||||
JERRY_DEBUGGER_EVAL_ERROR_END = 22
|
JERRY_DEBUGGER_EVAL_RESULT_END = 22
|
||||||
|
JERRY_DEBUGGER_EVAL_ERROR = 23
|
||||||
|
JERRY_DEBUGGER_EVAL_ERROR_END = 24
|
||||||
|
|
||||||
|
|
||||||
# Messages sent by the client to server.
|
# Messages sent by the client to server.
|
||||||
@@ -813,6 +815,7 @@ def main():
|
|||||||
args = arguments_parse()
|
args = arguments_parse()
|
||||||
|
|
||||||
debugger = JerryDebugger(args.address)
|
debugger = JerryDebugger(args.address)
|
||||||
|
exception_string = ""
|
||||||
|
|
||||||
non_interactive = args.non_interactive
|
non_interactive = args.non_interactive
|
||||||
|
|
||||||
@@ -865,6 +868,9 @@ def main():
|
|||||||
|
|
||||||
if buffer_type == JERRY_DEBUGGER_EXCEPTION_HIT:
|
if buffer_type == JERRY_DEBUGGER_EXCEPTION_HIT:
|
||||||
print("Exception throw detected (to disable automatic stop type exception 0)")
|
print("Exception throw detected (to disable automatic stop type exception 0)")
|
||||||
|
if exception_string:
|
||||||
|
print("Exception hint: %s" % (exception_string))
|
||||||
|
exception_string = ""
|
||||||
|
|
||||||
if breakpoint[1]:
|
if breakpoint[1]:
|
||||||
breakpoint_info = "at"
|
breakpoint_info = "at"
|
||||||
@@ -880,6 +886,12 @@ def main():
|
|||||||
if prompt.quit:
|
if prompt.quit:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
elif buffer_type == JERRY_DEBUGGER_EXCEPTION_STR:
|
||||||
|
exception_string += data[3:]
|
||||||
|
|
||||||
|
elif buffer_type == JERRY_DEBUGGER_EXCEPTION_STR_END:
|
||||||
|
exception_string += data[3:]
|
||||||
|
|
||||||
elif buffer_type in [JERRY_DEBUGGER_BACKTRACE, JERRY_DEBUGGER_BACKTRACE_END]:
|
elif buffer_type in [JERRY_DEBUGGER_BACKTRACE, JERRY_DEBUGGER_BACKTRACE_END]:
|
||||||
frame_index = 0
|
frame_index = 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
c
|
c
|
||||||
|
c
|
||||||
|
c
|
||||||
|
c
|
||||||
quit
|
quit
|
||||||
|
|||||||
@@ -2,5 +2,18 @@ Connecting to: localhost:5001
|
|||||||
Stopped at tests/debugger/do_exception.js:15
|
Stopped at tests/debugger/do_exception.js:15
|
||||||
(jerry-debugger) c
|
(jerry-debugger) c
|
||||||
Exception throw detected (to disable automatic stop type exception 0)
|
Exception throw detected (to disable automatic stop type exception 0)
|
||||||
Stopped at tests/debugger/do_exception.js:19 (in foo() at line:17, col:1)
|
Exception hint: TypeError
|
||||||
|
Stopped around tests/debugger/do_exception.js:19 (in foo() at line:17, col:1)
|
||||||
|
(jerry-debugger) c
|
||||||
|
Exception throw detected (to disable automatic stop type exception 0)
|
||||||
|
Exception hint: ReferenceError
|
||||||
|
Stopped at tests/debugger/do_exception.js:24 (in foo() at line:17, col:1)
|
||||||
|
(jerry-debugger) c
|
||||||
|
Exception throw detected (to disable automatic stop type exception 0)
|
||||||
|
Exception hint: 456
|
||||||
|
Stopped at tests/debugger/do_exception.js:29 (in foo() at line:17, col:1)
|
||||||
|
(jerry-debugger) c
|
||||||
|
Exception throw detected (to disable automatic stop type exception 0)
|
||||||
|
Exception hint: RangeError: Bad range!
|
||||||
|
Stopped around tests/debugger/do_exception.js:34 (in foo() at line:17, col:1)
|
||||||
(jerry-debugger) quit
|
(jerry-debugger) quit
|
||||||
|
|||||||
@@ -15,11 +15,25 @@
|
|||||||
print("exception handler configuration test")
|
print("exception handler configuration test")
|
||||||
|
|
||||||
function foo() {
|
function foo() {
|
||||||
try {
|
try {
|
||||||
b = a / c;
|
undefined();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e); // pass exception object to err handler
|
}
|
||||||
}
|
|
||||||
|
try {
|
||||||
|
xxx();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw 456;
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw new RangeError("Bad range!");
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foo()
|
foo()
|
||||||
|
|||||||
Reference in New Issue
Block a user