What survives
Published
Variadic code looks fragile under a transform that rewrites names, because it reads its inputs positionally rather than by name and often forwards them to somewhere else entirely. Measured against the real engine in five configurations, every case came out identical. One number does change on the ES5 target, and it is a property of downlevel compilation rather than of renaming.
Why variadic code looks fragile
Most JavaScript reads its inputs by name: a function declares parameters and uses them. Variadic code does the opposite. It reads arguments, or a rest parameter, and works positionally, often without naming the values at all. Then it frequently forwards the whole set to another function with apply or a spread.
That shape makes people nervous about protection for a reasonable reason. If a transform reordered parameters, dropped an unused declared parameter it believed was dead, or rewrote a forwarding call, the failure would appear at the far end of a call chain rather than where the mistake was made. Wrappers, loggers, decorators and polyfills are all built this way, and they tend to sit at the bottom of the stack where a bug is expensive.
The measured answer is that this whole family is untouched, and the reason is that almost nothing in it is a name.
What was measured
A single sample was written to exercise the surface in one file: arguments.length and its contents with more arguments than declared and with fewer; the array-like shape of arguments, confirming it is not an array and has no map; three conversions, with Array.prototype.slice.call, Array.from and a spread; rest parameters with and without extra arguments, including the check that a rest parameter really is an array; Function.prototype.length for a plain function, one with a rest parameter, one with no parameters and a rest-only function; spread at the call site including a mixed spread of two arrays; Math.max called both by spread and by apply; spreading a string and a Set into a call; forwarding arguments onward with both apply and a spread; the sloppy-mode link between arguments and a named parameter; the strict-mode case where that link is broken; an arrow function reading the enclosing function's arguments; and a hand-written variadic sum.
The file was run in Node, then protected and run again, with the two outputs compared line by line, across the same five configurations used elsewhere in this series.
All five matched the original exactly. Forwarding still delivered the right values through both apply and spread. The sloppy-mode link still showed a reassignment to a named parameter through arguments[0], and the strict-mode version still did not. An arrow function still read the enclosing function's arguments rather than having one of its own.
Why it holds: arguments is not a name your code owns
The structural reason is short. Renaming rewrites bindings your code declares and property names your code uses. arguments is neither: it is a binding the language provides inside every non-arrow function, and it is not a candidate for renaming any more than this is.
The values inside it are reached by numeric index, and an index is not a name. arguments[0] is a computed access with a number in it, which is the same reason typed arrays and ordinary array element access are untouched. There is nothing there for a rename to attach to.
Rest parameters are an ordinary binding, so they are renamed like any other local variable, and the array they hold is built by the language rather than by anything a transform rewrites. Spread at a call site is a syntactic form rather than a name; the transform preserves the form and the values flow through it as before.
Declared parameters are renamed, of course, and that is the case worth thinking about for one second. Because the rename is scope-aware, a parameter and every reference to it move together, so a function that reads both its declared parameter and arguments stays consistent. The sample tested exactly that, including the sloppy-mode case where the two are linked and a write to one is observable through the other.
The strict-mode difference that protection did not cause
One result in the sample looks like a protection effect and is not, so it is worth separating out.
In sloppy mode, a function's named parameters and its arguments object are linked: assign to the parameter and arguments[0] shows the new value. In strict mode that link does not exist, and arguments[0] keeps the original. The sample measured both and both came through protection unchanged.
The reason to flag it is that this difference travels with the strict-mode directive. If a build drops or adds a directive, variadic code that relies on the linked behaviour changes meaning without any renaming being involved. That is a real hazard in the general case, and it is a question about the directive rather than about arguments. It has its own article, and it is the thing to check first if variadic code behaves differently after a build change.
The one number that changes, and where
There is a measured difference, and stating it precisely keeps it from being mistaken for something larger.
Function.prototype.length reports the number of parameters before the first one with a default value. A function written as function f(a, b = 2) reports 1. On the ES5 target, where default parameters have to be lowered into assignments inside the body, that function reports 2 instead. Measured directly: 1 in the original, 2 on the ES5 target with and without renaming, and 1 on the modern target in both cases. The function's behaviour was unchanged; only the reported arity differed.
This is inherent to downlevel compilation rather than specific to this engine, and standard downlevel compilers produce the same shift for the same reason. Everything else in the same measurement was stable: a plain two-parameter function reported 2, a function with a rest parameter reported the count of the parameters before it, and a rest-only function reported 0, all unchanged in every configuration.
It only matters if something reads arity at run time, which a few dependency injectors, test harnesses, currying helpers and middleware dispatchers do. If that describes your code, the fix is the modern target rather than avoiding protection.
Scope, and how to check your own build
This article covers the arguments object, rest parameters and spread in argument lists, measured on the default configuration and the four variants named above. Spread in an object literal is a different feature that copies properties rather than passing arguments, and it is not covered here.
Checking your own build is worth doing if you ship wrappers or middleware, because that is where variadic code concentrates. Write a scratch file with your forwarding helper, call it with several different argument counts including zero, and print what arrives at the far end. Protect it with the configuration you actually use, run both files under Node, and compare.
If anything in your stack reads Function.prototype.length, add a line that prints it for the functions you register, and compare that number specifically. It is the one value in this area measured to move, and only on the ES5 target with default parameters.
Frequently asked questions
Does obfuscation break the arguments object?
No. Measured across five configurations, arguments came through identical: its length, its contents with more or fewer arguments than declared, its array-like shape, and all three conversions to a real array. It is a binding the language provides rather than a name your code declares, so it is not a candidate for renaming.
Do rest parameters and spread at call sites survive protection?
Yes. Rest parameters still produced real arrays, spread at the call site still delivered the right values including a mixed spread of two arrays, and spreading a string or a Set into a call was unchanged. Math.max worked identically whether called by spread or by apply.
Does forwarding arguments to another function still work?
Yes, in both forms measured. Forwarding with apply and forwarding with a spread both delivered the correct values after protection in all five configurations. This is the shape wrappers and middleware use, so it is the one worth checking against your own build.
Does anything about arity change after protection?
One thing, on the ES5 target only. Function.prototype.length reports the number of parameters before the first default, so a function written with a default reports 1 originally but 2 after an ES5 downlevel, because defaults become assignments in the body. The modern target reports 1 in both cases, and the function's behaviour is unchanged either way.
Why does the link between arguments and named parameters differ between builds?
That difference comes from strict mode, not from protection. In sloppy mode a write to a named parameter is visible through arguments; in strict mode it is not. Both behaviours came through protection unchanged, so if variadic code changes meaning after a build change, check whether the strict mode directive survived.
Do arrow functions get their own arguments object after protection?
No, and they should not. An arrow function has no arguments object of its own and reads the enclosing function's, which is what the sample measured before and after protection with the same result.
Related reading