fix(minifier): preserve Object method calls when arg could be a Proxy#21056
Open
gthb wants to merge 9 commits into
Open
fix(minifier): preserve Object method calls when arg could be a Proxy#21056gthb wants to merge 9 commits into
gthb wants to merge 9 commits into
Conversation
…Proxy What --- Preserve calls to Object methods (`keys`, `getOwnPropertyDescriptor`, `hasOwn`, `getPrototypeOf`, etc.) when the target argument could be a Proxy, since Proxy handler traps are observable side effects. Previously the minifier treated all these Object methods as unconditionally side-effect-free, removing calls like `Object.getOwnPropertyDescriptor(proxy, "foo")` even when the argument was a Proxy with a `getOwnPropertyDescriptor` trap. Also handle `Object.create(proto, props)` where the second argument (propertiesObject) could be a Proxy, since `ObjectDefineProperties` reads `[[OwnPropertyKeys]]` and `[[Get]]` on it. Closes oxc-project#17157 How --- Split the Object methods previously listed in `is_pure_global_method_call` into three categories: - Unconditionally pure: `Object.is` (SameValue comparison, no object introspection). - First-argument-sensitive: `getOwnPropertyDescriptor`, `keys`, `hasOwn`, `getPrototypeOf`, `isExtensible`, `isFrozen`, `isSealed`, and others that invoke Proxy-trappable internal methods (`[[GetOwnProperty]]`, `[[OwnPropertyKeys]]`, `[[GetPrototypeOf]]`, `[[IsExtensible]]`) on their first argument. - Second-argument-sensitive: `Object.create`, where the optional `propertiesObject` is introspected via `[[OwnPropertyKeys]]` and `[[Get]]`. For the sensitive methods, first check whether evaluating the arguments themselves has side effects (this catches cases like `new Proxy(...)` as an inline argument, since `Proxy` is not a pure constructor). Then check the relevant argument's `value_type()`: if `Undetermined` (a variable, call result, or other expression whose type is not statically known), the argument could be a Proxy, so the call is treated as having side effects. Primitive literals and object/array/function literals have known types and cannot be Proxies. This follows the existing pattern used for `instanceof` Proxy detection in the same file.
JustFly1984
added a commit
to JustFly1984/oxc
that referenced
this pull request
Apr 10, 2026
…Proxy Cherry-picked from oxc-project#21056 (commit e0ae3be) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace `is_proxy_sensitive_object_method()` + separate `Object.create` special case with a single `proxy_sensitive_arg_index()` function that returns which argument index must not be a Proxy. This eliminates code duplication between first-arg and second-arg checking paths and centralizes all proxy-sensitivity knowledge in one place. No behavior change.
…nsitive methods Object.keys(...x) was incorrectly treated as pure because Argument::as_expression() returns None for SpreadElement, and the subsequent is_some_and() defaulted to false. Use map_or(true, ...) so spread elements are conservatively treated as having side effects, matching the pattern used in the Number/Symbol constructor checks.
…methods These Object methods internally perform property reads ([[OwnPropertyKeys]], [[Get]], [[GetOwnProperty]]). When the context says property reads are side-effect-free (Rollup's treeshake.propertyReadSideEffects: false), these calls should be pure regardless of Proxy risk. This matches 6 other places in the same file that already check this setting.
…methods
These use EnumerableOwnProperties which calls [[OwnPropertyKeys]] and
[[Get]] on their first argument, same as Object.keys. Previously they
fell through to "always impure"; now Object.values({}) and
Object.entries({}) are correctly recognized as pure.
Merging this PR will not alter performance
Comparing Footnotes
|
Dunqing
added a commit
that referenced
this pull request
Jun 16, 2026
`Object.keys`, `getOwnPropertyDescriptor(s)`, `getOwnPropertyNames`, `getOwnPropertySymbols`, `getPrototypeOf`, `hasOwn`, `isExtensible`, `isFrozen`, and `isSealed` introspect their target via internal methods a `Proxy` can trap (`[[OwnPropertyKeys]]`, `[[GetOwnProperty]]`, ...), so they have observable side effects when the target is a Proxy. They were treated as unconditionally pure and dropped when unused -- e.g. `Object.getOwnPropertyDescriptor(proxy, "foo")` was silently removed, skipping the trap. Treat them as side-effect-free only when the target is provably not a Proxy (a determined, non-null value type, i.e. a literal) -- unless the bundler opts out of property-read side effects. A `null`/`undefined`/missing target is kept too, since it throws a TypeError. `Object.create(proto)` is pure only with an object/null prototype and no `properties` argument (read via `[[OwnPropertyKeys]]`/`[[Get]]`). `Object.values`/`entries` are no longer treated as pure at all, as they invoke `[[Get]]` and run getters even on a plain object. `Object.is` stays unconditionally pure. Adapted from #21056 by Gunnlaugur Thor Briem, with corrections for the getter-invoking methods (`values`/`entries`, `create` with properties) and the throw-on-`null`/`undefined` cases. Closes #17157 Co-authored-by: Gunnlaugur Thor Briem <gunnlaugur@gmail.com>
graphite-app Bot
pushed a commit
that referenced
this pull request
Jun 17, 2026
…23483) ## Summary Closes #17157. `Object.keys`, `getOwnPropertyDescriptor(s)`, `getOwnPropertyNames`, `getOwnPropertySymbols`, `getPrototypeOf`, `hasOwn`, `isExtensible`, `isFrozen`, and `isSealed` introspect their target through internal methods a `Proxy` can trap (`[[OwnPropertyKeys]]`, `[[GetOwnProperty]]`, …), so they have observable side effects when the target is a Proxy. They were treated as unconditionally pure and dropped when unused — so `Object.getOwnPropertyDescriptor(proxy, "foo")` / `Object.keys(proxy)` were silently removed, skipping the trap. They are now treated as side-effect-free only when the target is provably not a Proxy (a determined, non-null value type — i.e. a literal), unless the bundler opts out of property-read side effects. A `null`/`undefined`/missing target is also kept (it throws a `TypeError`). - `Object.create(proto)` is pure only with an object/`null` prototype and no `properties` argument (read via `[[OwnPropertyKeys]]`/`[[Get]]`). - `Object.values`/`entries` are no longer treated as pure at all — they invoke `[[Get]]` and run getters even on a plain object. - `Object.is` stays unconditionally pure. ## Credit Adapted from #21056 by @gthb. Their `proxy_sensitive_arg_index` approach is preserved; this version adds corrections for the getter-invoking methods (`values`/`entries`, and `create` with properties) and the throw-on-`null`/`undefined` cases that the original treated as droppable. Verified by `cargo test -p oxc_minifier` / `-p oxc_ecmascript`; `minsize` unchanged. cloese #21056
camc314
pushed a commit
that referenced
this pull request
Jul 3, 2026
…23483) ## Summary Closes #17157. `Object.keys`, `getOwnPropertyDescriptor(s)`, `getOwnPropertyNames`, `getOwnPropertySymbols`, `getPrototypeOf`, `hasOwn`, `isExtensible`, `isFrozen`, and `isSealed` introspect their target through internal methods a `Proxy` can trap (`[[OwnPropertyKeys]]`, `[[GetOwnProperty]]`, …), so they have observable side effects when the target is a Proxy. They were treated as unconditionally pure and dropped when unused — so `Object.getOwnPropertyDescriptor(proxy, "foo")` / `Object.keys(proxy)` were silently removed, skipping the trap. They are now treated as side-effect-free only when the target is provably not a Proxy (a determined, non-null value type — i.e. a literal), unless the bundler opts out of property-read side effects. A `null`/`undefined`/missing target is also kept (it throws a `TypeError`). - `Object.create(proto)` is pure only with an object/`null` prototype and no `properties` argument (read via `[[OwnPropertyKeys]]`/`[[Get]]`). - `Object.values`/`entries` are no longer treated as pure at all — they invoke `[[Get]]` and run getters even on a plain object. - `Object.is` stays unconditionally pure. ## Credit Adapted from #21056 by @gthb. Their `proxy_sensitive_arg_index` approach is preserved; this version adds corrections for the getter-invoking methods (`values`/`entries`, and `create` with properties) and the throw-on-`null`/`undefined` cases that the original treated as droppable. Verified by `cargo test -p oxc_minifier` / `-p oxc_ecmascript`; `minsize` unchanged. cloese #21056
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Preserve calls to Object methods (
keys,values,entries,getOwnPropertyDescriptor,hasOwn,getPrototypeOf, etc.) when the target argument could be a Proxy, since Proxy handler traps are observable side effects.Previously the minifier treated all these Object methods as unconditionally side-effect-free, removing calls like
Object.getOwnPropertyDescriptor(proxy, "foo")even when the argument was a Proxy with agetOwnPropertyDescriptortrap.Also handle
Object.create(proto, props)where the second argument (propertiesObject) could be a Proxy, sinceObjectDefinePropertiesreads[[OwnPropertyKeys]]and[[Get]]on it.Closes #17157
How
Add a
proxy_sensitive_arg_index(object, method) -> Option<usize>function inknown_globals.rsthat maps each proxy-sensitive method to the argument index that must not be a Proxy. This covers first-argument methods (keys,values,entries,getOwnPropertyDescriptor,hasOwn,getPrototypeOf,isExtensible,isFrozen,isSealed, etc.) andObject.create(second argument).Object.isstays inis_pure_global_method_callas unconditionally pure.For the sensitive methods, check:
new Proxy(...)sinceProxyis not a pure constructor)PropertyReadSideEffects::Noneis set, in which case Proxy traps are irrelevant (matching Rollup'streeshake.propertyReadSideEffects: false)value_type()— ifUndetermined, the argument could be a Proxy. Spread arguments are conservatively treated as side-effectful.