Compare commits

...

8 Commits

Author SHA1 Message Date
autofix-ci[bot]
17aa91a9c5 [autofix.ci] apply automated fixes 2025-08-30 12:29:12 +00:00
Claude Bot
4964bb665b Fix ResolveMessage empty message property by explicitly setting it after object creation
The issue was that JavaScript's Error constructor was overriding the message
property with an empty string after the ResolveMessage object was created.
This happened because ResolveMessage inherits from Error (inheritsFromError: true)
and the Error constructor sets message = '' by default.

The fix explicitly sets the message property on the JavaScript object after
creation using the formatted error text from msg.data.text, ensuring that
the proper error messages are displayed in tests.

Fixes test failures in:
- test/js/node/missing-module.test.js
- test/js/bun/resolve/resolve-error.test.ts
- test/js/node/module/node-module-module.test.js
- test/js/node/module/require-extensions.test.ts
- test/bundler/bundler_plugin.test.ts
2025-08-30 12:27:44 +00:00
autofix-ci[bot]
265922ca9c [autofix.ci] apply automated fixes 2025-08-30 01:51:31 +00:00
Claude Bot
b63f5950c1 Fix ErrorInstance inheritance for ResolveMessage and other classes
This commit fixes the build failures that occurred when classes inherit from ErrorInstance by properly implementing the custom heap cell type allocation pattern.

Key changes:
1. Fixed symbol property callback generation in generate-classes.ts by allowing symbol properties with `fn` definitions to generate callbacks while skipping cache/getter/setter processing
2. Implemented proper ErrorInstance inheritance with correct constructor parameters
3. Added custom heap cell type support for ErrorInstance subclasses by using UseCustomHeapCellType::Yes when inheritsFromError is true
4. Added m_heapCellTypeForJSResolveMessage to BunClientData and properly initialized it in the constructor
5. Added ZigGeneratedClasses.h include to BunClientData.cpp to access generated class definitions

The ResolveMessage class now properly inherits from ErrorInstance, allowing it to pass instanceof Error checks and be safely JSON.stringify-ed without heap-use-after-free issues.

All regression tests for resolve-message-json-stringify now pass.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-30 01:50:03 +00:00
autofix-ci[bot]
9e5584c817 [autofix.ci] apply automated fixes 2025-08-29 23:27:34 +00:00
Claude Bot
b6e1db6acd Fix bindgen to properly inherit from ErrorInstance for Error.isError() support
Key changes to make ResolveMessage/BuildMessage proper ErrorInstance objects:

1. **Proper inheritance**: Classes with inheritsFromError:true now inherit from
   JSC::ErrorInstance instead of JSC::JSDestructibleObject

2. **Correct subspace allocation**: ErrorInstance classes use built-in error
   subspace instead of generating custom subspace methods

3. **Fixed constructor signatures**: ErrorInstance requires ErrorType::Error
   parameter in constructor

4. **Fixed finishCreation**: ErrorInstance requires message and cause parameters
   for proper initialization

5. **Set ErrorInstanceType**: JSType set to ErrorInstanceType for Error.isError()
   to return true

6. **Symbol property handling**: Fixed cached property generation to skip @@
   symbol properties that cause C++ syntax errors

This makes ResolveMessage objects proper Error instances that pass both
Error.isError() and instanceof Error checks.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 23:25:27 +00:00
autofix-ci[bot]
67787f1686 [autofix.ci] apply automated fixes 2025-08-29 02:27:22 +00:00
Claude Bot
22717f63e9 ResolveMessage should extend Error and allow JSON stringification 2025-08-29 02:24:52 +00:00
7 changed files with 165 additions and 18 deletions

View File

@@ -6,7 +6,7 @@ pub const ResolveMessage = struct {
msg: logger.Msg,
allocator: std.mem.Allocator,
referrer: ?Fs.Path = null,
referrer: bun.String = bun.String.empty,
logged: bool = false,
pub fn constructor(globalThis: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!*ResolveMessage {
@@ -173,9 +173,16 @@ pub const ResolveMessage = struct {
resolve_error.* = ResolveMessage{
.msg = try msg.clone(allocator),
.allocator = allocator,
.referrer = Fs.Path.init(referrer),
.referrer = bun.String.cloneUTF8(referrer),
};
return resolve_error.toJS(globalThis);
const js_value = resolve_error.toJS(globalThis);
// Explicitly set the message property to override any empty message set by Error constructor
const message_str = ZigString.init(resolve_error.msg.data.text);
js_value.put(globalThis, ZigString.static("message"), message_str.toJS(globalThis));
return js_value;
}
pub fn getPosition(
@@ -217,14 +224,15 @@ pub const ResolveMessage = struct {
this: *ResolveMessage,
globalThis: *jsc.JSGlobalObject,
) jsc.JSValue {
if (this.referrer) |referrer| {
return ZigString.init(referrer.text).toJS(globalThis);
} else {
if (this.referrer.isEmpty()) {
return jsc.JSValue.jsNull();
} else {
return this.referrer.toJS(globalThis);
}
}
pub fn finalize(this: *ResolveMessage) callconv(.C) void {
this.referrer.deref();
this.msg.deinit(bun.default_allocator);
}
};
@@ -235,7 +243,6 @@ const Resolver = @import("../resolver//resolver.zig");
const std = @import("std");
const bun = @import("bun");
const Fs = bun.fs;
const default_allocator = bun.default_allocator;
const logger = bun.logger;
const strings = bun.strings;

View File

@@ -25,6 +25,7 @@
#include "NodeVM.h"
#include "../../bake/BakeGlobalObject.h"
#include "napi_handle_scope.h"
#include "ZigGeneratedClasses.h"
namespace WebCore {
using namespace JSC;
@@ -36,6 +37,7 @@ JSHeapData::JSHeapData(Heap& heap)
, m_heapCellTypeForNodeVMGlobalObject(JSC::IsoHeapCellType::Args<Bun::NodeVMGlobalObject>())
, m_heapCellTypeForBakeGlobalObject(JSC::IsoHeapCellType::Args<Bake::GlobalObject>())
, m_heapCellTypeForNapiHandleScopeImpl(JSC::IsoHeapCellType::Args<Bun::NapiHandleScopeImpl>())
, m_heapCellTypeForJSResolveMessage(JSC::IsoHeapCellType::Args<WebCore::JSResolveMessage>())
, m_domBuiltinConstructorSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMBuiltinConstructorBase)
, m_domConstructorSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMConstructorBase)
, m_domNamespaceObjectSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMObject)

View File

@@ -60,6 +60,7 @@ public:
JSC::IsoHeapCellType m_heapCellTypeForNodeVMGlobalObject;
JSC::IsoHeapCellType m_heapCellTypeForNapiHandleScopeImpl;
JSC::IsoHeapCellType m_heapCellTypeForBakeGlobalObject;
JSC::IsoHeapCellType m_heapCellTypeForJSResolveMessage;
private:
Lock m_lock;

View File

@@ -8,6 +8,7 @@ export default [
configurable: false,
klass: {},
JSType: "0b11101110",
inheritsFromError: true,
proto: {
message: {
getter: "getMessage",

View File

@@ -217,6 +217,12 @@ export class ClassDefinition {
callbacks?: Record<string, string>;
/**
* Whether this class should inherit from Error.prototype instead of Object.prototype.
* This makes instances of the class satisfy `instanceof Error`.
*/
inheritsFromError?: boolean;
constructor(options: Partial<ClassDefinition>) {
this.name = options.name ?? "";
this.klass = options.klass ?? {};

View File

@@ -775,12 +775,14 @@ ${
function renderCachedFieldsHeader(typeName, klass, proto, values) {
const rows: string[] = [];
for (const name in klass) {
if (name.startsWith("@@")) continue; // Skip symbol properties
if (("cache" in klass[name] && klass[name].cache === true) || klass[name]?.internal) {
rows.push(`mutable JSC::WriteBarrier<JSC::Unknown> m_${name};`);
}
}
for (const name in proto) {
if (name.startsWith("@@")) continue; // Skip symbol properties
if (proto[name]?.cache === true || klass[name]?.internal) {
rows.push(`mutable JSC::WriteBarrier<JSC::Unknown> m_${name};`);
}
@@ -1066,7 +1068,9 @@ JSC_DEFINE_CUSTOM_GETTER(js${typeName}Constructor, (JSGlobalObject * lexicalGlob
}
for (const name in proto) {
if ("cache" in proto[name] || proto[name]?.internal) {
// Skip symbol properties except for functions that need callbacks generated
if (name.startsWith("@@") && !("fn" in proto[name])) continue;
if (("cache" in proto[name] || proto[name]?.internal) && !name.startsWith("@@")) {
const cacheName = typeof proto[name].cache === "string" ? `m_${proto[name].cache}` : `m_${name}`;
if ("cache" in proto[name]) {
if (!supportsObjectCreate) {
@@ -1138,7 +1142,10 @@ JSC_DEFINE_CUSTOM_GETTER(${symbolName(typeName, name)}GetterWrap, (JSGlobalObjec
}
}
rows.push(writeBarrier(symbolName, typeName, name, cacheName));
} else if ("getter" in proto[name] || ("accessor" in proto[name] && proto[name].getter)) {
} else if (
("getter" in proto[name] || ("accessor" in proto[name] && proto[name].getter)) &&
!name.startsWith("@@")
) {
if (!supportsObjectCreate) {
rows.push(`
JSC_DEFINE_CUSTOM_GETTER(${symbolName(typeName, name)}GetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue encodedThisValue, PropertyName attributeName))
@@ -1177,7 +1184,7 @@ JSC_DEFINE_CUSTOM_GETTER(${symbolName(typeName, name)}GetterWrap, (JSGlobalObjec
}
}
if ("setter" in proto[name] || ("accessor" in proto[name] && proto[name].setter)) {
if (("setter" in proto[name] || ("accessor" in proto[name] && proto[name].setter)) && !name.startsWith("@@")) {
rows.push(
`
JSC_DEFINE_CUSTOM_SETTER(${symbolName(typeName, name)}SetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue encodedThisValue, EncodedJSValue encodedValue, PropertyName attributeName))
@@ -1292,6 +1299,7 @@ JSC_DEFINE_HOST_FUNCTION(${symbolName(typeName, name)}Callback, (JSGlobalObject
function allCachedValues(obj: ClassDefinition) {
let values = (obj.values ?? []).slice().map(name => [name, `m_${name}`]);
for (const name in obj.proto) {
if (name.startsWith("@@")) continue; // Skip symbol properties
let cacheName = obj.proto[name].cache;
if (cacheName === true) {
cacheName = "m_" + name;
@@ -1311,6 +1319,11 @@ var extraIncludes = [];
function generateClassHeader(typeName, obj: ClassDefinition) {
var { klass, proto, JSType = "ObjectType", values = [], callbacks = {}, zigOnly = false } = obj;
// Override JSType for Error inheritance
if (obj.inheritsFromError) {
JSType = "ErrorInstanceType";
}
if (zigOnly) return "";
const name = className(typeName);
@@ -1368,9 +1381,9 @@ function generateClassHeader(typeName, obj: ClassDefinition) {
const final = obj.final ?? true;
return `
class ${name}${final ? " final" : ""} : public JSC::JSDestructibleObject {
class ${name}${final ? " final" : ""} : public ${obj.inheritsFromError ? "JSC::ErrorInstance" : "JSC::JSDestructibleObject"} {
public:
using Base = JSC::JSDestructibleObject;
using Base = ${obj.inheritsFromError ? "JSC::ErrorInstance" : "JSC::JSDestructibleObject"};
static constexpr unsigned StructureFlags = Base::StructureFlags${obj.hasOwnProperties() ? ` | HasStaticPropertyTable` : ""};
static ${name}* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx);
@@ -1379,12 +1392,17 @@ function generateClassHeader(typeName, obj: ClassDefinition) {
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<${name}, WebCore::UseCustomHeapCellType::No>(
return WebCore::subspaceForImpl<${name}, WebCore::UseCustomHeapCellType::${obj.inheritsFromError ? "Yes" : "No"}>(
vm,
[](auto& spaces) { return spaces.${clientSubspaceFor(typeName)}.get(); },
[](auto& spaces, auto&& space) { spaces.${clientSubspaceFor(typeName)} = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.${subspaceFor(typeName)}.get(); },
[](auto& spaces, auto&& space) { spaces.${subspaceFor(typeName)} = std::forward<decltype(space)>(space); });
[](auto& spaces, auto&& space) { spaces.${subspaceFor(typeName)} = std::forward<decltype(space)>(space); }${
obj.inheritsFromError
? `,
[](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeFor${name}; }`
: ""
});
}
static void destroy(JSC::JSCell*);
@@ -1426,7 +1444,7 @@ function generateClassHeader(typeName, obj: ClassDefinition) {
${name}(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
: Base(vm, structure)
: Base(vm, structure${obj.inheritsFromError ? ", JSC::ErrorType::Error" : ""})
{
m_ctx = sinkPtr;
${weakInit.trim()}
@@ -1465,6 +1483,7 @@ function generateClassHeader(typeName, obj: ClassDefinition) {
function domJITTypeCheckFields(proto, klass) {
var output = "#if BUN_DEBUG\n";
for (const name in proto) {
if (name.startsWith("@@")) continue; // Skip symbol properties
const { DOMJIT, fn } = proto[name];
if (!DOMJIT) continue;
output += `std::optional<std::optional<JSC::JSType>> m_${fn}_expectedResultType = std::nullopt;\n`;
@@ -1667,7 +1686,7 @@ const ClassInfo ${name}::s_info = { "${typeName}"_s, &Base::s_info, ${obj.hasOwn
void ${name}::finishCreation(VM& vm)
{
Base::finishCreation(vm);
Base::finishCreation(vm${obj.inheritsFromError ? ", WTF::emptyString(), JSC::jsUndefined()" : ""});
ASSERT(inherits(info()));
}
@@ -1758,7 +1777,7 @@ ${
JSObject* ${name}::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
{
auto *structure = ${prototypeName(typeName)}::createStructure(vm, globalObject, globalObject->objectPrototype());
auto *structure = ${prototypeName(typeName)}::createStructure(vm, globalObject, ${obj.inheritsFromError ? "globalObject->errorPrototype()" : "globalObject->objectPrototype()"});
structure->setMayBePrototype(true);
return ${prototypeName(typeName)}::create(vm, globalObject, structure);
}
@@ -1871,7 +1890,9 @@ function generateZig(
const gc_fields = Object.entries({
...proto,
...Object.fromEntries((values || []).map(a => [a, { internal: true }])),
}).filter(([name, { cache, internal }]) => (cache && typeof cache !== "string") || internal);
}).filter(
([name, { cache, internal }]) => !name.startsWith("@@") && ((cache && typeof cache !== "string") || internal),
);
const cached_values_string =
gc_fields.length > 0

View File

@@ -0,0 +1,109 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
test("ResolveMessage JSON.stringify should not cause heap-use-after-free and should extend Error", async () => {
// Create a temporary directory with test files
const tempDir = tempDirWithFiles("resolve-message-test", {
"index.js": `
try {
// This should fail to resolve and create a ResolveMessage
require("./nonexistent-module");
} catch (error) {
// Verify it extends Error
console.log("instanceof Error:", error instanceof Error);
// Try to JSON.stringify the error to trigger the heap-use-after-free
console.log("Error caught:", JSON.stringify(error));
process.exit(0);
}
`,
});
// Run the test file with Bun
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
cwd: tempDir,
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The test passes if we don't get an ASAN error and the process exits normally
// In the past, this would cause a heap-use-after-free error
expect(exitCode).toBe(0);
expect(stdout).toContain("instanceof Error: true");
expect(stdout).toContain("Error caught:");
// Make sure we don't have any ASAN errors in stderr
expect(stderr).not.toContain("AddressSanitizer");
expect(stderr).not.toContain("heap-use-after-free");
});
test("ResolveMessage toJSON should handle missing referrer gracefully", async () => {
const tempDir = tempDirWithFiles("resolve-message-missing-referrer", {
"index.js": `
// Test dynamic import that fails
import("./does-not-exist").catch(error => {
// Verify it extends Error
console.log("instanceof Error:", error instanceof Error);
// This should not crash when JSON.stringify is called on the resolve error
try {
const json = JSON.stringify(error);
console.log("JSON output success");
process.exit(0);
} catch (e) {
console.error("JSON.stringify failed:", e);
process.exit(1);
}
});
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
cwd: tempDir,
env: bunEnv,
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("instanceof Error: true");
expect(stdout).toContain("JSON output success");
expect(stderr).not.toContain("AddressSanitizer");
expect(stderr).not.toContain("heap-use-after-free");
});
test("ResolveMessage should pass Error.isError check", async () => {
const tempDir = tempDirWithFiles("resolve-message-error-iserror", {
"index.js": `
try {
require("./nonexistent-module");
} catch (error) {
// Verify it extends Error using Error.isError
console.log("Error.isError:", Error.isError(error));
console.log("instanceof Error:", error instanceof Error);
process.exit(0);
}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
cwd: tempDir,
env: bunEnv,
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("Error.isError: true");
expect(stdout).toContain("instanceof Error: true");
expect(stderr).not.toContain("AddressSanitizer");
expect(stderr).not.toContain("heap-use-after-free");
});