Skip to content

URLSearchParams methods unreachable through dynamically-typed access (append is not a function) #5961

Closed

Description

@proggeramlug

Repro

const u = new URL("https://x.com/p");
const o: any = [u.searchParams][0];   // any type-erasing indirection
console.log("T", typeof o.append);
try { o.append("a","1"); console.log("OK", u.toString()); } catch(e) { console.log("ERR", String(e)); }
  • node: T function → OK https://x.com/p?a=1
  • perry-compiled: T undefined → ERR TypeError: append is not a function

Mechanism

create_url_search_params_object (perry-runtime url/search_params.rs) allocates the native URLSearchParams as an ordinary object with class_id == 0 holding _entries/_owner fields — deliberately (the shape-guard in try_read_as_search_params relies on it). Its methods exist only via static, type-inference-directed lowering (perry-codegen/expr/url_main.rs routes sp.append(...) to js_url_search_params_append when receiver_class_name infers URLSearchParams). The instance itself carries no vtable and no prototype: Object.getPrototypeOf(sp) is Object.prototype, Object.keys(sp) is ["_entries","_owner"].

So the moment the receiver's static type is lost — any, a heterogeneous container, a minified bundle where inference can't track the value — the generic dynamic member read finds nothing and every method call throws X is not a function. Real-world hit: a bundled app's auth-URL builder doing eleven url.searchParams.append(...) calls fails at runtime while the identical code passes when extracted (where inference succeeds).

typeof o.append === "undefined" (not a thrown lookup) confirms it's the plain-object field-miss path.

Fix direction

Make native URLSearchParams methods reachable dynamically. Two shapes:

  1. Dynamic-fallback dispatch (surgical): in the generic member-get/method-call miss path, when the receiver shape-matches URLSearchParams (class_id == 0 + leading _entries key, as try_read_as_search_params already checks) and the name is one of the URLSearchParams surface (append/delete/entries/forEach/get/getAll/has/keys/set/sort/toString/values/size), return/invoke the corresponding js_url_search_params_* native. Mirrors how other natives expose a *_method_value surface.
  2. Real identity (structural): allocate URLSearchParams with a reserved registered class id + vtable entries for the natives, updating the class_id == 0 shape-guards (try_read_as_search_params, MIMEParams disambiguation) accordingly.

The same audit likely applies to sibling natives whose methods are static-lowering-only (e.g. Headers via js_headers_method_value, URL itself) — worth checking typeof (x as any).method for each.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions