Skip to content

Capture Proxy [[Construct]] at creation per ProxyCreate#2669

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:proxy-isconstructor
Jul 13, 2026
Merged

Capture Proxy [[Construct]] at creation per ProxyCreate#2669
lahma merged 1 commit into
sebastienros:mainfrom
lahma:proxy-isconstructor

Conversation

@lahma

@lahma lahma commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Per ProxyCreate, a proxy has [[Construct]] iff its target is a constructor at creation time — a handler construct trap cannot confer constructability. JsProxy.IsConstructor additionally probed the handler for a construct property, so:

new (new Proxy(() => {}, { construct: () => ({}) }))() // Jint: trap ran, spec/V8: TypeError

The probe (handler.TryGetValue("construct")) could also fire user getters as a side effect of mere IsConstructor checks (e.g. Reflect.construct newTarget validation).

Changes:

  • _isConstructor captured once in the constructor from target.IsConstructor (mirrors how IsCallable is already captured); the property returns the field.
  • [[Construct]]/[[Call]] now check revocation first, so calling/constructing a revoked proxy throws the correct Cannot perform 'construct'/'apply' on a proxy that has been revoked (character-identical to V8) instead of "(intermediate value) is not a constructor/function"; the defensive not-a-constructor/function guards remain (JsProxy always implements the call interfaces, so callers' casts pass even for non-callable targets) with real messages.
  • typeof on revoked proxies is unaffected (IsCallable stays a captured value, per spec: revocation does not remove the slots).

Cross-checked against node 24 / V8 on all new tests. Observable behavior change (construct-trap-on-non-constructor now correctly throws) — worth a release-note line.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EsHuKuE4UihKZp5HYapDHE

@lahma
lahma enabled auto-merge (squash) July 13, 2026 12:54
Per https://tc39.es/ecma262/#sec-proxycreate, a proxy exotic object only
gets a [[Construct]] internal method when its target has one at creation
time - a handler "construct" trap cannot confer constructability. Jint's
JsProxy.IsConstructor instead probed the handler for a "construct"
property, which made `new (new Proxy(() => {}, { construct: ... }))()`
succeed where the spec requires a TypeError, and the handler probe via
TryGetValue could fire user-defined getters as a side effect of a mere
IsConstructor check (e.g. from Reflect.construct newTarget validation or
`class extends`).

IsConstructor is now captured into a readonly field in the constructor
(mirroring the existing IsCallable capture), making probes side-effect
free and keeping the verdict stable after revocation nulls the target -
matching the spec model where the internal-method slots themselves
survive revocation.

Also fixes error ordering in the [[Call]]/[[Construct]] paths: the
revocation check now runs before the callable/constructor check, so
invoking a revoked function proxy reports "Cannot perform 'apply'/
'construct' on a proxy that has been revoked" instead of the misleading
"(intermediate value) is not a function/constructor" (the defensive
checks now use the captured slots and real messages). typeof on revoked
proxies is unaffected since IsCallable stays a captured value.

Cross-checked against Node.js: non-constructor target + construct trap
throws TypeError, constructor target + trap returns the trap result,
Reflect.construct rejects a non-constructor proxy newTarget, and revoked
proxies keep typeof "function" while apply/construct throw the revoked
TypeError with identical wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsHuKuE4UihKZp5HYapDHE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant