Skip to content

Commit f2bf0a6

Browse files
committed
Slice prepended scope arg out of user-visible arguments.length
When the scope pass prepends a $$scope argument and toildefender$bind-wraps a function, user-visible `arguments` must exclude the injected arg. The slice count was gated on methodRefersToArguments(), which only detects *unresolved* `arguments` references and misses `arguments.length` (escope resolves `arguments`). Brand-check helpers (the shape of `#x in obj` / private getters) then read an off-by-one `arguments.length` and returned the wrong branch, corrupting downlevelled #private access -- e.g. a WeakMap receiving undefined as a key. Always slice by the $$scope param count instead. This is what broke ToilGate's fully-obfuscated build with numeric_vm + dead_code (dead_code kept the helper native, exposing the path the VM's slice had masked). Verified end-to-end.
1 parent 9c6fc08 commit f2bf0a6

1 file changed

Lines changed: 10 additions & 2 deletions

File tree

src/obfuscator.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,8 +798,16 @@ export function protect(inputOptions: ToilDefenderOptions): ToilDefenderResult {
798798
const scopeManager = escope.analyze(ast, lexicalScopeOptions);
799799
fns = asAstNodeArray(methods.extractMethods(ast));
800800
fns = fns.map((method: AstNode) => {
801-
const refers = methods.methodRefersToArguments(method, scopeManager);
802-
const scopeArgumentCount = refers ? nodeParams(method).filter((param) => nodeName(param).indexOf("$$scope") == 0).length : 0;
801+
// `bareArguments` (what `arguments` / `arguments.length` lower to) must
802+
// be sliced by the number of scope params the scope pass prepended, so
803+
// that user-visible `arguments` excludes the injected `$$scope` arg.
804+
// This must NOT be gated on methodRefersToArguments(): that helper only
805+
// detects *unresolved* `arguments` references, and misses `arguments.length`
806+
// (escope resolves `arguments`), which left brand-check helpers like
807+
// `#x in obj` reading a length that was off-by-one and returning the wrong
808+
// value — corrupting downlevelled #private access. Param-index references
809+
// already account for the prepend via the shifted param list.
810+
const scopeArgumentCount = nodeParams(method).filter((param) => (nodeName(param) || "").indexOf("$$scope") == 0).length;
803811
methods.removeFirstArguments(method, scopeArgumentCount);
804812
return asAstNode(methods.replaceArgumentReferences(method, true));
805813
});

0 commit comments

Comments
 (0)