diff --git a/CHANGELOG.md b/CHANGELOG.md index d9d564722c50..29d1cea001b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Align WebKit and VM source-position assertions with Node's constructor locations and select these regressions for native stack-formatting changes in both fork CI lanes. + - Preserve the native-context count in the fast `v8.getHeapStatistics()` adapter and avoid appending a second strict code-generation flag to inherited worker arguments. - Return one-based CallSite columns and null native positions, preserve column 1 in stack strings, and align constructor locations across stack formats and line breaks. Adapts [oven-sh/bun#35179](https://github.com/oven-sh/bun/pull/35179) and [oven-sh/bun#37396](https://github.com/oven-sh/bun/pull/37396). Thanks @robobun! diff --git a/docs/runtime/nodejs-compat.mdx b/docs/runtime/nodejs-compat.mdx index 06c6b1489e12..0274c5b4fcc5 100644 --- a/docs/runtime/nodejs-compat.mdx +++ b/docs/runtime/nodejs-compat.mdx @@ -19,7 +19,7 @@ The OpenClaw fork reports missing runtime imports without downloading npm packag `Error.prepareStackTrace` supports deletion, descriptor restoration, and accessor replacement. Nested stack formatting and throwing formatters leave later captures usable. -`CallSite.getLineNumber()` and `getColumnNumber()` return one-based positions, or `null` when a frame has no source position. Stack strings include column 1 and use the same constructor positions as CallSites, including across line breaks. Some expression positions still differ from Node.js because JavaScriptCore and Bun's transpiler select different syntax locations. +`CallSite.getLineNumber()` and `getColumnNumber()` return one-based positions, or `null` when a frame has no source position. Stack strings include column 1 and use the same constructor positions as CallSites, pointing to the `new` keyword, including across line breaks and in `node:vm` scripts. Some expression positions still differ from Node.js because JavaScriptCore and Bun's transpiler select different syntax locations. ### [`node:assert`](https://nodejs.org/api/assert.html) diff --git a/scripts/openclaw-ci/tests.ts b/scripts/openclaw-ci/tests.ts index 4f34ad796b82..11141eb9ba4b 100644 --- a/scripts/openclaw-ci/tests.ts +++ b/scripts/openclaw-ci/tests.ts @@ -68,6 +68,11 @@ const webkitSensitive = [ "test/js/node/module/node-module-module.test.js", ]; +const sourcePositionSensitive = [ + "test/js/bun/jsc/webkit-upgrade-7b485a76e9.test.ts", + "test/js/node/vm/vm-sourceUrl.test.ts", +]; + // Runtime implementations span JS, Rust and C++; keep their shared boundaries explicit. const sourceSuites: [RegExp, string[]][] = [ [/child_process|subprocess|spawn/i, ["child_process"]], @@ -103,6 +108,13 @@ export function selectTests(changed: string[], tracked: string[], nightly: boole } } } else if (path.startsWith("src/")) { + if ( + /^src\/jsc\/bindings\/(?:CallSite\w*|ErrorStack\w*|FormatStackTraceForJS|ZigSourceProvider)\.(?:cpp|h)$/.test( + path, + ) + ) { + sourcePositionSensitive.forEach(test => selected.add(test)); + } for (const [pattern, suites] of sourceSuites) { if (!pattern.test(path)) continue; for (const test of [...smoke, ...broader]) { diff --git a/test/internal/source-lints/openclaw-ci.test.ts b/test/internal/source-lints/openclaw-ci.test.ts index a71799dcf04b..7dd4bdb15765 100644 --- a/test/internal/source-lints/openclaw-ci.test.ts +++ b/test/internal/source-lints/openclaw-ci.test.ts @@ -36,6 +36,21 @@ test.each(["linux", "darwin"])("WebKit updates select VM, Intl, GC, hook and plu expect(selected).not.toContain("test/bundler/esbuild/default.test.ts"); }); +test.each(["linux", "darwin"])("stack position changes select WebKit and VM sourceURL coverage on %s", platform => { + for (const source of [ + "CallSitePrototype.cpp", + "ErrorStackFrame.cpp", + "ErrorStackFrame.h", + "ErrorStackTrace.cpp", + "FormatStackTraceForJS.cpp", + "ZigSourceProvider.cpp", + ]) { + const selected = selectTests([`src/jsc/bindings/${source}`], tracked, false, platform); + expect(selected).toContain("test/js/bun/jsc/webkit-upgrade-7b485a76e9.test.ts"); + expect(selected).toContain("test/js/node/vm/vm-sourceUrl.test.ts"); + } +}); + test("changed tests are added exactly once, renamed tests use the new path, deleted tests are omitted", () => { const added = "test/js/node/fs/new check.test.ts"; const deleted = "test/js/node/fs/removed.test.ts"; diff --git a/test/js/bun/jsc/webkit-upgrade-7b485a76e9.test.ts b/test/js/bun/jsc/webkit-upgrade-7b485a76e9.test.ts index 04ba98bf07fc..27805cb4fda9 100644 --- a/test/js/bun/jsc/webkit-upgrade-7b485a76e9.test.ts +++ b/test/js/bun/jsc/webkit-upgrade-7b485a76e9.test.ts @@ -149,7 +149,7 @@ describe("WebKit 7b485a76e9 upgrade", () => { } const frames = [...stack.matchAll(/positions\.js:(\d+):(\d+)/g)].map(m => [Number(m[1]), Number(m[2])]); expect(frames).toEqual([ - [6, 37], + [6, 28], [4, 15], [7, 6], ]); @@ -164,8 +164,8 @@ describe("WebKit 7b485a76e9 upgrade", () => { .at(-1); } }; - expect(offsetFrames("throw new Error('first line')")).toEqual([11, 21]); - expect(offsetFrames("\nthrow new Error('second line')")).toEqual([12, 16]); + expect(offsetFrames("throw new Error('first line')")).toEqual([11, 12]); + expect(offsetFrames("\nthrow new Error('second line')")).toEqual([12, 7]); }); test("an error from a class field initializer is reported where the constructor starts (c76c52f5b1)", () => { @@ -205,7 +205,7 @@ describe("WebKit 7b485a76e9 upgrade", () => { // That is not worth its cost to a short source, which is read if it is asked. const short = (0, eval)("(function () {\n return new Error().stack;\n})"); expect(sourceHasLineStarts(short)).toBe(false); - expect(short()).toContain(":2:19"); + expect(short()).toContain(":2:10"); expect(sourceHasLineStarts(short)).toBe(true); // The builtins of the engine share one text, which is never parsed as a whole and needs no table. expect(sourceHasLineStarts(Array.prototype.map)).toBe(false); @@ -229,7 +229,7 @@ describe("WebKit 7b485a76e9 upgrade", () => { if (length >= probe.length && (i % 3 === 1 || i % 64 <= 1 || i % 64 === 63 || i % 5 === 1)) { const before = random(length - probe.length + 1); text += fill(before, " ") + probe + fill(length - probe.length - before, " ") + end; - marks.push(`("p${i}")`); + marks.push(`new Error("p${i}")`); } else text += (length >= 4 ? "/*" + fill(length - 4, "c") + "*/" : fill(length, " ")) + end; } text += "probes"; diff --git a/test/js/node/vm/__snapshots__/vm-sourceUrl.test.ts.snap b/test/js/node/vm/__snapshots__/vm-sourceUrl.test.ts.snap index 650537102d0a..bbc85154f8ee 100644 --- a/test/js/node/vm/__snapshots__/vm-sourceUrl.test.ts.snap +++ b/test/js/node/vm/__snapshots__/vm-sourceUrl.test.ts.snap @@ -6,7 +6,7 @@ throw new Error("hello"); ^ Error: hello - at hellohello.js:2:16 + at hellohello.js:2:7 at runInNewContext (unknown) at (:6:5)" `;