Engineering Journal
Table Formatter
Table Formatter

The Bug Class Where Your Monkey-Patch Silently Does Nothing

2026-07-23

TLDR

Reassigning global functions (window.fn = wrappedFn) fails to intercept callers that cached the original function reference during initialization. Event listeners bound prior to reassignment continue calling the un-wrapped reference. Explicitly rebinding element listeners or utilizing completion callbacks ensures wrapper execution.
Interception StrategyLookup BehaviorEvent Listener InvocationExecution Reliability
Global Name ReassignmentDereferenced at call timeExecutes stale cached referenceFails silently (Bypassed)
Caller Rebinding / CallbacksDirectly bound handler referenceExecutes updated wrapper100% Guaranteed Execution

Early-bound event handlers cache function pointers, causing reassigned global wrappers to fail silently

Wrapping global functions to add post-processing behavior is a common JavaScript pattern:

// Vulnerable to stale reference caching
const originalFn = window.runPipeline;
window.runPipeline = function() {
    originalFn();
    emitResults();
};

However, if an event listener cached window.runPipeline during initialization ($('#btn').on('click', runPipeline)), clicking the button invokes the stored reference directly, bypassing global reassignments silently.


Dynamically dereferencing globals inside anonymous closures ensures wrapper execution

To ensure post-processing routines execute reliably, we updated event handlers to call global functions dynamically at invocation time:

   STALE CACHED POINTER (INTERCEPTION BYPASSED):
   [Init Time] ────> el.addEventListener('click', runPipeline) (Caches Pointer 0x111)
   [Patch Time] ───> window.runPipeline = wrappedPipeline      (Updates pointer to 0x222)
   [Click Time] ───> Executes Pointer 0x111 (Calls original runPipeline directly, bypassing wrapper!)

DYNAMIC LOOKUP CLOSURE (INTERCEPTION SUCCEEDS): [Init Time] ────> el.addEventListener('click', () => { window.runPipeline() }) [Patch Time] ───> window.runPipeline = wrappedPipeline (Updates pointer to 0x222) [Click Time] ───> Calls closure ──> Resolves window.runPipeline ──> Executes 0x222 (Wrapper runs!)

Here is the implementation to resolve globals dynamically:

// 1. Dynamic dereferencing wrapper prevents stale reference caching
$('#runBtn').off('click').on('click', () => {
    window.runPipeline(); // Evaluates current window pointer at click time
    emitResults();
});

// 2. Alternatively, expose explicit lifecycle hooks export function registerPipelineHook(callbackFn) { pipelineHooks.push(callbackFn); }

Dereferencing window.runPipeline() inside the event handler ensures reassignments execute as expected.

Rule of thumb: Resolve global methods dynamically inside closure callbacks rather than passing raw function variables to early event bindings.
Read this post in the full Engineering Journal →