Compare commits

...

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
3c6d685957 [autofix.ci] apply automated fixes 2026-02-26 09:33:44 +00:00
Sosuke Suzuki
df5d977bfd fix(crypto): fix X509Certificate subclassing and .prototype property
Two bugs in the X509Certificate constructor (copy-paste from
NodeVMScript.cpp):

1. finishCreation() did not set the .prototype property on the
   constructor, leaving X509Certificate.prototype undefined. This
   made `class Foo extends X509Certificate {}` throw immediately
   with "The value of the superclass's prototype property is not
   an object or null".

2. When subclassing (newTarget != default constructor), the code
   used functionGlobalObject->NodeVMScriptStructure() instead of
   m_JSX509CertificateClassStructure.get(...), which would have
   given subclass instances the vm.Script structure/prototype chain.
   Also fixed the error message which still said "Script".

Before fix: all 6 tests fail (cannot even declare extends)
After fix:  all 6 tests pass
2026-02-26 18:29:47 +09:00
3 changed files with 86 additions and 13 deletions

View File

@@ -228,16 +228,16 @@ To build for macOS x64:
The order of the `--target` flag does not matter, as long as they're delimited by a `-`.
| --target | Operating System | Architecture | Modern | Baseline | Libc |
| --------------------- | ---------------- | ------------ | ------ | -------- | ----- |
| bun-linux-x64 | Linux | x64 | ✅ | ✅ | glibc |
| bun-linux-arm64 | Linux | arm64 | ✅ | N/A | glibc |
| bun-windows-x64 | Windows | x64 | ✅ | ✅ | - |
| bun-windows-arm64 | Windows | arm64 | ✅ | N/A | - |
| bun-darwin-x64 | macOS | x64 | ✅ | ✅ | - |
| bun-darwin-arm64 | macOS | arm64 | ✅ | N/A | - |
| bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl |
| bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl |
| --target | Operating System | Architecture | Modern | Baseline | Libc |
| -------------------- | ---------------- | ------------ | ------ | -------- | ----- |
| bun-linux-x64 | Linux | x64 | ✅ | ✅ | glibc |
| bun-linux-arm64 | Linux | arm64 | ✅ | N/A | glibc |
| bun-windows-x64 | Windows | x64 | ✅ | ✅ | - |
| bun-windows-arm64 | Windows | arm64 | ✅ | N/A | - |
| bun-darwin-x64 | macOS | x64 | ✅ | ✅ | - |
| bun-darwin-arm64 | macOS | arm64 | ✅ | N/A | - |
| bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl |
| bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl |
<Warning>
On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline`

View File

@@ -97,6 +97,7 @@ JSX509CertificateConstructor* JSX509CertificateConstructor::create(VM& vm, JSGlo
void JSX509CertificateConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSObject* prototype)
{
Base::finishCreation(vm, 1, "X509Certificate"_s, PropertyAdditionMode::WithStructureTransition);
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
}
static JSValue createX509Certificate(JSC::VM& vm, JSGlobalObject* globalObject, Structure* structure, JSValue arg)
{
@@ -158,15 +159,14 @@ JSC_DEFINE_HOST_FUNCTION(x509CertificateConstructorConstruct, (JSGlobalObject *
Structure* structure = zigGlobalObject->m_JSX509CertificateClassStructure.get(zigGlobalObject);
JSValue newTarget = callFrame->newTarget();
if (zigGlobalObject->m_JSX509CertificateClassStructure.constructor(zigGlobalObject) != newTarget) [[unlikely]] {
auto scope = DECLARE_THROW_SCOPE(vm);
if (!newTarget) {
throwTypeError(globalObject, scope, "Class constructor Script cannot be invoked without 'new'"_s);
throwTypeError(globalObject, scope, "Class constructor X509Certificate cannot be invoked without 'new'"_s);
return {};
}
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
RETURN_IF_EXCEPTION(scope, {});
structure = InternalFunction::createSubclassStructure(globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure());
structure = InternalFunction::createSubclassStructure(globalObject, newTarget.getObject(), functionGlobalObject->m_JSX509CertificateClassStructure.get(functionGlobalObject));
RETURN_IF_EXCEPTION(scope, {});
}

View File

@@ -0,0 +1,73 @@
import { describe, expect, test } from "bun:test";
import { X509Certificate } from "node:crypto";
import { readFileSync } from "node:fs";
import path from "node:path";
// Regression test for X509Certificate subclassing.
// Previously, subclassing used NodeVMScriptStructure() instead of the
// X509Certificate structure (copy-paste bug), causing subclass instances
// to inherit from vm.Script.prototype instead of X509Certificate.prototype.
// Also, X509Certificate.prototype was undefined because finishCreation()
// did not call putDirectWithoutTransition for the prototype property.
const certPath = path.join(import.meta.dir, "..", "test", "fixtures", "keys", "agent1-cert.pem");
const certPem = readFileSync(certPath);
describe("X509Certificate", () => {
test("constructor has .prototype property", () => {
expect(X509Certificate.prototype).toBeDefined();
expect(typeof X509Certificate.prototype).toBe("object");
});
test("prototype has expected methods", () => {
expect(typeof X509Certificate.prototype.checkHost).toBe("function");
expect(typeof X509Certificate.prototype.toJSON).toBe("function");
expect(typeof X509Certificate.prototype.toString).toBe("function");
});
test("instance uses correct prototype", () => {
const cert = new X509Certificate(certPem);
expect(Object.getPrototypeOf(cert)).toBe(X509Certificate.prototype);
expect(cert instanceof X509Certificate).toBe(true);
});
test("can be subclassed", () => {
class MyX509 extends X509Certificate {
customMethod() {
return "custom";
}
}
const cert = new MyX509(certPem);
expect(cert instanceof MyX509).toBe(true);
expect(cert instanceof X509Certificate).toBe(true);
expect(cert.customMethod()).toBe("custom");
// Should still have X509Certificate methods
expect(typeof cert.subject).toBe("string");
});
test("subclass prototype chain is correct", () => {
class MyX509 extends X509Certificate {}
const cert = new MyX509(certPem);
const proto = Object.getPrototypeOf(cert);
expect(proto).toBe(MyX509.prototype);
expect(Object.getPrototypeOf(proto)).toBe(X509Certificate.prototype);
// Verify it's NOT inheriting from vm.Script (the previous bug)
const vm = require("node:vm");
expect(cert instanceof vm.Script).toBe(false);
expect(Object.getPrototypeOf(proto)).not.toBe(vm.Script.prototype);
});
test("subclass instance accesses X509 getters correctly", () => {
class MyX509 extends X509Certificate {}
const cert = new MyX509(certPem);
// These getters rely on the correct Structure to read internal fields
expect(cert.subject).toBeDefined();
expect(cert.issuer).toBeDefined();
expect(cert.serialNumber).toBeDefined();
expect(typeof cert.fingerprint).toBe("string");
});
});