The stack frame of a function that calls import() does not point at the import() call. It points at whatever source location the function recorded last before the call: an earlier call on a previous line, the start of the enclosing expression statement, or the function itself. At the top level of a module that last case gives 1:1.
I reproduced it with the qjs binary built from the v0.17.0 release tarball. dep.js does not need to exist in any of the examples.
The most direct case is an error thrown by code that import() runs synchronously. Here it is a getter on the options object:
repro-getter.js:
function main() {
console.log("loading");
return import("./dep.js", { get with() { throw new Error("boom"); } });
}
main().catch(e => console.log(e.message + "\n" + e.stack));
$ qjs repro-getter.js
loading
boom
at get with (repro-getter.js:3:56)
at main (repro-getter.js:2:13)
at <anonymous> (repro-getter.js:6:1)
The frame for main points at log on line 2. The import() call is at 3:12.
The same location shows up when import() rejects without running any user code, for example because the options argument is invalid. The TypeError has no stack yet. It gets one when the caller's await rethrows it, and the await right after the import() call maps to the same place:
repro.js:
async function main() {
console.log("loading");
const mod = await import("./dep.js", { with: 1 });
}
main().catch(e => console.log(e.message + "\n" + e.stack));
$ qjs repro.js
loading
options.with must be an object
at main (repro.js:2:13)
The call is at 3:23.
repro-toplevel.mjs:
// top-level await in a module
const mod = await import("./dep.js", { with: 1 });
$ qjs repro-toplevel.mjs
TypeError: options.with must be an object
at <anonymous> (repro-toplevel.mjs:1:1)
Line 1 is the comment. The call is at 2:19.
You cannot see this with a missing module and the stock qjs loader. That error is created in a job with no JS frames, so it gets an empty stack. An embedder that leaves the stack unset in that case, so that the await fills it in, gets the same wrong location.
The cause is in js_parse_postfix_expr. The TOK_IMPORT case emits OP_import with no OP_source_loc before it:
https://github.com/quickjs-ng/quickjs/blob/19dbe8524c1a7357d268cb586ee315616984fef8/quickjs.c#L27320-L27362
Most other operations that can throw record a location, such as calls, new, identifier references, binary operators, throw and iterator close. Expression statements record where they start. So the pc of the import() call, and of an await directly after it, resolves to the previous entry in the pc2line table. The fix is to save the position of the import keyword and emit it right before OP_import, the same way #1473 did for the for-of iterator close. With that change the examples above report 3:12, 3:23 and 2:19. I have a patch with a regression test and can open a PR.
I reproduced this on v0.17.0. The parser code involved is unchanged on master at 19dbe85. This change does not help errors that are created later in a job and never have JS frames, for example from an asynchronous loader. That is a separate problem, related to the async stack trace work in #1248.
The stack frame of a function that calls import() does not point at the import() call. It points at whatever source location the function recorded last before the call: an earlier call on a previous line, the start of the enclosing expression statement, or the function itself. At the top level of a module that last case gives 1:1.
I reproduced it with the qjs binary built from the v0.17.0 release tarball. dep.js does not need to exist in any of the examples.
The most direct case is an error thrown by code that import() runs synchronously. Here it is a getter on the options object:
repro-getter.js:
The frame for main points at
logon line 2. The import() call is at 3:12.The same location shows up when import() rejects without running any user code, for example because the options argument is invalid. The TypeError has no stack yet. It gets one when the caller's
awaitrethrows it, and theawaitright after the import() call maps to the same place:repro.js:
The call is at 3:23.
repro-toplevel.mjs:
Line 1 is the comment. The call is at 2:19.
You cannot see this with a missing module and the stock qjs loader. That error is created in a job with no JS frames, so it gets an empty stack. An embedder that leaves the stack unset in that case, so that the await fills it in, gets the same wrong location.
The cause is in
js_parse_postfix_expr. TheTOK_IMPORTcase emitsOP_importwith noOP_source_locbefore it:https://github.com/quickjs-ng/quickjs/blob/19dbe8524c1a7357d268cb586ee315616984fef8/quickjs.c#L27320-L27362
Most other operations that can throw record a location, such as calls,
new, identifier references, binary operators,throwand iterator close. Expression statements record where they start. So the pc of the import() call, and of anawaitdirectly after it, resolves to the previous entry in the pc2line table. The fix is to save the position of theimportkeyword and emit it right beforeOP_import, the same way #1473 did for the for-of iterator close. With that change the examples above report 3:12, 3:23 and 2:19. I have a patch with a regression test and can open a PR.I reproduced this on v0.17.0. The parser code involved is unchanged on master at 19dbe85. This change does not help errors that are created later in a job and never have JS frames, for example from an asynchronous loader. That is a separate problem, related to the async stack trace work in #1248.