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
This commit is contained in:
Charlie Gordon 2024-05-14 20:36:10 +02:00 committed by GitHub
parent 99c6719b7d
commit 5a7e578482
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 72 additions and 14 deletions

View file

@ -6635,7 +6635,7 @@ static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num,
const char *fmt, va_list ap, BOOL add_backtrace) const char *fmt, va_list ap, BOOL add_backtrace)
{ {
char buf[256]; char buf[256];
JSValue obj, ret; JSValue obj, ret, msg;
vsnprintf(buf, sizeof(buf), fmt, ap); vsnprintf(buf, sizeof(buf), fmt, ap);
obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num],
@ -6644,10 +6644,14 @@ static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num,
/* out of memory: throw JS_NULL to avoid recursing */ /* out of memory: throw JS_NULL to avoid recursing */
obj = JS_NULL; obj = JS_NULL;
} else { } else {
JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg = JS_NewString(ctx, buf);
JS_NewString(ctx, buf), if (JS_IsException(msg))
msg = JS_NewString(ctx, "Invalid error message");
if (!JS_IsException(msg)) {
JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg,
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
} }
}
if (add_backtrace) { if (add_backtrace) {
build_backtrace(ctx, obj, NULL, 0, 0, 0); build_backtrace(ctx, obj, NULL, 0, 0, 0);
} }
@ -18633,11 +18637,33 @@ int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const
static int js_parse_expect(JSParseState *s, int tok) static int js_parse_expect(JSParseState *s, int tok)
{ {
if (s->token.val != tok) { char buf[ATOM_GET_STR_BUF_SIZE];
/* XXX: dump token correctly in all cases */
return js_parse_error(s, "expecting '%c'", tok); if (s->token.val == tok)
}
return next_token(s); return next_token(s);
switch(s->token.val) {
case TOK_EOF:
return js_parse_error(s, "Unexpected end of input");
case TOK_NUMBER:
return js_parse_error(s, "Unexpected number");
case TOK_STRING:
return js_parse_error(s, "Unexpected string");
case TOK_TEMPLATE:
return js_parse_error(s, "Unexpected string template");
case TOK_REGEXP:
return js_parse_error(s, "Unexpected regexp");
case TOK_IDENT:
return js_parse_error(s, "Unexpected identifier '%s'",
JS_AtomGetStr(s->ctx, buf, sizeof(buf),
s->token.u.ident.atom));
case TOK_ERROR:
return js_parse_error(s, "Invalid or unexpected token");
default:
return js_parse_error(s, "Unexpected token '%.*s'",
(int)(s->buf_ptr - s->token.ptr),
(const char *)s->token.ptr);
}
} }
static int js_parse_expect_semi(JSParseState *s) static int js_parse_expect_semi(JSParseState *s)

View file

@ -1299,12 +1299,16 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
const char *msg; const char *msg;
msg = JS_ToCString(ctx, exception_val); msg = JS_ToCString(ctx, exception_val);
if (msg == NULL) {
ret = -1;
} else {
error_class = strdup_len(msg, strcspn(msg, ":")); error_class = strdup_len(msg, strcspn(msg, ":"));
if (!str_equal(error_class, error_type)) if (!str_equal(error_class, error_type))
ret = -1; ret = -1;
free(error_class); free(error_class);
JS_FreeCString(ctx, msg); JS_FreeCString(ctx, msg);
} }
}
} else { } else {
ret = -1; ret = -1;
} }

View file

@ -20,7 +20,14 @@ function assert_throws(expected_error, func, message)
var err = false; var err = false;
var msg = message ? " (" + message + ")" : ""; var msg = message ? " (" + message + ")" : "";
try { try {
switch (typeof func) {
case 'string':
eval(func);
break;
case 'function':
func(); func();
break;
}
} catch(e) { } catch(e) {
err = true; err = true;
if (!(e instanceof expected_error)) { if (!(e instanceof expected_error)) {
@ -546,7 +553,7 @@ function test_function_expr_name()
function test_expr(expr, err) { function test_expr(expr, err) {
if (err) if (err)
assert_throws(err, () => eval(expr), `for ${expr}`); assert_throws(err, expr, `for ${expr}`);
else else
assert(1, eval(expr), `for ${expr}`); assert(1, eval(expr), `for ${expr}`);
} }
@ -608,6 +615,26 @@ function test_number_literals()
test_expr('0.a', SyntaxError); test_expr('0.a', SyntaxError);
} }
function test_syntax()
{
assert_throws(SyntaxError, "do");
assert_throws(SyntaxError, "do;");
assert_throws(SyntaxError, "do{}");
assert_throws(SyntaxError, "if");
assert_throws(SyntaxError, "if\n");
assert_throws(SyntaxError, "if 1");
assert_throws(SyntaxError, "if \0");
assert_throws(SyntaxError, "if ;");
assert_throws(SyntaxError, "if 'abc'");
assert_throws(SyntaxError, "if `abc`");
assert_throws(SyntaxError, "if /abc/");
assert_throws(SyntaxError, "if abc");
assert_throws(SyntaxError, "if abc\u0064");
assert_throws(SyntaxError, "if abc\\u0064");
assert_throws(SyntaxError, "if \u0123");
assert_throws(SyntaxError, "if \\u0123");
}
test_op1(); test_op1();
test_cvt(); test_cvt();
test_eq(); test_eq();
@ -629,3 +656,4 @@ test_argument_scope();
test_function_expr_name(); test_function_expr_name();
test_reserved_names(); test_reserved_names();
test_number_literals(); test_number_literals();
test_syntax();