Skip to content

Prototype method replacement not observed through a typed-parameter receiver #9131

Description

@proggeramlug

A prototype method replaced after first call is not picked up when the receiver arrives as a typed parameter, though it is picked up on a direct call.

class P { v = 1; m() { return 1; } }
function run(o: P, n: number) { let s = 0; for (let i = 0; i < n; i++) s += (o as any).m(); return s; }

const p = new P();
const before = run(p, 3);
(P.prototype as any).m = function () { return 100; };
const after = run(p, 3);
console.log(before, after);
node v26.5.1  3 300
perry         3 3

Patching mid-loop behaves the same way:

const p = new P(); let s = 0;
for (let i = 0; i < 6; i++) { s += p.m(); if (i === 2) (P.prototype as any).m = function () { return 50; }; }
// node: 153    perry: 6

The direct-call form is correct, which is what makes this narrow:

const p = new P(); const a = p.m();
(P.prototype as any).m = function () { return "patched"; };
console.log(a, p.m());   // node: orig patched   perry: orig patched  ✓

Not a regression

Found while auditing #9124 and re-checked on #9130. Both reproduce identically on main, so this predates the inline-probe series — but it lives in exactly the code that series is changing (method_override.rs's guard selection), so it is worth pinning before more optimization lands on top.

Related shapes that are also wrong on main

From the same 23-shape probe, all identical on main and on #9124/#9130:

shape node perry
Object.setPrototypeOf(c, {inc: () => 777}) then c.inc() 777 2
Object.setPrototypeOf(p, Q.prototype) then p.m() "b" "a"
c.inc.call(otherInstance) [101,101,1] TypeError
get m() { return () => this.v * 7 } then g.m() 21 TypeError
delete c.v mid-loop then c.inc() NaN 21

The first two are the same root as the headline case (a stale prototype edge); the last three look independent and may deserve splitting out once someone digs in.

Why it matters beyond spec conformance

Monkey-patching a prototype is how a lot of instrumentation, test doubles and polyfills work. Silently continuing to call the old body — with no error — is the failure mode most likely to be misdiagnosed as "my mock didn't get applied".

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions