Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions crates/perry-runtime/src/object/class_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2922,14 +2922,18 @@ pub(crate) fn ordinary_function_prototype_value_for_read(func_value: f64) -> Opt
// `'prototype' in C.prototype.m === false`). (Test262 definition method/accessor
// prop-desc.)
//
// #4973 exception: bound NATIVE-MODULE *class* exports (`http.Server`,
// `https.Server`) are constructors in Node, and the util.inherits-era
// subclass pattern reads their `.prototype` as a setPrototypeOf operand
// (`Object.setPrototypeOf(testServer.prototype, http.Server.prototype)`).
// Returning None here made that read `undefined` and the setPrototypeOf
// threw "Object prototype may only be an Object or null". These exports
// are cached singleton closures (NATIVE_CALLABLE_EXPORTS), so the
// synthetic-class path below gives them a stable prototype object.
// #4973 / #3527 exception: bound NATIVE-MODULE *class* exports
// (`http.Server`, `http.IncomingMessage`, `http.ServerResponse`, …) are
// constructors in Node, and the util.inherits / `Object.create(Ctor.
// prototype)` subclass pattern reads their `.prototype` as a
// setPrototypeOf / Object.create operand. Returning None here made that
// read `undefined`, and `Object.create(undefined)` /
// `Object.setPrototypeOf(x, undefined)` then threw "Object prototype may
// only be an Object or null" — the exact blocker hit at Express init
// (`express/lib/request.js`: `Object.create(http.IncomingMessage.
// prototype)`). These exports are cached singleton closures
// (NATIVE_CALLABLE_EXPORTS), so the synthetic-class path below gives them
// a stable prototype object.
{
let jv = crate::value::JSValue::from_bits(func_value.to_bits());
if jv.is_pointer() {
Expand All @@ -2942,7 +2946,20 @@ pub(crate) fn ordinary_function_prototype_value_for_read(func_value: f64) -> Opt
super::native_module::bound_native_callable_module_and_method(func_value)
}
.map(|(module, method)| {
matches!(module.as_str(), "http" | "https") && method == "Server"
matches!(
(module.as_str(), method.as_str()),
// Shared http/https constructor classes.
("http" | "https", "Server" | "Agent")
// http-only request/response constructor classes
// that userland subclasses (Express, util.inherits).
| (
"http",
"IncomingMessage"
| "ServerResponse"
| "OutgoingMessage"
| "ClientRequest"
)
)
})
.unwrap_or(false);
if !is_native_class_export {
Expand Down
34 changes: 34 additions & 0 deletions test-files/test_gap_3527_http_ctor_prototype.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// #3527: native `node:http` constructor classes expose a real `.prototype`
// object so userland subclassing — `Object.create(http.IncomingMessage.
// prototype)` (Express), `util.inherits(...)` — works instead of throwing
// "Object prototype may only be an Object or null".
import http from "node:http";
import https from "node:https";
import util from "node:util";

for (const name of [
"IncomingMessage",
"ServerResponse",
"OutgoingMessage",
"ClientRequest",
"Server",
"Agent",
] as const) {
console.log(`http.${name}.prototype:`, typeof (http as any)[name].prototype);
}
console.log("https.Server.prototype:", typeof https.Server.prototype);
console.log("https.Agent.prototype:", typeof https.Agent.prototype);

// Express's request/response prototype pattern.
const req = Object.create(http.IncomingMessage.prototype);
const res = Object.create(http.ServerResponse.prototype);
console.log("Object.create(IncomingMessage.prototype) is object:", typeof req === "object");
console.log("Object.create(ServerResponse.prototype) is object:", typeof res === "object");

// util.inherits over a native http class.
function MyMessage() {}
util.inherits(MyMessage, http.IncomingMessage);
console.log(
"util.inherits links prototype chain:",
Object.getPrototypeOf(MyMessage.prototype) === http.IncomingMessage.prototype,
);