methods that rewrite the object they live on
this Mutation
A method quietly rewrites the object it lives on — or loses its receiver entirely — so a later read sees a value nobody assigned on purpose.
When a method mutates this, every caller is coupled to call order.
01in the wild
In the wild
Mutating this In Place
A method quietly rewrites the object it lives on, so a later read sees a value nobody assigned on purpose.
example.js
class Cart {
constructor() { this.items = []; }
// SMELL: mutates this and returns nothing useful
add(item) { this.items.push(item); }
}
// RIGHT: return a new cart, never mutate in place
const add = (cart, item) => ({ items: [...cart.items, item] });Mutating this couples every caller to call order. Returning a fresh value makes the operation a pure function.
// observed
mutating: order of add() calls changes shared state pure: each call is independent and replayable
example.py
class Counter:
def __init__(self):
self.n = 0
# SMELL: side effect on self, no return
def bump(self):
self.n += 1
# RIGHT: a value object that returns a new instance
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Counter:
n: int = 0
def bump(self):
return replace(self, n=self.n + 1)A frozen dataclass cannot be mutated, so bump() must hand back a new value. State changes become traceable.
// observed
mutable: c.bump() changes c in place frozen: c2 = c.bump(); c is untouched
Losing this in a Callback
Pass a method as a callback and it forgets which object it belonged to.
example.js
class Timer {
constructor() { this.count = 0; }
tick() { this.count++; } // depends on `this`
}
const t = new Timer();
// SMELL: passing the method drops its receiver
setInterval(t.tick, 1000); // TypeError: this is undefined
// RIGHT: bind the receiver, or wrap in an arrow
setInterval(() => t.tick(), 1000);A bare method reference loses its `this`. An arrow wrapper (or .bind(t)) preserves the receiver.
// observed
bare: this is undefined inside tick() arrow: t.count increments as expected
example.py
class Timer:
def __init__(self): self.count = 0
def tick(self): self.count += 1
t = Timer()
# A *bound* method keeps its self -- this is safe in Python
schedule(t.tick) # fine: t.tick is bound to t
# SMELL: the *unbound* function needs self passed in
schedule(Timer.tick) # TypeError: missing 'self'Python binds self when you access t.tick. Reaching through the class instead drops the receiver, mirroring the JS trap.
// observed
bound: t.count increments unbound: TypeError, no self supplied
02cross-pollination
Where this compounds
Nondeterminism
- Concurrent Singleton Mutation × Cross-Boundary State Exposure
Runtime Errors
- Type Confusion / Bad Coercion × Type Errors in Dynamic Languages
- Deserialized Type Confusion Crash × Insecure Deserialization
- Unexpected-Type Element in a Heterogeneous Collection × Duck Typing Without Contracts
03weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.