diff --git a/Makefile b/Makefile index 0270a6a..1c78547 100644 --- a/Makefile +++ b/Makefile @@ -445,6 +445,7 @@ test: qjs ./qjs tests/test_bignum.js ./qjs tests/test_std.js ./qjs tests/test_worker.js + ./qjs tests/test_line_column.js ifdef CONFIG_SHARED_LIBS ifdef CONFIG_BIGNUM ./qjs --bignum tests/test_bjson.js diff --git a/cutils.c b/cutils.c index c0aacef..37ed9c2 100644 --- a/cutils.c +++ b/cutils.c @@ -303,6 +303,16 @@ int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp) return c; } +int utf8_str_len(const uint8_t *p_start, const uint8_t *p_end) { + int count = 0; + while (p_start < p_end) { + if (!unicode_from_utf8(p_start, UTF8_CHAR_LEN_MAX, &p_start)) + break; + count += 1; + } + return count; +} + #if 0 #if defined(EMSCRIPTEN) || defined(__ANDROID__) diff --git a/cutils.h b/cutils.h index f079e5c..75ab735 100644 --- a/cutils.h +++ b/cutils.h @@ -297,6 +297,7 @@ static inline void dbuf_set_error(DynBuf *s) int unicode_to_utf8(uint8_t *buf, unsigned int c); int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp); +int utf8_str_len(const uint8_t *p_start, const uint8_t *p_end); static inline BOOL is_surrogate(uint32_t c) { diff --git a/quickjs-atom.h b/quickjs-atom.h index f4d5838..481ddb0 100644 --- a/quickjs-atom.h +++ b/quickjs-atom.h @@ -81,6 +81,7 @@ DEF(empty_string, "") DEF(length, "length") DEF(fileName, "fileName") DEF(lineNumber, "lineNumber") +DEF(columnNumber, "columnNumber") DEF(message, "message") DEF(cause, "cause") DEF(errors, "errors") diff --git a/quickjs-opcode.h b/quickjs-opcode.h index 1e18212..40cb15e 100644 --- a/quickjs-opcode.h +++ b/quickjs-opcode.h @@ -291,6 +291,7 @@ def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in pha def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ +def( column_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ #if SHORT_OPCODES DEF( push_minus1, 1, 0, 1, none_int) diff --git a/quickjs.c b/quickjs.c index e8fdd8a..92745d8 100644 --- a/quickjs.c +++ b/quickjs.c @@ -573,6 +573,10 @@ typedef struct JSVarDef { #define PC2LINE_RANGE 5 #define PC2LINE_OP_FIRST 1 #define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) +#define PC2COLUMN_BASE (-1) +#define PC2COLUMN_RANGE 5 +#define PC2COLUMN_OP_FIRST 1 +#define PC2COLUMN_DIFF_PC_MAX ((255 - PC2COLUMN_OP_FIRST) / PC2COLUMN_RANGE) typedef enum JSFunctionKindEnum { JS_FUNC_NORMAL = 0, @@ -616,9 +620,12 @@ typedef struct JSFunctionBytecode { /* debug info, move to separate structure to save memory? */ JSAtom filename; int line_num; + int column_num; int source_len; int pc2line_len; + int pc2column_len; uint8_t *pc2line_buf; + uint8_t *pc2column_buf; char *source; } debug; } JSFunctionBytecode; @@ -5891,6 +5898,8 @@ typedef struct JSMemoryUsage_helper { int64_t js_func_code_size; int64_t js_func_pc2line_count; int64_t js_func_pc2line_size; + int64_t js_func_pc2column_count; + int64_t js_func_pc2column_size; } JSMemoryUsage_helper; static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp); @@ -5938,6 +5947,11 @@ static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *h hp->js_func_pc2line_count += 1; hp->js_func_pc2line_size += b->debug.pc2line_len; } + if (b->debug.pc2column_len) { + memory_used_count++; + hp->js_func_pc2column_count += 1; + hp->js_func_pc2column_size += b->debug.pc2column_len; + } } hp->js_func_size += js_func_size; hp->js_func_count += 1; @@ -6239,13 +6253,17 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) s->js_func_code_size = mem.js_func_code_size; s->js_func_pc2line_count = mem.js_func_pc2line_count; s->js_func_pc2line_size = mem.js_func_pc2line_size; + s->js_func_pc2column_count = mem.js_func_pc2column_count; + s->js_func_pc2column_size = mem.js_func_pc2column_size; s->memory_used_count += round(mem.memory_used_count) + s->atom_count + s->str_count + s->obj_count + s->shape_count + - s->js_func_count + s->js_func_pc2line_count; + s->js_func_count + s->js_func_pc2line_count + + s->js_func_pc2column_count; s->memory_used_size += s->atom_size + s->str_size + s->obj_size + s->prop_size + s->shape_size + - s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; + s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size + + s->js_func_pc2column_size; } void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) @@ -6357,6 +6375,12 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) s->js_func_pc2line_size, (double)s->js_func_pc2line_size / s->js_func_pc2line_count); } + if(s->js_func_pc2column_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " pc2column", s->js_func_pc2column_count, + s->js_func_pc2column_size, + (double)s->js_func_pc2column_size / s->js_func_pc2column_count); + } } if (s->c_func_count) { fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); @@ -6501,6 +6525,55 @@ static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, return line_num; } +int find_column_num(JSContext* ctx, JSFunctionBytecode* b, + uint32_t pc_value) +{ + const uint8_t* p_end, *p; + int new_column_num, column_num, pc, v, ret; + unsigned int op; + + if (!b->has_debug || !b->debug.pc2column_buf) { + /* function was stripped */ + return -1; + } + + pc = 0; + p = b->debug.pc2column_buf; + p_end = p + b->debug.pc2column_len; + column_num = b->debug.column_num; + while (p < p_end) { + op = *p++; + if (op == 0) { + uint32_t val; + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) { + fail: + /* should never happen */ + return b->debug.column_num; + } + p += ret; + new_column_num = column_num + v; + } else { + op -= PC2COLUMN_OP_FIRST; + pc += (op / PC2COLUMN_RANGE); + new_column_num = column_num + (op % PC2COLUMN_RANGE) + PC2COLUMN_BASE; + } + + if (pc_value < pc) { + return column_num; + } + + column_num = new_column_num; + } + + return column_num; +} + /* in order to avoid executing arbitrary code during the stack trace generation, we only look at simple 'name' properties containing a string. */ @@ -6531,7 +6604,7 @@ static const char *get_func_name(JSContext *ctx, JSValueConst func) and line number information (used for parse error). */ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, const char *filename, int line_num, - int backtrace_flags) + int column_num, int backtrace_flags) { JSStackFrame *sf; JSValue str; @@ -6540,18 +6613,24 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, const char *str1; JSObject *p; BOOL backtrace_barrier; + int latest_line_num = -1; + int latest_column_num = -1; js_dbuf_init(ctx, &dbuf); if (filename) { dbuf_printf(&dbuf, " at %s", filename); - if (line_num != -1) + if (line_num != -1) { + latest_line_num = line_num; dbuf_printf(&dbuf, ":%d", line_num); + } + if (column_num != -1) { + latest_column_num = column_num; + dbuf_printf(&dbuf, ":%d", column_num); + } dbuf_putc(&dbuf, '\n'); str = JS_NewString(ctx, filename); JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); if (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL) goto done; } @@ -6573,19 +6652,33 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, if (js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b; const char *atom_str; - int line_num1; b = p->u.func.function_bytecode; backtrace_barrier = b->backtrace_barrier; if (b->has_debug) { - line_num1 = find_line_num(ctx, b, - sf->cur_pc - b->byte_code_buf - 1); + line_num = find_line_num(ctx, b, sf->cur_pc - b->byte_code_buf - 1); + column_num = find_column_num(ctx, b, sf->cur_pc - b->byte_code_buf - 1); + line_num = line_num == -1 ? b->debug.line_num : line_num; + column_num = column_num == -1 ? b->debug.column_num : column_num; + if (column_num != -1) { + column_num += 1; + } + if (latest_line_num == -1) { + latest_line_num = line_num; + } + if (latest_column_num == -1) { + latest_column_num = column_num; + } atom_str = JS_AtomToCString(ctx, b->debug.filename); dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : ""); JS_FreeCString(ctx, atom_str); - if (line_num1 != -1) - dbuf_printf(&dbuf, ":%d", line_num1); + if (line_num != -1) { + dbuf_printf(&dbuf, ":%d", line_num); + if (column_num != -1) { + dbuf_printf(&dbuf, ":%d", column_num); + } + } dbuf_putc(&dbuf, ')'); } } else { @@ -6605,6 +6698,15 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, dbuf_free(&dbuf); JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, str, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (line_num != -1) { + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, latest_line_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + if (column_num != -1) { + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_columnNumber, JS_NewInt32(ctx, latest_column_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + } } /* Note: it is important that no exception is returned by this function */ @@ -6644,7 +6746,7 @@ static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } if (add_backtrace) { - build_backtrace(ctx, obj, NULL, 0, 0); + build_backtrace(ctx, obj, NULL, 0, 0, 0); } ret = JS_Throw(ctx, obj); return ret; @@ -14794,6 +14896,14 @@ static JSValue js_function_proto_lineNumber(JSContext *ctx, return JS_UNDEFINED; } +static JSValue js_function_proto_columnNumber(JSContext *ctx, JSValueConst this_val) { + JSFunctionBytecode* b = JS_GetFunctionBytecode(this_val); + if (b && b->has_debug) { + return JS_NewInt32(ctx, b->debug.column_num); + } + return JS_UNDEFINED; +} + static int js_arguments_define_own_property(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValueConst val, @@ -18637,7 +18747,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, before if the exception happens in a bytecode operation */ sf->cur_pc = pc; - build_backtrace(ctx, rt->current_exception, NULL, 0, 0); + build_backtrace(ctx, rt->current_exception, NULL, 0, 0, 0); } if (!JS_IsUncatchableError(ctx, rt->current_exception)) { while (sp > stack_buf) { @@ -19911,6 +20021,11 @@ typedef struct LineNumberSlot { int line_num; } LineNumberSlot; +typedef struct ColumnNumberSlot { + uint32_t pc; + int column_num; +} ColumnNumberSlot; + typedef enum JSParseFunctionEnum { JS_PARSE_FUNC_STATEMENT, JS_PARSE_FUNC_VAR, @@ -20030,10 +20145,18 @@ typedef struct JSFunctionDef { int line_number_last; int line_number_last_pc; + ColumnNumberSlot* column_number_slots; + int column_number_size; + int column_number_count; + int column_number_last_pc; + int column_number_last; + /* pc2line table */ JSAtom filename; int line_num; + int column_num; DynBuf pc2line; + DynBuf pc2column; char *source; /* raw source, utf-8 encoded */ int source_len; @@ -20044,7 +20167,8 @@ typedef struct JSFunctionDef { typedef struct JSToken { int val; - int line_num; /* line number of token start */ + int line_num; /* line number of token start */ + int column_num; /* colum number of token start */ const uint8_t *ptr; union { struct { @@ -20073,6 +20197,9 @@ typedef struct JSParseState { JSContext *ctx; int last_line_num; /* line number of last token */ int line_num; /* line number of current offset */ + const uint8_t *column_ptr; + const uint8_t *column_last_ptr; + int column_num_count; const char *filename; JSToken token; BOOL got_lf; /* true if got line feed before the current token */ @@ -20213,11 +20340,19 @@ static void __attribute((unused)) dump_token(JSParseState *s, } } +static int calc_column_position(JSParseState *s) { + if(s->column_last_ptr > s->column_ptr) { + s->column_num_count += utf8_str_len(s->column_ptr, s->column_last_ptr); + s->column_ptr = s->column_last_ptr; + } + return s->column_num_count; +} + int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...) { JSContext *ctx = s->ctx; va_list ap; - int backtrace_flags; + int backtrace_flags, column_num; va_start(ap, fmt); JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); @@ -20225,7 +20360,9 @@ int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const backtrace_flags = 0; if (s->cur_func && s->cur_func->backtrace_barrier) backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; + column_num = calc_column_position(s); build_backtrace(ctx, ctx->rt->current_exception, s->filename, s->line_num, + column_num < 0 ? -1 : column_num, backtrace_flags); return -1; } @@ -20263,6 +20400,7 @@ static __exception int js_parse_template_part(JSParseState *s, const uint8_t *p) { uint32_t c; StringBuffer b_s, *b = &b_s; + s->token.column_num = calc_column_position(s); /* p points to the first byte of the template part */ if (string_buffer_init(s->ctx, b, 32)) @@ -20295,6 +20433,8 @@ static __exception int js_parse_template_part(JSParseState *s, const uint8_t *p) } if (c == '\n') { s->line_num++; + s->column_ptr = s->column_last_ptr = p; + s->column_num_count = 0; } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); @@ -20327,6 +20467,7 @@ static __exception int js_parse_string(JSParseState *s, int sep, int ret; uint32_t c; StringBuffer b_s, *b = &b_s; + s->token.column_num = calc_column_position(s); /* string */ if (string_buffer_init(s->ctx, b, 32)) @@ -20382,8 +20523,11 @@ static __exception int js_parse_string(JSParseState *s, int sep, case '\n': /* ignore escaped newline sequence */ p++; - if (sep != '`') + if (sep != '`') { s->line_num++; + s->column_ptr = s->column_last_ptr = p; + s->column_num_count = 0; + } continue; default: if (c >= '0' && c <= '9') { @@ -20698,7 +20842,9 @@ static __exception int next_token(JSParseState *s) s->got_lf = FALSE; s->last_line_num = s->token.line_num; redo: + s->column_last_ptr = p; s->token.line_num = s->line_num; + s->token.column_num = 0; s->token.ptr = p; c = *p; switch(c) { @@ -20729,6 +20875,8 @@ static __exception int next_token(JSParseState *s) line_terminator: s->got_lf = TRUE; s->line_num++; + s->column_ptr = p; + s->column_num_count = 0; goto redo; case '\f': case '\v': @@ -20752,7 +20900,8 @@ static __exception int next_token(JSParseState *s) if (*p == '\n') { s->line_num++; s->got_lf = TRUE; /* considered as LF for ASI */ - p++; + s->column_ptr = ++p; + s->column_num_count = 0; } else if (*p == '\r') { s->got_lf = TRUE; /* considered as LF for ASI */ p++; @@ -21164,6 +21313,9 @@ static __exception int next_token(JSParseState *s) break; } s->buf_ptr = p; + if (!s->token.column_num) { + s->token.column_num = calc_column_position(s); + } // dump_token(s, &s->token); return 0; @@ -21222,7 +21374,9 @@ static __exception int json_next_token(JSParseState *s) p = s->last_ptr = s->buf_ptr; s->last_line_num = s->token.line_num; redo: + s->column_last_ptr = p; s->token.line_num = s->line_num; + s->token.column_num = 0; s->token.ptr = p; c = *p; switch(c) { @@ -21251,6 +21405,8 @@ static __exception int json_next_token(JSParseState *s) case '\n': p++; s->line_num++; + s->column_ptr = p; + s->column_num_count = 0; goto redo; case '\f': case '\v': @@ -21282,7 +21438,8 @@ static __exception int json_next_token(JSParseState *s) } if (*p == '\n') { s->line_num++; - p++; + s->column_ptr = ++p; + s->column_num_count = 0; } else if (*p == '\r') { p++; } else if (*p >= 0x80) { @@ -21392,6 +21549,9 @@ static __exception int json_next_token(JSParseState *s) break; } s->buf_ptr = p; + if (!s->token.column_num) { + s->token.column_num = calc_column_position(s); + } // dump_token(s, &s->token); return 0; @@ -21599,6 +21759,11 @@ static void emit_atom(JSParseState *s, JSAtom name) emit_u32(s, JS_DupAtom(s->ctx, name)); } +static void emit_column(JSParseState *s, int column_num) { + emit_u8(s, OP_column_num); + emit_u32(s, column_num); +} + static int update_label(JSFunctionDef *s, int label, int delta) { LabelSlot *ls; @@ -22162,7 +22327,7 @@ static __exception int js_parse_function_decl(JSParseState *s, JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, - int start_line); + int start_line, int start_column); static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s); static __exception int js_parse_function_decl2(JSParseState *s, JSParseFunctionEnum func_type, @@ -22170,6 +22335,7 @@ static __exception int js_parse_function_decl2(JSParseState *s, JSAtom func_name, const uint8_t *ptr, int function_line_num, + int function_column_num, JSParseExportEnum export_flag, JSFunctionDef **pfd); static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags); @@ -22463,12 +22629,18 @@ typedef struct JSParsePos { int line_num; BOOL got_lf; const uint8_t *ptr; + const uint8_t *column_ptr; + const uint8_t *column_last_ptr; + int column_num_count; } JSParsePos; static int js_parse_get_pos(JSParseState *s, JSParsePos *sp) { sp->last_line_num = s->last_line_num; sp->line_num = s->token.line_num; + sp->column_ptr = s->column_ptr; + sp->column_last_ptr = s->column_last_ptr; + sp->column_num_count = s->column_num_count; sp->ptr = s->token.ptr; sp->got_lf = s->got_lf; return 0; @@ -22478,6 +22650,9 @@ static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp { s->token.line_num = sp->last_line_num; s->line_num = sp->line_num; + s->column_ptr = sp->column_ptr; + s->column_last_ptr = sp->column_last_ptr; + s->column_num_count = sp->column_num_count; s->buf_ptr = sp->ptr; s->got_lf = sp->got_lf; return next_token(s); @@ -22683,7 +22858,7 @@ static __exception int js_parse_object_literal(JSParseState *s) { JSAtom name = JS_ATOM_NULL; const uint8_t *start_ptr; - int start_line, prop_type; + int start_line, start_column, prop_type; BOOL has_proto; if (next_token(s)) @@ -22695,6 +22870,7 @@ static __exception int js_parse_object_literal(JSParseState *s) /* specific case for getter/setter */ start_ptr = s->token.ptr; start_line = s->token.line_num; + start_column = s->token.column_num; if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) @@ -22740,7 +22916,7 @@ static __exception int js_parse_object_literal(JSParseState *s) func_kind = JS_FUNC_ASYNC_GENERATOR; } if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL, - start_ptr, start_line)) + start_ptr, start_line, start_column)) goto fail; if (name == JS_ATOM_NULL) { emit_op(s, OP_define_method_computed); @@ -22816,7 +22992,7 @@ static __exception int js_parse_class_default_ctor(JSParseState *s, { JSParsePos pos; const char *str; - int ret, line_num; + int ret, line_num, column_num; JSParseFunctionEnum func_type; const uint8_t *saved_buf_end; @@ -22830,14 +23006,17 @@ static __exception int js_parse_class_default_ctor(JSParseState *s, func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; } line_num = s->token.line_num; + column_num = s->token.column_num; saved_buf_end = s->buf_end; s->buf_ptr = (uint8_t *)str; s->buf_end = (uint8_t *)(str + strlen(str)); + s->column_last_ptr = s->buf_ptr; ret = next_token(s); if (!ret) { ret = js_parse_function_decl2(s, func_type, JS_FUNC_NORMAL, JS_ATOM_NULL, (uint8_t *)str, - line_num, JS_PARSE_EXPORT_NONE, pfd); + line_num, column_num, + JS_PARSE_EXPORT_NONE, pfd); } s->buf_end = saved_buf_end; ret |= js_parse_seek_token(s, &pos); @@ -23070,7 +23249,7 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, // stack is now: if (js_parse_function_decl2(s, JS_PARSE_FUNC_CLASS_STATIC_INIT, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num, + s->token.ptr, s->token.line_num,s->token.column_num, JS_PARSE_EXPORT_NONE, &init) < 0) { goto fail; } @@ -23146,7 +23325,8 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set, JS_FUNC_NORMAL, JS_ATOM_NULL, start_ptr, s->token.line_num, - JS_PARSE_EXPORT_NONE, &method_fd)) + s->token.column_num, JS_PARSE_EXPORT_NONE, + &method_fd)) goto fail; if (is_private) { method_fd->need_home_object = TRUE; /* needed for brand check */ @@ -23289,7 +23469,7 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, if (is_private) { class_fields[is_static].need_brand = TRUE; } - if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd)) + if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, s->token.column_num, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) { @@ -23979,6 +24159,7 @@ static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg, JSAtom prop_name, var_name; int opcode, scope, tok1, skip_bits; BOOL has_initializer; + emit_column(s, s->token.column_num); if (has_ellipsis < 0) { /* pre-parse destructuration target for spread detection */ @@ -24416,10 +24597,12 @@ static void optional_chain_test(JSParseState *s, int *poptional_chaining_label, static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) { FuncCallType call_type; - int optional_chaining_label; + int optional_chaining_label, column_num; BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; call_type = FUNC_CALL_NORMAL; + column_num = s->token.column_num; + emit_column(s, column_num); switch(s->token.val) { case TOK_NUMBER: { @@ -24499,7 +24682,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; build_backtrace(s->ctx, s->ctx->rt->current_exception, s->filename, s->token.line_num, - backtrace_flags); + s->token.column_num, backtrace_flags); return -1; } ret = emit_push_const(s, str, 0); @@ -24521,7 +24704,8 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) case TOK_FUNCTION: if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) + s->token.ptr, s->token.line_num, + s->token.column_num)) return -1; break; case TOK_CLASS: @@ -24560,15 +24744,18 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) peek_token(s, TRUE) != '\n') { const uint8_t *source_ptr; int source_line_num; + int source_column_num; source_ptr = s->token.ptr; source_line_num = s->token.line_num; + source_column_num = s->token.column_num; if (next_token(s)) return -1; if (s->token.val == TOK_FUNCTION) { if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_ASYNC, JS_ATOM_NULL, - source_ptr, source_line_num)) + source_ptr, source_line_num, + source_column_num)) return -1; } else { name = JS_DupAtom(s->ctx, JS_ATOM_async); @@ -24960,6 +25147,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) break; } } else { + emit_column(s, column_num); if (next_token(s)) return -1; emit_func_call: @@ -25003,6 +25191,8 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) } else if (s->token.val == '.') { if (next_token(s)) return -1; + column_num = s->token.column_num; + emit_column(s, column_num); parse_property: if (s->token.val == TOK_PRIVATE_NAME) { /* private class field */ @@ -25086,6 +25276,7 @@ static __exception int js_parse_delete(JSParseState *s) JSFunctionDef *fd = s->cur_func; JSAtom name; int opcode; + emit_column(s, s->token.column_num); if (next_token(s)) return -1; @@ -25549,6 +25740,7 @@ static __exception int js_parse_coalesce_expr(JSParseState *s, int parse_flags) emit_op(s, OP_is_undefined_or_null); emit_goto(s, OP_if_false, label1); emit_op(s, OP_drop); + emit_column(s, s->token.column_num); if (js_parse_expr_binary(s, 8, parse_flags)) return -1; @@ -25597,6 +25789,7 @@ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) int opcode, op, scope; JSAtom name0 = JS_ATOM_NULL; JSAtom name; + emit_column(s, s->token.column_num); if (s->token.val == TOK_YIELD) { BOOL is_star = FALSE, is_async; @@ -25739,10 +25932,10 @@ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) { return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num); + s->token.ptr, s->token.line_num, s->token.column_num); } else if (token_is_pseudo_keyword(s, JS_ATOM_async)) { const uint8_t *source_ptr; - int source_line_num, tok; + int source_line_num, source_column_num, tok; JSParsePos pos; /* fast test */ @@ -25752,6 +25945,7 @@ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) source_ptr = s->token.ptr; source_line_num = s->token.line_num; + source_column_num = s->token.column_num; js_parse_get_pos(s, &pos); if (next_token(s)) return -1; @@ -25761,7 +25955,7 @@ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) peek_token(s, TRUE) == TOK_ARROW)) { return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_ASYNC, JS_ATOM_NULL, - source_ptr, source_line_num); + source_ptr, source_line_num, source_column_num); } else { /* undo the token parsing */ if (js_parse_seek_token(s, &pos)) @@ -25771,7 +25965,7 @@ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) peek_token(s, TRUE) == TOK_ARROW) { return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num); + s->token.ptr, s->token.line_num, s->token.column_num); } next: if (s->token.val == TOK_IDENT) { @@ -26496,6 +26690,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, JSContext *ctx = s->ctx; JSAtom label_name; int tok; + emit_column(s, s->token.column_num); /* specific label handling */ /* XXX: support multiple labels on loop statements */ @@ -27185,7 +27380,8 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, parse_func_var: if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) + s->token.ptr, s->token.line_num, + s->token.column_num)) goto fail; break; } @@ -29096,7 +29292,8 @@ static __exception int js_parse_export(JSParseState *s) return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num, - JS_PARSE_EXPORT_NAMED, NULL); + s->token.column_num, JS_PARSE_EXPORT_NAMED, + NULL); } if (next_token(s)) @@ -29206,7 +29403,8 @@ static __exception int js_parse_export(JSParseState *s) return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num, - JS_PARSE_EXPORT_DEFAULT, NULL); + s->token.column_num, JS_PARSE_EXPORT_DEFAULT, + NULL); } else { if (js_parse_assign_expr(s)) return -1; @@ -29403,7 +29601,8 @@ static __exception int js_parse_source_element(JSParseState *s) peek_token(s, TRUE) == TOK_FUNCTION)) { if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num)) + s->token.ptr, s->token.line_num, + s->token.column_num)) return -1; } else if (s->token.val == TOK_EXPORT && fd->module) { if (js_parse_export(s)) @@ -29425,7 +29624,9 @@ static JSFunctionDef *js_new_function_def(JSContext *ctx, JSFunctionDef *parent, BOOL is_eval, BOOL is_func_expr, - const char *filename, int line_num) + const char *filename, + int line_num, + int column_num) { JSFunctionDef *fd; @@ -29473,8 +29674,10 @@ static JSFunctionDef *js_new_function_def(JSContext *ctx, fd->filename = JS_NewAtom(ctx, filename); fd->line_num = line_num; + fd->column_num = column_num; js_dbuf_init(ctx, &fd->pc2line); + js_dbuf_init(ctx, &fd->pc2column); //fd->pc2line_last_line_num = line_num; //fd->pc2line_last_pc = 0; fd->last_opcode_line_num = line_num; @@ -29533,6 +29736,7 @@ static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd) js_free(ctx, fd->jump_slots); js_free(ctx, fd->label_slots); js_free(ctx, fd->line_number_slots); + js_free(ctx, fd->column_number_slots); for(i = 0; i < fd->cpool_count; i++) { JS_FreeValue(ctx, fd->cpool[i]); @@ -29566,6 +29770,7 @@ static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd) JS_FreeAtom(ctx, fd->filename); dbuf_free(&fd->pc2line); + dbuf_free(&fd->pc2column); js_free(ctx, fd->source); @@ -31563,6 +31768,10 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s) s->line_number_size++; goto no_change; + case OP_column_num: + s->column_number_size++; + goto no_change; + case OP_eval: /* convert scope index to adjusted variable index */ { int call_argc = get_u16(bc_buf + pos + 1); @@ -31877,6 +32086,21 @@ static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, int line_num) } } +/* the pc2col table gives a column number for each PC value */ +static void add_pc2col_info(JSFunctionDef *s, uint32_t pc, int column_num) +{ + if(s->column_number_slots != NULL + && s->column_number_count < s->column_number_size + && pc >= s->column_number_last_pc + && column_num != s->column_number_last) { + s->column_number_slots[s->column_number_count].pc = pc; + s->column_number_slots[s->column_number_count].column_num = column_num; + s->column_number_count++; + s->column_number_last_pc = pc; + s->column_number_last = column_num; + } +} + static void compute_pc2line_info(JSFunctionDef *s) { if (!(s->js_mode & JS_MODE_STRIP) && s->line_number_slots) { @@ -31915,6 +32139,45 @@ static void compute_pc2line_info(JSFunctionDef *s) } } +static void compute_pc2column_info(JSFunctionDef *s) +{ + if(!(s->js_mode & JS_MODE_STRIP) && s->column_number_slots) { + int last_column_num = s->column_num; + uint32_t last_pc = 0; + int i; + + js_dbuf_init(s->ctx, &s->pc2column); + for(i = 0; i < s->column_number_count; i++) { + uint32_t pc = s->column_number_slots[i].pc; + int column_num = s->column_number_slots[i].column_num; + int diff_pc, diff_column; + + if (column_num < 0) + continue; + + diff_pc = pc - last_pc; + diff_column = column_num - last_column_num; + if (diff_column == 0 || diff_pc < 0) + continue; + + if (diff_column >= PC2COLUMN_BASE && + diff_column < PC2COLUMN_BASE + PC2COLUMN_RANGE && + diff_pc <= PC2COLUMN_DIFF_PC_MAX) { + dbuf_putc(&s->pc2column, (diff_column - PC2COLUMN_BASE) + + diff_pc * PC2COLUMN_RANGE + PC2COLUMN_OP_FIRST); + } else { + /* longer encoding */ + dbuf_putc(&s->pc2column, 0); + dbuf_put_leb128(&s->pc2column, diff_pc); + dbuf_put_sleb128(&s->pc2column, diff_column); + } + + last_pc = pc; + last_column_num = column_num; + } + } +} + static RelocEntry *add_reloc(JSContext *ctx, LabelSlot *ls, uint32_t addr, int size) { RelocEntry *re; @@ -32079,7 +32342,7 @@ static void put_short_code(DynBuf *bc_out, int op, int idx) /* peephole optimizations and resolve goto/labels */ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) { - int pos, pos_next, bc_len, op, op1, len, i, line_num; + int pos, pos_next, bc_len, op, op1, len, i, line_num, column_num; const uint8_t *bc_buf; DynBuf bc_out; LabelSlot *label_slots, *ls; @@ -32093,7 +32356,7 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) label_slots = s->label_slots; line_num = s->line_num; - + column_num = s->column_num; cc.bc_buf = bc_buf = s->byte_code.buf; cc.bc_len = bc_len = s->byte_code.size; js_dbuf_init(ctx, &bc_out); @@ -32114,6 +32377,14 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) s->line_number_last_pc = 0; } + if(s->column_number_size && !(s->js_mode & JS_MODE_STRIP)) { + s->column_number_slots = js_mallocz(s->ctx, sizeof(*s->column_number_slots) * s->column_number_size); + if(s->column_number_slots == NULL) + return -1; + s->column_number_last = s->column_num; + s->column_number_last_pc = 0; + } + /* initialize the 'home_object' variable if needed */ if (s->home_object_var_idx >= 0) { dbuf_putc(&bc_out, OP_special_object); @@ -32187,6 +32458,12 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) line_num = get_u32(bc_buf + pos + 1); break; + case OP_column_num: + /* same with OP_line_num */ + column_num = get_u32(bc_buf + pos + 1); + add_pc2col_info(s, bc_out.size, column_num); + break; + case OP_label: { label = get_u32(bc_buf + pos + 1); @@ -32910,6 +33187,11 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) if (s->line_number_slots[j].pc > pos) s->line_number_slots[j].pc -= delta; } + for (j = 0; j < s->column_number_count; j++) { + if (s->column_number_slots[j].pc > pos) { + s->column_number_slots[j].pc -= delta; + } + } continue; } break; @@ -32941,8 +33223,11 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) s->label_slots = NULL; /* XXX: should delay until copying to runtime bytecode function */ compute_pc2line_info(s); + compute_pc2column_info(s); js_free(ctx, s->line_number_slots); + js_free(ctx, s->column_number_slots); s->line_number_slots = NULL; + s->column_number_slots = NULL; /* set the new byte code */ dbuf_free(&s->byte_code); s->byte_code = bc_out; @@ -33413,6 +33698,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) if (fd->js_mode & JS_MODE_STRIP) { JS_FreeAtom(ctx, fd->filename); dbuf_free(&fd->pc2line); // probably useless + dbuf_free(&fd->pc2column); } else { /* XXX: source and pc2line info should be packed at the end of the JSFunctionBytecode structure, avoiding allocation overhead @@ -33420,14 +33706,19 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) b->has_debug = 1; b->debug.filename = fd->filename; b->debug.line_num = fd->line_num; + b->debug.column_num = fd->column_num; //DynBuf pc2line; //compute_pc2line_info(fd, &pc2line); //js_free(ctx, fd->line_number_slots) b->debug.pc2line_buf = js_realloc(ctx, fd->pc2line.buf, fd->pc2line.size); + b->debug.pc2column_buf = js_realloc(ctx, fd->pc2column.buf, fd->pc2column.size); if (!b->debug.pc2line_buf) b->debug.pc2line_buf = fd->pc2line.buf; + if(!b->debug.pc2column_buf) + b->debug.pc2column_buf = fd->pc2column.buf; b->debug.pc2line_len = fd->pc2line.size; + b->debug.pc2column_len = fd->pc2column.size; b->debug.source = fd->source; b->debug.source_len = fd->source_len; } @@ -33510,6 +33801,7 @@ static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b) if (b->has_debug) { JS_FreeAtomRT(rt, b->debug.filename); js_free_rt(rt, b->debug.pc2line_buf); + js_free_rt(rt, b->debug.pc2column_buf); js_free_rt(rt, b->debug.source); } @@ -33673,7 +33965,7 @@ static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s) JSFunctionDef *fd; fd = js_new_function_def(s->ctx, s->cur_func, FALSE, FALSE, - s->filename, 0); + s->filename, 0, 0); if (!fd) return NULL; fd->func_name = JS_ATOM_NULL; @@ -33701,6 +33993,7 @@ static __exception int js_parse_function_decl2(JSParseState *s, JSAtom func_name, const uint8_t *ptr, int function_line_num, + int function_column_num, JSParseExportEnum export_flag, JSFunctionDef **pfd) { @@ -33815,7 +34108,8 @@ static __exception int js_parse_function_decl2(JSParseState *s, } fd = js_new_function_def(ctx, fd, FALSE, is_expr, - s->filename, function_line_num); + s->filename, function_line_num, + function_column_num); if (!fd) { JS_FreeAtom(ctx, func_name); return -1; @@ -34265,11 +34559,12 @@ static __exception int js_parse_function_decl(JSParseState *s, JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, - int function_line_num) + int function_line_num, + int function_column_num) { return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr, - function_line_num, JS_PARSE_EXPORT_NONE, - NULL); + function_line_num, function_column_num, + JS_PARSE_EXPORT_NONE, NULL); } static __exception int js_parse_program(JSParseState *s) @@ -34332,10 +34627,14 @@ static void js_parse_init(JSContext *ctx, JSParseState *s, s->ctx = ctx; s->filename = filename; s->line_num = 1; + s->column_ptr = (const uint8_t*)input; + s->column_last_ptr = s->column_ptr; + s->column_num_count = 0; s->buf_ptr = (const uint8_t *)input; s->buf_end = s->buf_ptr + input_len; s->token.val = ' '; s->token.line_num = 1; + s->token.column_num = 0; } static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, @@ -34423,7 +34722,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, js_mode |= JS_MODE_STRICT; } } - fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1); + fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1, 0); if (!fd) goto fail1; s->cur_func = fd; @@ -35174,6 +35473,9 @@ static int JS_WriteFunctionTag(BCWriterState *s, JSValueConst obj) bc_put_leb128(s, b->debug.line_num); bc_put_leb128(s, b->debug.pc2line_len); dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len); + bc_put_leb128(s, b->debug.column_num); + bc_put_leb128(s, b->debug.pc2column_len); + dbuf_put(&s->dbuf, b->debug.pc2column_buf, b->debug.pc2column_len); } for(i = 0; i < b->cpool_count; i++) { @@ -36216,6 +36518,17 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) if (bc_get_buf(s, b->debug.pc2line_buf, b->debug.pc2line_len)) goto fail; } + if (bc_get_leb128_int(s, &b->debug.column_num)) + goto fail; + if (bc_get_leb128_int(s, &b->debug.pc2column_len)) + goto fail; + if (b->debug.pc2column_len) { + b->debug.pc2column_buf = js_mallocz(ctx, b->debug.pc2column_len); + if (!b->debug.pc2column_buf) + goto fail; + if (bc_get_buf(s, b->debug.pc2column_buf, b->debug.pc2column_len)) + goto fail; + } #ifdef DUMP_READ_OBJECT bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); #endif @@ -38674,6 +38987,7 @@ static const JSCFunctionListEntry js_function_proto_funcs[] = { JS_CFUNC_DEF("[Symbol.hasInstance]", 1, js_function_hasInstance ), JS_CGETSET_DEF("fileName", js_function_proto_fileName, NULL ), JS_CGETSET_DEF("lineNumber", js_function_proto_lineNumber, NULL ), + JS_CGETSET_DEF("columnNumber", js_function_proto_columnNumber, NULL), }; /* Error class */ @@ -38783,7 +39097,7 @@ static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, } /* skip the Error() function in the backtrace */ - build_backtrace(ctx, obj, NULL, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); + build_backtrace(ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); return obj; exception: JS_FreeValue(ctx, obj); diff --git a/quickjs.h b/quickjs.h index 7199936..1986647 100644 --- a/quickjs.h +++ b/quickjs.h @@ -410,6 +410,7 @@ typedef struct JSMemoryUsage { int64_t shape_count, shape_size; int64_t js_func_count, js_func_size, js_func_code_size; int64_t js_func_pc2line_count, js_func_pc2line_size; + int64_t js_func_pc2column_count, js_func_pc2column_size; int64_t c_func_count, array_count; int64_t fast_array_count, fast_array_elements; int64_t binary_object_count, binary_object_size; diff --git a/tests/test_line_column.js b/tests/test_line_column.js new file mode 100644 index 0000000..4301ee0 --- /dev/null +++ b/tests/test_line_column.js @@ -0,0 +1,240 @@ +"use strict"; + +function assert(actual, expected, message) { + if (arguments.length == 1) expected = true; + + if (actual === expected) return; + + if (actual !== null && expected !== null && typeof actual == 'object' && + typeof expected == 'object' && actual.toString() === expected.toString()) + return; + + throw Error( + 'assertion failed: got |' + actual + '|' + + ', expected |' + expected + '|' + (message ? ' (' + message + ')' : '')); +} + +/** id not exists -> should be located at id */ +function test_line_column1() { + try { + eval(`'【';A;`); + } catch (e) { + assert(e.lineNumber, 1, 'test_line_column1'); + assert(e.columnNumber, 5, 'test_line_column1'); + } +} + +/** + * memeber call should be located at id: + * a.b.c() and c is null -> c will be located + */ +function test_line_column2() { + try { + eval(` +var a = { b: { c: { d: null }} }; +a.b.c.d(); + `); + } catch (e) { + assert(e.lineNumber, 3, 'test_line_column2'); + assert(e.columnNumber, 7, 'test_line_column2'); + } +} + +/** + * memeber call should be located at id: + * a.b.c() and b is null -> c will be located + */ +function test_line_column3() { + try { + eval(` +var a = { b: { c: { d: null }} }; +a.f.c.d(); + `); + } catch (e) { + assert(e.lineNumber, 3, 'test_line_column3'); + assert(e.columnNumber, 5, 'test_line_column3'); + } +} + +/** if id not exists -> should be located at id */ +function test_line_column4() { + try { + eval(`(function(){'use strict';a;}());`); + } catch (e) { + assert(e.lineNumber, 1, 'test_line_column4'); + assert(e.columnNumber, 26, 'test_line_column4'); + } +} + +/** if id not exists -> should be located at id */ +function test_line_column5() { + try { + eval(`'【';1+1;new A();`); + } catch (e) { + assert(e.lineNumber, 1, 'test_line_column5'); + assert(e.columnNumber, 13, 'test_line_column5'); + } +} + +/** new call should be located at 'new' */ +function test_line_column6() { + try { + eval(`'【';1+1;throw new Error();`); + } catch (e) { + assert(e.lineNumber, 1, 'test_line_column6'); + assert(e.columnNumber, 15, 'test_line_column6'); + } +} + +/** + * normal call should be located at function name: + * a() and a is null or occur error -> a will be located + */ +function test_line_column7() { + try { + eval(`1+1;a();`); + } catch (e) { + assert(e.lineNumber, 1, 'test_line_column7'); + assert(e.columnNumber, 5, 'test_line_column7'); + } +} + +/** + * if comment is first line, + * the line number of one line should be locate at next line + */ +function test_line_column8() { + try { + eval(` +/** + * something + * comment + * here + */ +1+1;a(); + `); + } catch (e) { + assert(e.lineNumber, 7, 'test_line_column8'); + assert(e.columnNumber, 5, 'test_line_column8'); + } +} + +/** nest function call */ +function test_line_column9() { + try { + eval(`(function(){'【'(function(){'use strict';a;}())}())`); + } catch (e) { + assert(e.lineNumber, 1, 'test_line_column9'); + assert(e.columnNumber, 41, 'test_line_column9'); + } +} + +/** multi function call */ +function test_line_column10() { + try { + eval(` +function a(){ + throw new Error(); +} + +function b(){ + a(); +} + +b(); + `); + } catch (e) { + assert(e.lineNumber, 3, 'test_line_column10'); + assert(e.columnNumber, 11, 'test_line_column10'); + } +} + +/** syntax error should be located at latest token position */ +function test_line_column11() { + try { + eval(` +var a = { + b: if(1){} +} + `); + } catch (e) { + assert(e.lineNumber, 3, 'test_line_column11'); + assert(e.columnNumber, 7, 'test_line_column11'); + } +} + +/** string template cases */ +function test_line_column12() { +// case 1 + try { + eval(` +var a = \`\$\{b;\} +1+1 +\`; + `); + } catch (e) { + assert(e.lineNumber, 2, 'test_line_column12'); + assert(e.columnNumber, 12, 'test_line_column12'); + } + +// case 2 + try { + eval(` +var a = \`1+1 +\$\{b;\} +2+2 +\`; + `); + } catch (e) { + assert(e.lineNumber, 3, 'test_line_column12'); + assert(e.columnNumber, 3, 'test_line_column12'); + } + +// case 3 + try { + eval(` +var a = \`1+1 +2+2 +\${b\}\`; + `); + } catch (e) { + assert(e.lineNumber, 4, 'test_line_column12'); + assert(e.columnNumber, 3, 'test_line_column12'); + } + +// case 4 + try { + eval(` +var a = \`1+1 +2+2 +\${3+3\}\`;b; + `); + } catch (e) { + assert(e.lineNumber, 4, 'test_line_column12'); + assert(e.columnNumber, 9, 'test_line_column12'); + } +} + +/** dynamic Function parse error should be located the latest token */ +function test_line_column13() { + try { + eval(`Function("===>", "a");`); + } catch (e) { + assert(e.lineNumber, 1, 'test_line_column13'); + assert(e.columnNumber, 20, 'test_line_column13'); + } +} + +test_line_column1(); +test_line_column2(); +test_line_column3(); +test_line_column4(); +test_line_column5(); +test_line_column6(); +test_line_column7(); +test_line_column8(); +test_line_column9(); +test_line_column10(); +test_line_column11(); +test_line_column12(); +test_line_column13(); \ No newline at end of file