Skip to content

Commit 5a7e578

Browse files
authored
Improve parsing error messages (#405)
- output more informative error messages in `js_parse_expect`. The previous code was bogus: ``` return js_parse_error(s, "expecting '%c'", tok); ``` this was causing a bug on `eval("do;")` where `tok` is `TOK_WHILE` (-70, 0xBA) creating an invalid UTF-8 encoding (lone trailing byte). This would ultimately have caused a failure in `JS_ThrowError2` if `JS_NewString` failed when converting the error message to a string if the conversion detected the invalid UTF-8 encoding and throwed an error (it currently does not, but should). - test for `JS_NewString` failure in `JS_ThrowError2` - test for `JS_FreeCString` failure in run-test262.c - add more test cases
1 parent 99c6719 commit 5a7e578

File tree

3 files changed

+72
-14
lines changed

3 files changed

+72
-14
lines changed

quickjs.c

+34-8
Original file line numberDiff line numberDiff line change
@@ -6635,7 +6635,7 @@ static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num,
66356635
const char *fmt, va_list ap, BOOL add_backtrace)
66366636
{
66376637
char buf[256];
6638-
JSValue obj, ret;
6638+
JSValue obj, ret, msg;
66396639

66406640
vsnprintf(buf, sizeof(buf), fmt, ap);
66416641
obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num],
@@ -6644,9 +6644,13 @@ static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num,
66446644
/* out of memory: throw JS_NULL to avoid recursing */
66456645
obj = JS_NULL;
66466646
} else {
6647-
JS_DefinePropertyValue(ctx, obj, JS_ATOM_message,
6648-
JS_NewString(ctx, buf),
6649-
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
6647+
msg = JS_NewString(ctx, buf);
6648+
if (JS_IsException(msg))
6649+
msg = JS_NewString(ctx, "Invalid error message");
6650+
if (!JS_IsException(msg)) {
6651+
JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg,
6652+
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
6653+
}
66506654
}
66516655
if (add_backtrace) {
66526656
build_backtrace(ctx, obj, NULL, 0, 0, 0);
@@ -18633,11 +18637,33 @@ int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const
1863318637

1863418638
static int js_parse_expect(JSParseState *s, int tok)
1863518639
{
18636-
if (s->token.val != tok) {
18637-
/* XXX: dump token correctly in all cases */
18638-
return js_parse_error(s, "expecting '%c'", tok);
18640+
char buf[ATOM_GET_STR_BUF_SIZE];
18641+
18642+
if (s->token.val == tok)
18643+
return next_token(s);
18644+
18645+
switch(s->token.val) {
18646+
case TOK_EOF:
18647+
return js_parse_error(s, "Unexpected end of input");
18648+
case TOK_NUMBER:
18649+
return js_parse_error(s, "Unexpected number");
18650+
case TOK_STRING:
18651+
return js_parse_error(s, "Unexpected string");
18652+
case TOK_TEMPLATE:
18653+
return js_parse_error(s, "Unexpected string template");
18654+
case TOK_REGEXP:
18655+
return js_parse_error(s, "Unexpected regexp");
18656+
case TOK_IDENT:
18657+
return js_parse_error(s, "Unexpected identifier '%s'",
18658+
JS_AtomGetStr(s->ctx, buf, sizeof(buf),
18659+
s->token.u.ident.atom));
18660+
case TOK_ERROR:
18661+
return js_parse_error(s, "Invalid or unexpected token");
18662+
default:
18663+
return js_parse_error(s, "Unexpected token '%.*s'",
18664+
(int)(s->buf_ptr - s->token.ptr),
18665+
(const char *)s->token.ptr);
1863918666
}
18640-
return next_token(s);
1864118667
}
1864218668

1864318669
static int js_parse_expect_semi(JSParseState *s)

run-test262.c

+8-4
Original file line numberDiff line numberDiff line change
@@ -1299,11 +1299,15 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
12991299
const char *msg;
13001300

13011301
msg = JS_ToCString(ctx, exception_val);
1302-
error_class = strdup_len(msg, strcspn(msg, ":"));
1303-
if (!str_equal(error_class, error_type))
1302+
if (msg == NULL) {
13041303
ret = -1;
1305-
free(error_class);
1306-
JS_FreeCString(ctx, msg);
1304+
} else {
1305+
error_class = strdup_len(msg, strcspn(msg, ":"));
1306+
if (!str_equal(error_class, error_type))
1307+
ret = -1;
1308+
free(error_class);
1309+
JS_FreeCString(ctx, msg);
1310+
}
13071311
}
13081312
} else {
13091313
ret = -1;

tests/test_language.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ function assert_throws(expected_error, func, message)
2020
var err = false;
2121
var msg = message ? " (" + message + ")" : "";
2222
try {
23-
func();
23+
switch (typeof func) {
24+
case 'string':
25+
eval(func);
26+
break;
27+
case 'function':
28+
func();
29+
break;
30+
}
2431
} catch(e) {
2532
err = true;
2633
if (!(e instanceof expected_error)) {
@@ -546,7 +553,7 @@ function test_function_expr_name()
546553

547554
function test_expr(expr, err) {
548555
if (err)
549-
assert_throws(err, () => eval(expr), `for ${expr}`);
556+
assert_throws(err, expr, `for ${expr}`);
550557
else
551558
assert(1, eval(expr), `for ${expr}`);
552559
}
@@ -608,6 +615,26 @@ function test_number_literals()
608615
test_expr('0.a', SyntaxError);
609616
}
610617

618+
function test_syntax()
619+
{
620+
assert_throws(SyntaxError, "do");
621+
assert_throws(SyntaxError, "do;");
622+
assert_throws(SyntaxError, "do{}");
623+
assert_throws(SyntaxError, "if");
624+
assert_throws(SyntaxError, "if\n");
625+
assert_throws(SyntaxError, "if 1");
626+
assert_throws(SyntaxError, "if \0");
627+
assert_throws(SyntaxError, "if ;");
628+
assert_throws(SyntaxError, "if 'abc'");
629+
assert_throws(SyntaxError, "if `abc`");
630+
assert_throws(SyntaxError, "if /abc/");
631+
assert_throws(SyntaxError, "if abc");
632+
assert_throws(SyntaxError, "if abc\u0064");
633+
assert_throws(SyntaxError, "if abc\\u0064");
634+
assert_throws(SyntaxError, "if \u0123");
635+
assert_throws(SyntaxError, "if \\u0123");
636+
}
637+
611638
test_op1();
612639
test_cvt();
613640
test_eq();
@@ -629,3 +656,4 @@ test_argument_scope();
629656
test_function_expr_name();
630657
test_reserved_names();
631658
test_number_literals();
659+
test_syntax();

0 commit comments

Comments
 (0)