Skip to content

Preserve runtime errors over the statement-terminator check#79

Open
Bascy wants to merge 1 commit into
cesanta:masterfrom
I-Connect:fix/preserve-runtime-error-over-semicolon-check
Open

Preserve runtime errors over the statement-terminator check#79
Bascy wants to merge 1 commit into
cesanta:masterfrom
I-Connect:fix/preserve-runtime-error-over-semicolon-check

Conversation

@Bascy

@Bascy Bascy commented May 28, 2026

Copy link
Copy Markdown

Summary

When a native (C) function returns an error mid-expression (e.g. myCFunc(badArg) * 60;), the engine currently reports "; expected" instead of the original error message.

Reproducing

static jsval_t bad(struct js *js, jsval_t *args, int nargs) {
  (void) args; (void) nargs;
  return js_mkerr(js, "boom");
}
...
js_set(js, js_glob(js), "bad", js_mkfun(bad));
js_eval(js, "bad() * 2;", ~0UL);
// Expected: "ERROR: boom"
// Actual:   "ERROR: ; expected"

Root cause

  1. The native function calls js_mkerr(...) which sets js->tok = TOK_EOF and js->pos = js->clen to short-circuit further parsing.
  2. do_call_op restores the parser state after the call returns (so the parser is sitting on the * token after )).
  3. RTL_BINOP / LTR_BINOP short-circuit on is_err(res) and do not consume the trailing operator.
  4. js_stmt's terminator check
    if (next(js) != TOK_SEMICOLON && next(js) != TOK_EOF && next(js) != TOK_RBRACE)
      return js_mkerr(js, "; expected");
    does not gate on is_err(res), sees the unconsumed *, and overwrites the real error (errmsg is a single shared buffer).

Fix

Short-circuit in js_stmt when res is already an error. One-line change in elk.c; existing terminator behavior for non-error statements is unchanged.

Test plan

  • Added two unit tests in test_c_funcs covering C-function errors embedded in larger expressions (gt() * 2, 1 + gt(null, 1)).
  • Existing tests for "; expected" (e.g. "1 2", "1 + 2 3", "{1}", function(){1}) still pass — they exercise expression-level non-error results followed by stray tokens, which is unaffected by this patch.
  • CI run against full test matrix (gcc, g++, MSVC, MinGW).

Previously, when a runtime error was returned from a native C function in
the middle of an expression (e.g. `myCFunc(badArg) * 60;`), the engine
would clobber the original error message with a misleading
"; expected" parse error.

Root cause:

1. The native function calls `js_mkerr(...)` which sets
   `js->tok = TOK_EOF` and `js->pos = js->clen` to short-circuit
   further parsing.
2. `do_call_op` restores the parser state after the call returns
   (so the parser is sitting on the `*` token after the function call).
3. The `RTL_BINOP` / `LTR_BINOP` loops short-circuit on `is_err(res)`
   and do NOT consume the trailing `*` operator.
4. `js_stmt` then reaches its terminator check
   `next(js) != TOK_SEMICOLON && next(js) != TOK_EOF && next(js) != TOK_RBRACE`
   without gating on `is_err(res)`, sees the unconsumed `*`, and
   overwrites the real error with "; expected" (errmsg is a single
   shared buffer).

Fix: short-circuit in `js_stmt` when `res` is already an error, so the
original error message is preserved.

Tests added to cover this case for C-function-returned errors embedded
in larger expressions.
@Bascy Bascy force-pushed the fix/preserve-runtime-error-over-semicolon-check branch from c514068 to 3d729ea Compare May 28, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant