memoize Without A Resolver On A Multi-Parameter Function
ID |
javascript.memoize_multiple_params |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
caching, reliability, suspicious-comparison |
Description
Reports a utility-library memoize applied to a function of more than one parameter with no resolver argument:
const area = _.memoize((width, height) => width * height);
area(2, 3); // 6
area(2, 9); // 6 again — the cache was keyed on 2 alone
Rationale
The default resolver of memoize is the first argument. Every call that agrees on it therefore gets the first result back, whatever the remaining arguments say.
Nothing about the code shows it. The memoized function keeps its signature and its type, every call site keeps compiling, and the wrong value comes back silently. It is also unusually hard to catch by hand, because the first call for any pair of arguments is always correct — the bug only appears on the second, and only when the first argument repeats.
Remediation
Pass a resolver that builds the key out of everything the function reads:
const area = _.memoize(
(width, height) => width * height,
(width, height) => width + "x" + height
);
Where the arguments are objects, key on the identifying fields rather than on the objects themselves — the default Map cache compares by reference, so two equal objects are two entries.
Notes
Recognised as the library function: _.memoize, lodash.memoize, underscore.memoize, and a bare memoize(…) in a file that imports lodash, one of its per-method modules, or underscore. Without one of those, a bare memoize is more likely a project helper with a key policy of its own, and assuming otherwise would be a false positive.
A function whose only parameter is a rest parameter is not reported: how many arguments it really takes is a property of its call sites, not of its signature.