Skip to content

Unwrap Proxy receivers and arguments before Java dispatch - #2061

Draft
edusperoni wants to merge 1 commit into
mainfrom
fix/proxy-receivers
Draft

edusperoni wants to merge 1 commit into
mainfrom
fix/proxy-receivers

Conversation

@edusperoni

Copy link
Copy Markdown
Collaborator

Description

Java object wrappers wrapped in a JS Proxy — which is what Vue 3's reactive() does to anything stored in component data() — expose no internal fields and no creation context, so the runtime could not find the Java object behind them. Reproduced on main with a bare new Proxy(new java.lang.StringBuilder(), {}):

Call through a Proxy Before
proxy.append("x") process abort: # Fatal error in v8::Object::GetInternalField() / Internal field out of bounds (MetadataNode::MethodCallback reads CallSuper off info.This())
any private-value lookup on a Proxy (V8GetPrivateValue/V8SetPrivateValue) process abort in GetCreationContext().ToLocalChecked() — a Proxy never has a creation context

Vue 3 users have been working around this with markRaw/toRaw.

Changes

  • ObjectManager::UnwrapProxy follows Proxy-of-Proxy chains to the target (null when revoked). GetJSInstanceInfo — and therefore every GetJavaObjectByJsObject caller (arg converters, FieldAccessor, ArrayElementAccessor, instanceof, Interop, NativeScriptException, ArrayHelper) — resolves through it.
  • Method, field, super, class and null accessors dispatch on the Proxy target exactly as on a direct receiver. A revoked Proxy, or one whose target is not a Java object for an instance member, throws a catchable TypeError. CallSuper is read only from objects that actually carry the field, so a plain object as this now throws the existing "no Java counterpart" error instead of aborting.
  • JsArgConverter, JsArgToArrayConverter, JSToJavaConverter and MethodCache::GetType marshal the target of a Proxy argument, so proxied Java objects and proxied JS arrays (and their elements) convert as their targets do; a revoked Proxy argument is a conversion error.
  • Implementation objects passed to extend() / interface constructors are unwrapped.
  • GetCreationContextOrCurrent replaces every GetCreationContext(...).ToLocalChecked(); Symbol.hasInstance gets the standard try/catch → ReThrowToV8; URLImpl::GetPointer checks the internal-field count first.

Proxy traps are consulted only for the JS-side lookup; the Java call runs on the target.

Does your commit message include the wording below to reference a specific issue in this repo?

No linked issue (reported through nativescript-vue; root-caused in the runtime).

Related Pull Requests

Does your pull request have unit tests?

Yes — test-app/app/src/main/assets/app/tests/testProxyReceivers.js, 16 specs (instance/trap/nested/static receivers, public field get/set, string coercion, proxied object/array/element arguments, array element assignment, revoked receiver and argument, non-Java target, plain-object receiver, instanceof, extended-class override + super). Full suite on the emulator: 1258 passed / 0 failed (main: 1242 / 0).

Shared-submodule versions of these specs can follow once both runtimes land.

Reactive frameworks (Vue's reactive()) wrap Java object wrappers in a JS
Proxy. A Proxy has no internal fields and no creation context, so the first
Java method call on one aborted the process in GetInternalField, and any
private-value lookup on one aborted in GetCreationContext.

- ObjectManager::UnwrapProxy follows Proxy-of-Proxy chains to the target
  (null when revoked); GetJSInstanceInfo, and therefore every
  GetJavaObjectByJsObject caller, resolves through it.
- Method, field, 'super', 'class' and 'null' accessors dispatch on the
  Proxy target. A revoked Proxy, or one whose target is not a Java object
  for an instance member, throws a TypeError.
- JsArgConverter, JsArgToArrayConverter, JSToJavaConverter and
  MethodCache::GetType marshal the target of a Proxy argument, so proxied
  Java objects and proxied JS arrays convert as their targets do; a revoked
  Proxy argument is a conversion error.
- Implementation objects passed to extend() or an interface constructor
  are unwrapped.
- CallSuper is read only from objects that have the field, so a plain
  object as `this` throws instead of aborting.
- Creation-context lookups fall back to the current context instead of
  aborting; Symbol.hasInstance catches native exceptions; URLImpl checks
  the field count before reading its pointer.
@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
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