From 251a8b2211f378456cc6bb0ccade9ce0a31e2d9c Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 14 Apr 2025 14:46:47 +0200 Subject: [PATCH] added column number in error messages - simplified parser --- quickjs-atom.h | 1 + quickjs.c | 646 +++++++++++++++++++++++++++--------------- tests/test_builtin.js | 89 ++++++ 3 files changed, 504 insertions(+), 232 deletions(-) diff --git a/quickjs-atom.h b/quickjs-atom.h index c3034b6..43f2526 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.c b/quickjs.c index f742e18..b5dcb45 100644 --- a/quickjs.c +++ b/quickjs.c @@ -633,10 +633,9 @@ typedef struct JSFunctionBytecode { struct { /* debug info, move to separate structure to save memory? */ JSAtom filename; - int line_num; + int source_len; int pc2line_len; uint8_t *pc2line_buf; - int source_len; char *source; } debug; } JSFunctionBytecode; @@ -6802,49 +6801,72 @@ static int get_sleb128(int32_t *pval, const uint8_t *buf, return ret; } +/* use pc_value = -1 to get the position of the function definition */ static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, - uint32_t pc_value) + uint32_t pc_value, int *pcol_num) { const uint8_t *p_end, *p; - int new_line_num, line_num, pc, v, ret; + int new_line_num, line_num, pc, v, ret, new_col_num, col_num; + uint32_t val; unsigned int op; - if (!b->has_debug || !b->debug.pc2line_buf) { - /* function was stripped */ - return -1; - } + if (!b->has_debug || !b->debug.pc2line_buf) + goto fail; /* function was stripped */ p = b->debug.pc2line_buf; p_end = p + b->debug.pc2line_len; - pc = 0; - line_num = b->debug.line_num; - while (p < p_end) { - op = *p++; - if (op == 0) { - uint32_t val; - ret = get_leb128(&val, p, p_end); + + /* get the function line and column numbers */ + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + line_num = val + 1; + + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + col_num = val + 1; + + if (pc_value != -1) { + pc = 0; + while (p < p_end) { + op = *p++; + if (op == 0) { + 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) + goto fail; + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128(&v, 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.line_num; - } - p += ret; - new_line_num = line_num + v; - } else { - op -= PC2LINE_OP_FIRST; - pc += (op / PC2LINE_RANGE); - new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + new_col_num = col_num + v; + + if (pc_value < pc) + goto done; + line_num = new_line_num; + col_num = new_col_num; } - if (pc_value < pc) - return line_num; - line_num = new_line_num; } + done: + *pcol_num = col_num; return line_num; + fail: + *pcol_num = 0; + return 0; } /* in order to avoid executing arbitrary code during the stack trace @@ -6874,7 +6896,7 @@ static const char *get_func_name(JSContext *ctx, JSValueConst func) /* if filename != NULL, an additional level is added with the filename and line number information (used for parse error). */ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, - const char *filename, int line_num, + const char *filename, int line_num, int col_num, int backtrace_flags) { JSStackFrame *sf; @@ -6888,13 +6910,16 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, if (filename) { dbuf_printf(&dbuf, " at %s", filename); if (line_num != -1) - dbuf_printf(&dbuf, ":%d", line_num); + dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); dbuf_putc(&dbuf, '\n'); str = JS_NewString(ctx, filename); + /* Note: SpiderMonkey does that, could update once there is a standard */ 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); + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_columnNumber, JS_NewInt32(ctx, col_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) @@ -6915,18 +6940,18 @@ 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; + int line_num1, col_num1; b = p->u.func.function_bytecode; if (b->has_debug) { line_num1 = find_line_num(ctx, b, - sf->cur_pc - b->byte_code_buf - 1); + sf->cur_pc - b->byte_code_buf - 1, &col_num1); 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_num1 != 0) + dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); dbuf_putc(&dbuf, ')'); } } else { @@ -6981,7 +7006,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; @@ -14756,11 +14781,16 @@ static JSValue js_function_proto_fileName(JSContext *ctx, } static JSValue js_function_proto_lineNumber(JSContext *ctx, - JSValueConst this_val) + JSValueConst this_val, int is_col) { JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); if (b && b->has_debug) { - return JS_NewInt32(ctx, b->debug.line_num); + int line_num, col_num; + line_num = find_line_num(ctx, b, -1, &col_num); + if (is_col) + return JS_NewInt32(ctx, col_num); + else + return JS_NewInt32(ctx, line_num); } return JS_UNDEFINED; } @@ -18597,7 +18627,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) { @@ -19863,9 +19893,17 @@ typedef struct LabelSlot { typedef struct LineNumberSlot { uint32_t pc; - int line_num; + uint32_t source_pos; } LineNumberSlot; +typedef struct { + /* last source position */ + const uint8_t *ptr; + int line_num; + int col_num; + const uint8_t *buf_start; +} GetLineColCache; + typedef enum JSParseFunctionEnum { JS_PARSE_FUNC_STATEMENT, JS_PARSE_FUNC_VAR, @@ -19956,7 +19994,7 @@ typedef struct JSFunctionDef { DynBuf byte_code; int last_opcode_pos; /* -1 if no last opcode */ - int last_opcode_line_num; + const uint8_t *last_opcode_source_ptr; BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ LabelSlot *label_slots; @@ -19988,7 +20026,8 @@ typedef struct JSFunctionDef { BOOL strip_debug : 1; /* strip all debug info (implies strip_source = TRUE) */ BOOL strip_source : 1; /* strip only source code */ JSAtom filename; - int line_num; + uint32_t source_pos; /* pointer in the eval() source */ + GetLineColCache *get_line_col_cache; /* XXX: could remove to save memory */ DynBuf pc2line; char *source; /* raw source, utf-8 encoded */ @@ -20000,8 +20039,7 @@ typedef struct JSFunctionDef { typedef struct JSToken { int val; - int line_num; /* line number of token start */ - const uint8_t *ptr; + const uint8_t *ptr; /* position in the source */ union { struct { JSValue str; @@ -20024,8 +20062,6 @@ typedef struct JSToken { typedef struct JSParseState { JSContext *ctx; - int last_line_num; /* line number of last token */ - int line_num; /* line number of current offset */ const char *filename; JSToken token; BOOL got_lf; /* true if got line feed before the current token */ @@ -20039,6 +20075,7 @@ typedef struct JSParseState { BOOL is_module; /* parsing a module */ BOOL allow_html_comments; BOOL ext_json; /* true if accepting JSON superset */ + GetLineColCache get_line_col_cache; } JSParseState; typedef struct JSOpCode { @@ -20167,19 +20204,97 @@ static void __attribute((unused)) dump_token(JSParseState *s, } } -int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...) +/* return the zero based line and column number in the source. */ +/* Note: we no longer support '\r' as line terminator */ +static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len) +{ + int line_num, col_num, c; + size_t i; + + line_num = 0; + col_num = 0; + for(i = 0; i < len; i++) { + c = buf[i]; + if (c == '\n') { + line_num++; + col_num = 0; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + *pcol_num = col_num; + return line_num; +} + +static int get_line_col_cached(GetLineColCache *s, int *pcol_num, const uint8_t *ptr) +{ + int line_num, col_num; + if (ptr >= s->ptr) { + line_num = get_line_col(&col_num, s->ptr, ptr - s->ptr); + if (line_num == 0) { + s->col_num += col_num; + } else { + s->line_num += line_num; + s->col_num = col_num; + } + } else { + line_num = get_line_col(&col_num, ptr, s->ptr - ptr); + if (line_num == 0) { + s->col_num -= col_num; + } else { + const uint8_t *p; + s->line_num -= line_num; + /* find the absolute column position */ + col_num = 0; + for(p = ptr - 1; p >= s->buf_start; p--) { + if (*p == '\n') { + break; + } else if (*p < 0x80 || *p >= 0xc0) { + col_num++; + } + } + s->col_num = col_num; + } + } + s->ptr = ptr; + *pcol_num = s->col_num; + return s->line_num; +} + +/* 'ptr' is the position of the error in the source */ +static int js_parse_error_v(JSParseState *s, const uint8_t *ptr, const char *fmt, va_list ap) { JSContext *ctx = s->ctx; - va_list ap; - - va_start(ap, fmt); + int line_num, col_num; + line_num = get_line_col(&col_num, s->buf_start, ptr - s->buf_start); JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); - va_end(ap); - build_backtrace(ctx, ctx->rt->current_exception, s->filename, s->line_num, - 0); + build_backtrace(ctx, ctx->rt->current_exception, s->filename, + line_num + 1, col_num + 1, 0); return -1; } +static __attribute__((format(printf, 3, 4))) int js_parse_error_pos(JSParseState *s, const uint8_t *ptr, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = js_parse_error_v(s, ptr, fmt, ap); + va_end(ap); + return ret; +} + +static __attribute__((format(printf, 2, 3))) int js_parse_error(JSParseState *s, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = js_parse_error_v(s, s->token.ptr, fmt, ap); + va_end(ap); + return ret; +} + static int js_parse_expect(JSParseState *s, int tok) { if (s->token.val != tok) { @@ -20243,13 +20358,11 @@ static __exception int js_parse_template_part(JSParseState *s, const uint8_t *p) p++; c = '\n'; } - if (c == '\n') { - s->line_num++; - } else if (c >= 0x80) { + if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { - js_parse_error(s, "invalid UTF-8 sequence"); + js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); goto fail; } p = p_next; @@ -20277,7 +20390,8 @@ static __exception int js_parse_string(JSParseState *s, int sep, int ret; uint32_t c; StringBuffer b_s, *b = &b_s; - + const uint8_t *p_escape; + /* string */ if (string_buffer_init(s->ctx, b, 32)) goto fail; @@ -20288,7 +20402,7 @@ static __exception int js_parse_string(JSParseState *s, int sep, if (c < 0x20) { if (!s->cur_func) { if (do_throw) - js_parse_error(s, "invalid character in a JSON string"); + js_parse_error_pos(s, p, "invalid character in a JSON string"); goto fail; } if (sep == '`') { @@ -20310,6 +20424,7 @@ static __exception int js_parse_string(JSParseState *s, int sep, break; } if (c == '\\') { + p_escape = p - 1; c = *p; /* XXX: need a specific JSON case to avoid accepting invalid escapes */ @@ -20332,8 +20447,6 @@ static __exception int js_parse_string(JSParseState *s, int sep, case '\n': /* ignore escaped newline sequence */ p++; - if (sep != '`') - s->line_num++; continue; default: if (c >= '0' && c <= '9') { @@ -20351,7 +20464,7 @@ static __exception int js_parse_string(JSParseState *s, int sep, goto invalid_escape; } else { if (do_throw) - js_parse_error(s, "octal escape sequences are not allowed in strict mode"); + js_parse_error_pos(s, p_escape, "octal escape sequences are not allowed in strict mode"); } goto fail; } @@ -20371,7 +20484,7 @@ static __exception int js_parse_string(JSParseState *s, int sep, if (ret == -1) { invalid_escape: if (do_throw) - js_parse_error(s, "malformed escape sequence in string literal"); + js_parse_error_pos(s, p_escape, "malformed escape sequence in string literal"); goto fail; } else if (ret < 0) { /* ignore the '\' (could output a warning) */ @@ -20470,16 +20583,16 @@ static __exception int js_parse_regexp(JSParseState *s) c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { invalid_utf8: - js_parse_error(s, "invalid UTF-8 sequence"); + js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); goto fail; } - p = p_next; /* LS or PS are considered as line terminator */ if (c == CP_LS || c == CP_PS) { eol_error: - js_parse_error(s, "unexpected line terminator in regexp"); + js_parse_error_pos(s, p - 1, "unexpected line terminator in regexp"); goto fail; } + p = p_next; } if (string_buffer_putc(b, c)) goto fail; @@ -20492,6 +20605,7 @@ static __exception int js_parse_regexp(JSParseState *s) if (c >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { + p++; goto invalid_utf8; } } @@ -20646,9 +20760,7 @@ static __exception int next_token(JSParseState *s) p = s->last_ptr = s->buf_ptr; s->got_lf = FALSE; - s->last_line_num = s->token.line_num; redo: - s->token.line_num = s->line_num; s->token.ptr = p; c = *p; switch(c) { @@ -20678,7 +20790,6 @@ static __exception int next_token(JSParseState *s) p++; line_terminator: s->got_lf = TRUE; - s->line_num++; goto redo; case '\f': case '\v': @@ -20699,11 +20810,7 @@ static __exception int next_token(JSParseState *s) p += 2; break; } - if (*p == '\n') { - s->line_num++; - s->got_lf = TRUE; /* considered as LF for ASI */ - p++; - } else if (*p == '\r') { + if (*p == '\n' || *p == '\r') { s->got_lf = TRUE; /* considered as LF for ASI */ p++; } else if (*p >= 0x80) { @@ -21126,9 +21233,7 @@ static __exception int json_next_token(JSParseState *s) free_token(s, &s->token); p = s->last_ptr = s->buf_ptr; - s->last_line_num = s->token.line_num; redo: - s->token.line_num = s->line_num; s->token.ptr = p; c = *p; switch(c) { @@ -21156,7 +21261,6 @@ static __exception int json_next_token(JSParseState *s) /* fall thru */ case '\n': p++; - s->line_num++; goto redo; case '\f': case '\v': @@ -21186,12 +21290,7 @@ static __exception int json_next_token(JSParseState *s) p += 2; break; } - if (*p == '\n') { - s->line_num++; - p++; - } else if (*p == '\r') { - p++; - } else if (*p >= 0x80) { + if (*p >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); if (c == -1) { p++; /* skip invalid UTF-8 */ @@ -21512,19 +21611,23 @@ static void emit_u32(JSParseState *s, uint32_t val) dbuf_put_u32(&s->cur_func->byte_code, val); } +static void emit_source_pos(JSParseState *s, const uint8_t *source_ptr) +{ + JSFunctionDef *fd = s->cur_func; + DynBuf *bc = &fd->byte_code; + + if (unlikely(fd->last_opcode_source_ptr != source_ptr)) { + dbuf_putc(bc, OP_line_num); + dbuf_put_u32(bc, source_ptr - s->buf_start); + fd->last_opcode_source_ptr = source_ptr; + } +} + static void emit_op(JSParseState *s, uint8_t val) { JSFunctionDef *fd = s->cur_func; DynBuf *bc = &fd->byte_code; - /* Use the line number of the last token used, not the next token, - nor the current offset in the source file. - */ - if (unlikely(fd->last_opcode_line_num != s->last_line_num)) { - dbuf_putc(bc, OP_line_num); - dbuf_put_u32(bc, s->last_line_num); - fd->last_opcode_line_num = s->last_line_num; - } fd->last_opcode_pos = bc->size; dbuf_putc(bc, val); } @@ -22096,15 +22199,13 @@ static __exception int js_parse_expr(JSParseState *s); 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); + JSAtom func_name, const uint8_t *ptr); static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s); static __exception int js_parse_function_decl2(JSParseState *s, JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, - int function_line_num, JSParseExportEnum export_flag, JSFunctionDef **pfd); static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags); @@ -22227,7 +22328,6 @@ static __exception int js_parse_template(JSParseState *s, int call, int *argc) /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; - s->last_line_num = s->token.line_num; if (js_parse_template_part(s, s->buf_ptr)) return -1; } @@ -22387,16 +22487,12 @@ static int __exception js_parse_property_name(JSParseState *s, } typedef struct JSParsePos { - int last_line_num; - int line_num; BOOL got_lf; const uint8_t *ptr; } 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->ptr = s->token.ptr; sp->got_lf = s->got_lf; return 0; @@ -22404,8 +22500,6 @@ static int js_parse_get_pos(JSParseState *s, JSParsePos *sp) 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->buf_ptr = sp->ptr; s->got_lf = sp->got_lf; return next_token(s); @@ -22438,6 +22532,17 @@ static BOOL is_regexp_allowed(int tok) #define SKIP_HAS_ELLIPSIS (1 << 1) #define SKIP_HAS_ASSIGNMENT (1 << 2) +static BOOL has_lf_in_range(const uint8_t *p1, const uint8_t *p2) +{ + const uint8_t *tmp; + if (p1 > p2) { + tmp = p1; + p1 = p2; + p2 = tmp; + } + return (memchr(p1, '\n', p2 - p1) != NULL); +} + /* XXX: improve speed with early bailout */ /* XXX: no longer works if regexps are present. Could use previous regexp parsing heuristics to handle most cases */ @@ -22448,7 +22553,8 @@ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_ JSParsePos pos; int last_tok, tok = TOK_EOF; int c, tok_len, bits = 0; - + const uint8_t *last_token_ptr; + /* protect from underflow */ state[level++] = 0; @@ -22479,7 +22585,6 @@ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_ /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; - s->last_line_num = s->token.line_num; if (js_parse_template_part(s, s->buf_ptr)) goto done; goto handle_template; @@ -22536,6 +22641,7 @@ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_ } else { last_tok = s->token.val; } + last_token_ptr = s->token.ptr; if (next_token(s)) { /* XXX: should clear the exception generated by next_token() */ break; @@ -22544,7 +22650,7 @@ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_ tok = s->token.val; if (token_is_pseudo_keyword(s, JS_ATOM_of)) tok = TOK_OF; - if (no_line_terminator && s->last_line_num != s->token.line_num) + if (no_line_terminator && has_lf_in_range(last_token_ptr, s->token.ptr)) tok = '\n'; break; } @@ -22611,7 +22717,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 prop_type; BOOL has_proto; if (next_token(s)) @@ -22622,7 +22728,6 @@ static __exception int js_parse_object_literal(JSParseState *s) while (s->token.val != '}') { /* specific case for getter/setter */ start_ptr = s->token.ptr; - start_line = s->token.line_num; if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) @@ -22668,7 +22773,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)) goto fail; if (name == JS_ATOM_NULL) { emit_op(s, OP_define_method_computed); @@ -22736,7 +22841,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, + const uint8_t *source_ptr, + GetLineColCache *get_line_col_cache); static void emit_return(JSParseState *s, BOOL hasval); static __exception int js_parse_left_hand_side_expr(JSParseState *s) @@ -22753,7 +22860,7 @@ static __exception int js_parse_class_default_ctor(JSParseState *s, int idx; fd = js_new_function_def(s->ctx, fd, FALSE, FALSE, s->filename, - s->token.line_num); + s->token.ptr, &s->get_line_col_cache); if (!fd) return -1; @@ -23031,7 +23138,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, JS_PARSE_EXPORT_NONE, &init) < 0) { goto fail; } @@ -23106,7 +23213,7 @@ 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, + start_ptr, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (is_private) { @@ -23250,7 +23357,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, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) { @@ -24385,7 +24492,8 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) FuncCallType call_type; int optional_chaining_label; BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; - + const uint8_t *op_token_ptr; + call_type = FUNC_CALL_NORMAL; switch(s->token.val) { case TOK_NUMBER: @@ -24444,8 +24552,10 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) s->token.u.regexp.flags); if (JS_IsException(str)) { /* add the line number info */ + int line_num, col_num; + line_num = get_line_col(&col_num, s->buf_start, s->token.ptr - s->buf_start); build_backtrace(s->ctx, s->ctx->rt->current_exception, - s->filename, s->token.line_num, 0); + s->filename, line_num + 1, col_num + 1, 0); return -1; } ret = emit_push_const(s, str, 0); @@ -24467,7 +24577,7 @@ 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)) return -1; break; case TOK_CLASS: @@ -24505,16 +24615,14 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) if (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) != '\n') { const uint8_t *source_ptr; - int source_line_num; source_ptr = s->token.ptr; - source_line_num = s->token.line_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)) return -1; } else { name = JS_DupAtom(s->ctx, JS_ATOM_async); @@ -24577,6 +24685,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) accept_lparen = TRUE; if (s->token.val != '(') { /* new operator on an object */ + emit_source_pos(s, s->token.ptr); emit_op(s, OP_dup); emit_op(s, OP_call_constructor); emit_u16(s, 0); @@ -24643,6 +24752,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) BOOL has_optional_chain = FALSE; if (s->token.val == TOK_QUESTION_MARK_DOT) { + op_token_ptr = s->token.ptr; /* optional chaining */ if (next_token(s)) return -1; @@ -24660,12 +24770,14 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) return js_parse_error(s, "template literal cannot appear in an optional chain"); } call_type = FUNC_CALL_TEMPLATE; + op_token_ptr = s->token.ptr; /* XXX: check if right position */ goto parse_func_call2; } else if (s->token.val == '(' && accept_lparen) { int opcode, arg_count, drop_count; /* function call */ parse_func_call: + op_token_ptr = s->token.ptr; if (next_token(s)) return -1; @@ -24864,6 +24976,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) /* drop the index */ emit_op(s, OP_drop); + emit_source_pos(s, op_token_ptr); /* apply function call */ switch(opcode) { case OP_get_field: @@ -24909,6 +25022,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) if (next_token(s)) return -1; emit_func_call: + emit_source_pos(s, op_token_ptr); switch(opcode) { case OP_get_field: case OP_scope_get_private_field: @@ -24947,9 +25061,11 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) } call_type = FUNC_CALL_NORMAL; } else if (s->token.val == '.') { + op_token_ptr = s->token.ptr; if (next_token(s)) return -1; parse_property: + emit_source_pos(s, op_token_ptr); if (s->token.val == TOK_PRIVATE_NAME) { /* private class field */ if (get_prev_opcode(fd) == OP_get_super) { @@ -24986,7 +25102,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) return -1; } else if (s->token.val == '[') { int prev_op; - + op_token_ptr = s->token.ptr; parse_array_access: prev_op = get_prev_opcode(fd); if (has_optional_chain) { @@ -24998,6 +25114,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) return -1; if (js_parse_expect(s, ']')) return -1; + emit_source_pos(s, op_token_ptr); if (prev_op == OP_get_super) { emit_op(s, OP_get_super_value); } else { @@ -25253,7 +25370,8 @@ static __exception int js_parse_expr_binary(JSParseState *s, int level, int parse_flags) { int op, opcode; - + const uint8_t *op_token_ptr; + if (level == 0) { return js_parse_unary(s, PF_POW_ALLOWED); } else if (s->token.val == TOK_PRIVATE_NAME && @@ -25284,6 +25402,7 @@ static __exception int js_parse_expr_binary(JSParseState *s, int level, } for(;;) { op = s->token.val; + op_token_ptr = s->token.ptr; switch(level) { case 1: switch(op) { @@ -25407,6 +25526,7 @@ static __exception int js_parse_expr_binary(JSParseState *s, int level, return -1; if (js_parse_expr_binary(s, level - 1, parse_flags)) return -1; + emit_source_pos(s, op_token_ptr); emit_op(s, opcode); } return 0; @@ -25659,10 +25779,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); } else if (token_is_pseudo_keyword(s, JS_ATOM_async)) { const uint8_t *source_ptr; - int source_line_num, tok; + int tok; JSParsePos pos; /* fast test */ @@ -25671,7 +25791,6 @@ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) goto next; source_ptr = s->token.ptr; - source_line_num = s->token.line_num; js_parse_get_pos(s, &pos); if (next_token(s)) return -1; @@ -25681,7 +25800,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); } else { /* undo the token parsing */ if (js_parse_seek_token(s, &pos)) @@ -25691,7 +25810,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); } next: if (s->token.val == TOK_IDENT) { @@ -26122,11 +26241,13 @@ static BOOL is_label(JSParseState *s) static int is_let(JSParseState *s, int decl_mask) { int res = FALSE; - + const uint8_t *last_token_ptr; + if (token_is_pseudo_keyword(s, JS_ATOM_let)) { JSParsePos pos; js_parse_get_pos(s, &pos); for (;;) { + last_token_ptr = s->token.ptr; if (next_token(s)) { res = -1; break; @@ -26145,7 +26266,8 @@ static int is_let(JSParseState *s, int decl_mask) /* Check for possible ASI if not scanning for Declaration */ /* XXX: should also check that `{` introduces a BindingPattern, but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */ - if (s->last_line_num == s->token.line_num || (decl_mask & DECL_MASK_OTHER)) { + if (!has_lf_in_range(last_token_ptr, s->token.ptr) || + (decl_mask & DECL_MASK_OTHER)) { res = TRUE; break; } @@ -26450,38 +26572,49 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, goto fail; break; case TOK_RETURN: - if (s->cur_func->is_eval) { - js_parse_error(s, "return not in a function"); - goto fail; - } - if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) { - js_parse_error(s, "return in a static initializer block"); - goto fail; - } - if (next_token(s)) - goto fail; - if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { - if (js_parse_expr(s)) + { + const uint8_t *op_token_ptr; + if (s->cur_func->is_eval) { + js_parse_error(s, "return not in a function"); + goto fail; + } + if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) { + js_parse_error(s, "return in a static initializer block"); + goto fail; + } + op_token_ptr = s->token.ptr; + if (next_token(s)) + goto fail; + if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { + if (js_parse_expr(s)) + goto fail; + emit_source_pos(s, op_token_ptr); + emit_return(s, TRUE); + } else { + emit_source_pos(s, op_token_ptr); + emit_return(s, FALSE); + } + if (js_parse_expect_semi(s)) goto fail; - emit_return(s, TRUE); - } else { - emit_return(s, FALSE); } - if (js_parse_expect_semi(s)) - goto fail; break; case TOK_THROW: - if (next_token(s)) - goto fail; - if (s->got_lf) { - js_parse_error(s, "line terminator not allowed after throw"); - goto fail; + { + const uint8_t *op_token_ptr; + op_token_ptr = s->token.ptr; + if (next_token(s)) + goto fail; + if (s->got_lf) { + js_parse_error(s, "line terminator not allowed after throw"); + goto fail; + } + if (js_parse_expr(s)) + goto fail; + emit_source_pos(s, op_token_ptr); + emit_op(s, OP_throw); + if (js_parse_expect_semi(s)) + goto fail; } - if (js_parse_expr(s)) - goto fail; - emit_op(s, OP_throw); - if (js_parse_expect_semi(s)) - goto fail; break; case TOK_LET: case TOK_CONST: @@ -27089,7 +27222,7 @@ 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)) goto fail; break; } @@ -27120,6 +27253,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, default: hasexpr: + emit_source_pos(s, s->token.ptr); if (js_parse_expr(s)) goto fail; if (s->cur_func->eval_ret_idx >= 0) { @@ -29021,7 +29155,7 @@ static __exception int js_parse_export(JSParseState *s) peek_token(s, TRUE) == TOK_FUNCTION)) { return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num, + s->token.ptr, JS_PARSE_EXPORT_NAMED, NULL); } @@ -29131,7 +29265,7 @@ static __exception int js_parse_export(JSParseState *s) peek_token(s, TRUE) == TOK_FUNCTION)) { return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, - s->token.ptr, s->token.line_num, + s->token.ptr, JS_PARSE_EXPORT_DEFAULT, NULL); } else { if (js_parse_assign_expr(s)) @@ -29329,7 +29463,7 @@ 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)) return -1; } else if (s->token.val == TOK_EXPORT && fd->module) { if (js_parse_export(s)) @@ -29351,7 +29485,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, + const uint8_t *source_ptr, + GetLineColCache *get_line_col_cache) { JSFunctionDef *fd; @@ -29400,13 +29536,13 @@ static JSFunctionDef *js_new_function_def(JSContext *ctx, fd->body_scope = -1; fd->filename = JS_NewAtom(ctx, filename); - fd->line_num = line_num; - + fd->source_pos = source_ptr - get_line_col_cache->buf_start; + fd->get_line_col_cache = get_line_col_cache; + js_dbuf_init(ctx, &fd->pc2line); //fd->pc2line_last_line_num = line_num; //fd->pc2line_last_pc = 0; - fd->last_opcode_line_num = line_num; - + fd->last_opcode_source_ptr = source_ptr; return fd; } @@ -29535,14 +29671,19 @@ static void dump_byte_code(JSContext *ctx, int pass, const JSVarDef *vars, int var_count, const JSClosureVar *closure_var, int closure_var_count, const JSValue *cpool, uint32_t cpool_count, - const char *source, int line_num, + const char *source, const LabelSlot *label_slots, JSFunctionBytecode *b) { const JSOpCode *oi; - int pos, pos_next, op, size, idx, addr, line, line1, in_source; + int pos, pos_next, op, size, idx, addr, line, line1, in_source, line_num; uint8_t *bits = js_mallocz(ctx, len * sizeof(*bits)); BOOL use_short_opcodes = (b != NULL); + if (b) { + int col_num; + line_num = find_line_num(ctx, b, -1, &col_num); + } + /* scan for jump targets */ for (pos = 0; pos < len; pos = pos_next) { op = tab[pos]; @@ -29595,10 +29736,12 @@ static void dump_byte_code(JSContext *ctx, int pass, pos = 0; while (pos < len) { op = tab[pos]; - if (source) { + if (source && b) { + int col_num; if (b) { - line1 = find_line_num(ctx, b, pos) - line_num + 1; + line1 = find_line_num(ctx, b, pos, &col_num) - line_num + 1; } else if (op == OP_line_num) { + /* XXX: no longer works */ line1 = get_u32(tab + pos + 1) - line_num + 1; } if (line1 > line) { @@ -29799,49 +29942,64 @@ static void dump_byte_code(JSContext *ctx, int pass, js_free(ctx, bits); } -static __maybe_unused void dump_pc2line(JSContext *ctx, const uint8_t *buf, int len, - int line_num) +static __maybe_unused void dump_pc2line(JSContext *ctx, const uint8_t *buf, int len) { - const uint8_t *p_end, *p_next, *p; - int pc, v; + const uint8_t *p_end, *p; + int pc, v, line_num, col_num, ret; unsigned int op; - + uint32_t val; + if (len <= 0) return; - printf("%5s %5s\n", "PC", "LINE"); + printf("%5s %5s %5s\n", "PC", "LINE", "COL"); p = buf; p_end = buf + len; + + /* get the function line and column numbers */ + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + line_num = val + 1; + + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + col_num = val + 1; + + printf("%5s %5d %5d\n", "-", line_num, col_num); + pc = 0; while (p < p_end) { op = *p++; if (op == 0) { - v = unicode_from_utf8(p, p_end - p, &p_next); - if (v < 0) + ret = get_leb128(&val, p, p_end); + if (ret < 0) goto fail; - pc += v; - p = p_next; - v = unicode_from_utf8(p, p_end - p, &p_next); - if (v < 0) { - fail: - printf("invalid pc2line encode pos=%d\n", (int)(p - buf)); - return; - } - if (!(v & 1)) { - v = v >> 1; - } else { - v = -(v >> 1) - 1; - } + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; line_num += v; - p = p_next; } else { op -= PC2LINE_OP_FIRST; pc += (op / PC2LINE_RANGE); line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE; } - printf("%5d %5d\n", pc, line_num); + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + col_num += v; + + printf("%5d %5d %5d\n", pc, line_num, col_num); } + fail: ; } static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionBytecode *b) @@ -29851,8 +30009,10 @@ static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionB const char *str; if (b->has_debug && b->debug.filename != JS_ATOM_NULL) { + int line_num, col_num; str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->debug.filename); - printf("%s:%d: ", str, b->debug.line_num); + line_num = find_line_num(ctx, b, -1, &col_num); + printf("%s:%d:%d: ", str, line_num, col_num); } str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name); @@ -29907,10 +30067,10 @@ static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionB b->closure_var, b->closure_var_count, b->cpool, b->cpool_count, b->has_debug ? b->debug.source : NULL, - b->has_debug ? b->debug.line_num : -1, NULL, b); + NULL, b); #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32) if (b->has_debug) - dump_pc2line(ctx, b->debug.pc2line_buf, b->debug.pc2line_len, b->debug.line_num); + dump_pc2line(ctx, b->debug.pc2line_buf, b->debug.pc2line_len); #endif printf("\n"); } @@ -31786,40 +31946,58 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s) return -1; } -/* the pc2line table gives a line number for each PC value */ -static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, int line_num) +/* the pc2line table gives a source position for each PC value */ +static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, uint32_t source_pos) { if (s->line_number_slots != NULL && s->line_number_count < s->line_number_size && pc >= s->line_number_last_pc - && line_num != s->line_number_last) { + && source_pos != s->line_number_last) { s->line_number_slots[s->line_number_count].pc = pc; - s->line_number_slots[s->line_number_count].line_num = line_num; + s->line_number_slots[s->line_number_count].source_pos = source_pos; s->line_number_count++; s->line_number_last_pc = pc; - s->line_number_last = line_num; + s->line_number_last = source_pos; } } +/* XXX: could use a more compact storage */ +/* XXX: get_line_col_cached() is slow. For more predictable + performance, line/cols could be stored every N source + bytes. Alternatively, get_line_col_cached() could be issued in + emit_source_pos() so that the deltas are more likely to be + small. */ static void compute_pc2line_info(JSFunctionDef *s) { - if (!s->strip_debug && s->line_number_slots) { - int last_line_num = s->line_num; + if (!s->strip_debug) { + int last_line_num, last_col_num; uint32_t last_pc = 0; - int i; - + int i, line_num, col_num; + const uint8_t *buf_start = s->get_line_col_cache->buf_start; js_dbuf_init(s->ctx, &s->pc2line); + + last_line_num = get_line_col_cached(s->get_line_col_cache, + &last_col_num, + buf_start + s->source_pos); + dbuf_put_leb128(&s->pc2line, last_line_num); /* line number minus 1 */ + dbuf_put_leb128(&s->pc2line, last_col_num); /* column number minus 1 */ + for (i = 0; i < s->line_number_count; i++) { uint32_t pc = s->line_number_slots[i].pc; - int line_num = s->line_number_slots[i].line_num; - int diff_pc, diff_line; + uint32_t source_pos = s->line_number_slots[i].source_pos; + int diff_pc, diff_line, diff_col; - if (line_num < 0) + if (source_pos == -1) + continue; + diff_pc = pc - last_pc; + if (diff_pc < 0) continue; - diff_pc = pc - last_pc; + line_num = get_line_col_cached(s->get_line_col_cache, &col_num, + buf_start + source_pos); diff_line = line_num - last_line_num; - if (diff_line == 0 || diff_pc < 0) + diff_col = col_num - last_col_num; + if (diff_line == 0 && diff_col == 0) continue; if (diff_line >= PC2LINE_BASE && @@ -31833,8 +32011,11 @@ static void compute_pc2line_info(JSFunctionDef *s) dbuf_put_leb128(&s->pc2line, diff_pc); dbuf_put_sleb128(&s->pc2line, diff_line); } + dbuf_put_sleb128(&s->pc2line, diff_col); + last_pc = pc; last_line_num = line_num; + last_col_num = col_num; } } } @@ -32030,7 +32211,7 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) label_slots = s->label_slots; - line_num = s->line_num; + line_num = s->source_pos; cc.bc_buf = bc_buf = s->byte_code.buf; cc.bc_len = bc_len = s->byte_code.size; @@ -32048,7 +32229,7 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) s->line_number_slots = js_mallocz(s->ctx, sizeof(*s->line_number_slots) * s->line_number_size); if (s->line_number_slots == NULL) return -1; - s->line_number_last = s->line_num; + s->line_number_last = s->source_pos; s->line_number_last_pc = 0; } @@ -32197,7 +32378,7 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) { /* jump to return/throw: remove jump, append return/throw */ /* updating the line number obfuscates assembly listing */ - //if (line1 >= 0) line_num = line1; + //if (line1 != -1) line_num = line1; update_label(s, label, -1); add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, op1); @@ -32245,7 +32426,7 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) int pos1 = cc.pos; int line1 = cc.line_num; if (code_has_label(&cc, pos1, label)) { - if (line1 >= 0) line_num = line1; + if (line1 != -1) line_num = line1; pos_next = pos1; update_label(s, label, -1); label = cc.label; @@ -33278,7 +33459,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, - fd->cpool, fd->cpool_count, fd->source, fd->line_num, + fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); printf("\n"); } @@ -33293,7 +33474,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, - fd->cpool, fd->cpool_count, fd->source, fd->line_num, + fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); printf("\n"); } @@ -33377,7 +33558,6 @@ 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; //DynBuf pc2line; //compute_pc2line_info(fd, &pc2line); @@ -33620,7 +33800,8 @@ 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, s->buf_start, + &s->get_line_col_cache); if (!fd) return NULL; fd->func_name = JS_ATOM_NULL; @@ -33647,7 +33828,6 @@ static __exception int js_parse_function_decl2(JSParseState *s, JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, - int function_line_num, JSParseExportEnum export_flag, JSFunctionDef **pfd) { @@ -33762,7 +33942,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, ptr, + &s->get_line_col_cache); if (!fd) { JS_FreeAtom(ctx, func_name); return -1; @@ -34211,12 +34392,10 @@ static __exception int js_parse_function_decl(JSParseState *s, JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, JSAtom func_name, - const uint8_t *ptr, - int function_line_num) + const uint8_t *ptr) { return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr, - function_line_num, JS_PARSE_EXPORT_NONE, - NULL); + JS_PARSE_EXPORT_NONE, NULL); } static __exception int js_parse_program(JSParseState *s) @@ -34278,11 +34457,15 @@ static void js_parse_init(JSContext *ctx, JSParseState *s, memset(s, 0, sizeof(*s)); s->ctx = ctx; s->filename = filename; - s->line_num = 1; s->buf_start = 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.ptr = s->buf_ptr; + + s->get_line_col_cache.ptr = s->buf_start; + s->get_line_col_cache.buf_start = s->buf_start; + s->get_line_col_cache.line_num = 0; + s->get_line_col_cache.col_num = 0; } static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, @@ -34368,7 +34551,8 @@ 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, + s->buf_start, &s->get_line_col_cache); if (!fd) goto fail1; s->cur_func = fd; @@ -35031,7 +35215,6 @@ static int JS_WriteFunctionTag(BCWriterState *s, JSValueConst obj) if (b->has_debug) { bc_put_atom(s, b->debug.filename); - 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); if (b->debug.source) { @@ -35992,10 +36175,8 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) bc_read_trace(s, "debug {\n"); if (bc_get_atom(s, &b->debug.filename)) goto fail; - if (bc_get_leb128_int(s, &b->debug.line_num)) - goto fail; #ifdef DUMP_READ_OBJECT - bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf(" line: %d\n", b->debug.line_num); + bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); #endif if (bc_get_leb128_int(s, &b->debug.pc2line_len)) goto fail; @@ -38465,7 +38646,8 @@ static const JSCFunctionListEntry js_function_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_function_toString ), 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_MAGIC_DEF("lineNumber", js_function_proto_lineNumber, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("columnNumber", js_function_proto_lineNumber, NULL, 1 ), }; /* Error class */ @@ -38575,7 +38757,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/tests/test_builtin.js b/tests/test_builtin.js index 38b2640..2fd3c41 100644 --- a/tests/test_builtin.js +++ b/tests/test_builtin.js @@ -508,6 +508,42 @@ function test_typed_array() assert(a.toString(), "1,2,10,11"); } +function check_error_pos(e, expected_error, line_num, col_num) +{ + var expected_pos; + expected_pos = ":" + line_num + ":" + col_num; + if (expected_error === SyntaxError) + expected_pos += "\n"; + else + expected_pos += ")"; + if (e.stack.indexOf(expected_pos) < 0) { + throw_error("unexpected line or column number. error=" + e.message + + ".got |" + e.stack + + "|, expected |" + expected_pos + "|"); + } +} + +function assert_json_error(str, line_num, col_num) +{ + var err = false; + var expected_pos; + + try { + JSON.parse(str); + } catch(e) { + err = true; + if (!(e instanceof SyntaxError)) { + throw_error("unexpected exception type"); + return; + } + /* XXX: the way quickjs returns JSON errors is not similar to Node or spiderMonkey */ + check_error_pos(e, SyntaxError, line_num, col_num); + } + if (!err) { + throw_error("expected exception"); + } +} + function test_json() { var a, s; @@ -531,6 +567,9 @@ function test_json() 3 ] ]`); + + assert_json_error('\n" \\x"', 2, 4); + assert_json_error('\n{ "a": x }"', 2, 8); } function test_date() @@ -965,6 +1004,55 @@ function test_rope() rope_concat(100000, -1); } + +function eval_error(eval_str, expected_error, line_num, col_num) +{ + var err = false; + var expected_pos; + + try { + eval(eval_str); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + throw_error("unexpected exception type"); + return; + } + check_error_pos(e, expected_error, line_num, col_num); + } + if (!err) { + throw_error("expected exception"); + } +} + +function test_line_column_numbers() +{ + var f, e; + + /* parsing */ + eval_error("\n 123 a ", SyntaxError, 2, 6); + eval_error("\n /* ", SyntaxError, 2, 3); + eval_error("function f a", SyntaxError, 1, 13); + /* currently regexp syntax errors point to the start of the regexp */ + eval_error("\n /aaa]/u", SyntaxError, 2, 3); + + /* function definitions */ + + e = eval("\n function f() { }; f;"); + assert(e.lineNumber, 2); + assert(e.columnNumber, 4); + + /* errors */ + e = eval('\n Error("hello");'); + check_error_pos(e, Error, 2, 8); + eval_error('\n throw Error("hello");', Error, 2, 14); + eval_error('\n 2 * Symbol();', TypeError, 2, 5); + eval_error('\n "café" * Symbol();', TypeError, 2, 10); + eval_error('\n null[0];', TypeError, 2, 6); + eval_error('\n null . abcd;', TypeError, 2, 7); + eval_error('\n null ( 1234 );', TypeError, 2, 7); +} + test(); test_function(); test_enum(); @@ -985,3 +1073,4 @@ test_weak_ref(); test_finalization_registry(); test_generator(); test_rope(); +test_line_column_numbers();