Skip to content
Closed
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
3 changes: 3 additions & 0 deletions changelog.d/11090-url-subclass-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Preserve parsed URL state when constructing classes that extend `URL`, including subclasses with an implicit constructor and constructors that explicitly call `super(...)`. URL subclasses now retain their own prototype and fields while exposing the expected URL properties and `instanceof` behavior.
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/expr/instance_misc1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub(crate) fn builtin_parent_reserved_class_id(name: &str) -> Option<u32> {
"BigInt64Array" => 0xFFFF0039,
"BigUint64Array" => 0xFFFF003A,
"Function" => 0xFFFF00F0,
"URL" => 0xFFFF0063,
// #10556: `class Sub extends EventEmitter {}` — same shape as the
// Array/Map/Set/Error builtins above. Without this edge,
// `new Sub() instanceof EventEmitter` never reaches the class-chain
Expand Down Expand Up @@ -554,6 +555,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
"ReadableStream" => 0xFFFF0060u32,
"WritableStream" => 0xFFFF0061u32,
"TransformStream" => 0xFFFF0062u32,
"URL" => 0xFFFF0063u32,
// node:stream/web codec stream constructors are heap
// ObjectHeader instances with runtime-owned class IDs.
"TextEncoderStream" => 0x7FFFFF30u32,
Expand Down
11 changes: 6 additions & 5 deletions crates/perry-codegen/src/expr/this_super_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,10 @@ pub(crate) fn bind_derived_this_after_super(ctx: &mut FnCtx<'_>) {
/// SuperCall arms) that can appear as a class heritage. `super(...)` to these
/// must NOT be routed through the runtime-value dispatch path
/// (`js_fetch_or_value_super`), which would invoke e.g. `Map()` without `new`
/// and throw "Constructor requires 'new'". Perry cannot yet give a subclass
/// instance the built-in's internal slots, so `super()` is a best-effort no-op
/// here — enough that `class M extends Map { constructor(){ super(); } }`
/// constructs without throwing. Refs class/subclass/builtin-objects/*/
/// super-must-be-called.
/// and throw "Constructor requires 'new'". Dedicated arms below either install
/// the built-in state on the provisional receiver or construct a branded value
/// with the subclass as `newTarget`. Refs class/subclass/builtin-objects/*/
/// super-must-be-called and #10639.
pub(crate) fn is_other_builtin_constructor_name(name: &str) -> bool {
matches!(
name,
Expand All @@ -152,6 +151,7 @@ pub(crate) fn is_other_builtin_constructor_name(name: &str) -> bool {
| "String"
| "Date"
| "RegExp"
| "URL"
| "Promise"
| "Function"
| "BigInt"
Expand Down Expand Up @@ -1183,6 +1183,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
| "String"
| "Date"
| "RegExp"
| "URL"
| "Function"
| "BigInt"
| "Symbol"
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/lower_call/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,7 @@ fn lower_new_impl_inner<'a>(
| "String"
| "Date"
| "RegExp"
| "URL"
| "Function"
| "BigInt"
| "Symbol"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,7 @@ pub unsafe extern "C" fn js_new_function_construct_with_new_target(
| "Number"
| "String"
| "RegExp"
| "URL"
| "Function"
) {
let scope = crate::gc::RuntimeHandleScope::new();
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-runtime/src/object/instanceof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const CLASS_ID_SUBTLE_CRYPTO: u32 = 0xFFFF00C1;
const CLASS_ID_CRYPTO_KEY: u32 = 0xFFFF00C2;
/// `value instanceof Function` reserved id (see `js_instanceof`).
const CLASS_ID_FUNCTION: u32 = 0xFFFF00F0;
const CLASS_ID_URL: u32 = 0xFFFF0063;

mod dynamic_dispatch;
mod static_dispatch;
Expand Down Expand Up @@ -212,6 +213,7 @@ pub(crate) fn global_builtin_constructor_class_id(name: &str) -> u32 {
"BigInt" => 0xFFFF00D3,
"Symbol" => 0xFFFF00D4,
"Date" => 0xFFFF0020,
"URL" => CLASS_ID_URL,
"Error" => crate::error::CLASS_ID_ERROR,
"TypeError" => crate::error::CLASS_ID_TYPE_ERROR,
"RangeError" => crate::error::CLASS_ID_RANGE_ERROR,
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-runtime/src/object/instanceof/static_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ pub extern "C" fn js_instanceof(value: f64, class_id: u32) -> f64 {
false_val
};
}
if class_id == CLASS_ID_URL {
let addr = value_addr(value);
let branded = addr != 0 && crate::url::is_url_object_shape(addr as *mut ObjectHeader);
return if branded || recorded_prototype_instanceof_builtin(value, "URL") == Some(true) {
true_val
} else {
false_val
};
}
// Keep in sync with perry-codegen/src/expr/instance_misc1.rs.
let classic_stream_name = match class_id {
0xFFFF0070 => Some("Stream"),
Expand Down
38 changes: 38 additions & 0 deletions test-files/test_gap_10639_url_subclass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// #10639: a URL subclass must receive URL's internal state while preserving
// its declared prototype and fields. Both the implicit derived constructor and
// a written `super(input, base)` call used to leave hostname/pathname undefined.

class ImplicitUrl extends URL {
kind = "implicit";
}

const implicit = new ImplicitUrl("child?q=1", "https://example.com/base/");
console.log(
"implicit:",
implicit.hostname,
implicit.pathname,
implicit.search,
implicit.kind,
implicit instanceof ImplicitUrl,
implicit instanceof URL,
);

class ExplicitUrl extends URL {
kind: string;

constructor(input: string, base: string) {
super(input, base);
this.kind = "explicit";
}
}

const explicit = new ExplicitUrl("../next#hash", "https://perry.dev/a/b/");
console.log(
"explicit:",
explicit.hostname,
explicit.pathname,
explicit.hash,
explicit.kind,
explicit instanceof ExplicitUrl,
explicit instanceof URL,
);
Loading