initial commit
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Regression test for issue #22656, #11730, and #7116
|
||||
*
|
||||
* These issues all related to the same root cause:
|
||||
* - Hash table resizing during recursive macro processing invalidated pointers
|
||||
* - This caused panics with "switch on corrupt value" or segmentation faults
|
||||
* - The crash occurred when macros returned collections with 3+ arrays/objects
|
||||
*
|
||||
* The fix:
|
||||
* 1. Seeds a placeholder immediately after getOrPut to prevent uninitialized memory access
|
||||
* 2. Uses `put` after processing to handle hash table resizing during recursion
|
||||
* 3. Removes overly strict circular reference checks that incorrectly triggered on shared refs
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
describe("issue #22656 - macro array/object handling", () => {
|
||||
test("handles collections with 3+ arrays without crashing", async () => {
|
||||
using dir = tempDir("macro-arrays", {
|
||||
"macro.ts": `
|
||||
export function test() {
|
||||
// This pattern caused crashes before the fix
|
||||
return [
|
||||
{ a: [] },
|
||||
{ b: [] },
|
||||
{ c: [] },
|
||||
{ d: [] }, // More than 3 to ensure it's fixed
|
||||
{ e: [] }
|
||||
];
|
||||
}
|
||||
`,
|
||||
"index.ts": `
|
||||
import { test } from './macro.ts' with { type: "macro" };
|
||||
const result = test();
|
||||
if (result.length !== 5) throw new Error("Expected 5 items");
|
||||
console.log("PASS");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("PASS");
|
||||
expect(stderr).not.toContain("panic");
|
||||
expect(stderr).not.toContain("corrupt value");
|
||||
});
|
||||
|
||||
test("handles collections with 3+ objects without crashing", async () => {
|
||||
using dir = tempDir("macro-objects", {
|
||||
"macro.ts": `
|
||||
export function test() {
|
||||
// This pattern also caused crashes
|
||||
return [
|
||||
{ a: {} },
|
||||
{ b: {} },
|
||||
{ c: {} },
|
||||
{ d: {} },
|
||||
{ e: {} }
|
||||
];
|
||||
}
|
||||
`,
|
||||
"index.ts": `
|
||||
import { test } from './macro.ts' with { type: "macro" };
|
||||
const result = test();
|
||||
if (result.length !== 5) throw new Error("Expected 5 items");
|
||||
console.log("PASS");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("PASS");
|
||||
});
|
||||
|
||||
test("handles deeply nested objects (issue #11730)", async () => {
|
||||
using dir = tempDir("macro-deep", {
|
||||
"macro.ts": `
|
||||
export function test() {
|
||||
const complex = {
|
||||
type: 'root',
|
||||
children: Array.from({ length: 10 }, (_, i) => ({
|
||||
id: i,
|
||||
nested: {
|
||||
arrays: [[], [], []],
|
||||
objects: [{}, {}, {}],
|
||||
deep: { value: i }
|
||||
}
|
||||
})),
|
||||
meta: Array.from({ length: 20 }, (_, i) => ({
|
||||
key: 'key_' + i,
|
||||
value: { nested: { deeply: { value: i } } }
|
||||
}))
|
||||
};
|
||||
// JSON parse/stringify pattern that was common in the bug reports
|
||||
const makeObject = () => JSON.parse(JSON.stringify(complex));
|
||||
return [makeObject(), makeObject(), makeObject(), makeObject(), makeObject()];
|
||||
}
|
||||
`,
|
||||
"index.ts": `
|
||||
import { test } from './macro.ts' with { type: "macro" };
|
||||
const result = test();
|
||||
if (result.length !== 5) throw new Error("Expected 5 items");
|
||||
if (result[0].type !== 'root') throw new Error("Expected root type");
|
||||
if (result[0].children.length !== 10) throw new Error("Expected 10 children");
|
||||
console.log("PASS");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("PASS");
|
||||
});
|
||||
|
||||
test("handles large arrays with spreading (issue #7116)", async () => {
|
||||
using dir = tempDir("macro-spread", {
|
||||
"macro.ts": `
|
||||
export function test() {
|
||||
const baseArray = Array.from({ length: 10 }, (_, i) => ({
|
||||
name: 'item_' + i,
|
||||
data: {
|
||||
arrays: [[], [], []],
|
||||
objects: [{}, {}, {}]
|
||||
},
|
||||
extra: {
|
||||
values: Array.from({ length: 5 }, (_, j) => ({ x: j }))
|
||||
}
|
||||
}));
|
||||
|
||||
const transform = () => baseArray.map(x => ({
|
||||
...x,
|
||||
additional: {
|
||||
arrays: [{ a: [] }, { b: [] }, { c: [] }, { d: [] }],
|
||||
objects: [{ w: {} }, { x: {} }, { y: {} }, { z: {} }]
|
||||
}
|
||||
}));
|
||||
|
||||
// Spreading pattern that triggered the bug
|
||||
return [...transform(), ...transform(), ...transform()];
|
||||
}
|
||||
`,
|
||||
"index.ts": `
|
||||
import { test } from './macro.ts' with { type: "macro" };
|
||||
const result = test();
|
||||
if (result.length !== 30) throw new Error("Expected 30 items");
|
||||
if (!result[0].additional) throw new Error("Expected additional property");
|
||||
if (!result[0].additional.arrays) throw new Error("Expected arrays");
|
||||
console.log("PASS");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("PASS");
|
||||
});
|
||||
|
||||
test("correctly handles shared references vs circular references", async () => {
|
||||
using dir = tempDir("macro-circular", {
|
||||
"macro.ts": `
|
||||
export function sharedRefs() {
|
||||
// Shared references should work fine
|
||||
const obj = { data: [] };
|
||||
return [obj, obj, obj, obj, obj]; // 5 references to same object
|
||||
}
|
||||
|
||||
export function nestedShared() {
|
||||
// Nested shared references
|
||||
const inner = { x: {} };
|
||||
const outer = { a: inner, b: inner, c: inner };
|
||||
return [outer, outer, outer];
|
||||
}
|
||||
`,
|
||||
"index.ts": `
|
||||
import { sharedRefs, nestedShared } from './macro.ts' with { type: "macro" };
|
||||
|
||||
const shared = sharedRefs();
|
||||
if (shared.length !== 5) throw new Error("Expected 5 shared refs");
|
||||
|
||||
const nested = nestedShared();
|
||||
if (nested.length !== 3) throw new Error("Expected 3 nested");
|
||||
|
||||
console.log("PASS");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("PASS");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user