commit a8e957529a2aeb0af1aaac76a56906af1b1f554b
parent 5f39ebf46d97002cd0b938a3bc9754a0d4c2b5aa
Author: Iain Ireland <iireland@mozilla.com>
Date: Wed, 22 Oct 2025 18:11:07 +0000
Bug 1995289: Don't call scripted proxy get trap for private fields r=jandem
Differential Revision: https://phabricator.services.mozilla.com/D269297
Diffstat:
5 files changed, 73 insertions(+), 0 deletions(-)
diff --git a/js/src/jit-test/tests/bug1995289.js b/js/src/jit-test/tests/bug1995289.js
@@ -0,0 +1,42 @@
+class Identity {
+ constructor(target) {
+ return target;
+ }
+}
+
+class TargetHandler extends Identity {
+ #proxy;
+
+ constructor(target, proxy) {
+ super(target);
+ this.#proxy = proxy;
+ }
+
+ static getProxy(obj) {
+ return obj.#proxy;
+ }
+}
+
+class ReactiveHandler extends TargetHandler {
+ #priv;
+
+ constructor(target, proxy) {
+ // Both the target and the proxy have the "#proxy" private field that stores the reference to the proxy
+ new TargetHandler(target, proxy);
+ super(proxy, proxy);
+ }
+
+ get(t, k, r) { throw "oops"; }
+
+ defineProperty(t, k, desc) {
+ ReactiveHandler.getProxy(t).#priv;
+ return Reflect.defineProperty(t, k, desc);
+ }
+}
+
+const target = {};
+const proxy = new ReactiveHandler(target, new Proxy(target, ReactiveHandler.prototype));
+
+for (var i = 0; i < 20; i++) {
+ proxy[i] = i;
+}
diff --git a/js/src/jit/BaselineCacheIRCompiler.cpp b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -3766,6 +3766,16 @@ bool BaselineCacheIRCompiler::emitCallScriptedProxyGetShared(
stubFrame.storeTracedValue(masm, target);
if constexpr (std::is_same_v<IdType, ValOperandId>) {
stubFrame.storeTracedValue(masm, idVal);
+# ifdef DEBUG
+ Label notPrivateSymbol;
+ masm.branchTestSymbol(Assembler::NotEqual, idVal, ¬PrivateSymbol);
+ masm.unboxSymbol(idVal, scratch);
+ masm.branch32(
+ Assembler::NotEqual, Address(scratch, JS::Symbol::offsetOfCode()),
+ Imm32(uint32_t(JS::SymbolCode::PrivateNameSymbol)), ¬PrivateSymbol);
+ masm.assumeUnreachable("Unexpected private field in callScriptedProxy");
+ masm.bind(¬PrivateSymbol);
+# endif
} else {
// We need to either trace the id here or grab the ICStubReg back from
// FramePointer + sizeof(void*) after the call in order to load it again.
diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp
@@ -1677,6 +1677,16 @@ AttachDecision GetPropIRGenerator::tryAttachScriptedProxy(
}
}
+ // Private fields of proxies are stored on the expando, and don't fire traps.
+ // Note that we don't need to guard against this in CacheIR, because there's
+ // no way to generate bytecode that will *sometimes* load a private field;
+ // either it's accessing a private field, or it isn't. We assert in the
+ // CacheIR implementation of callScriptedProxy(GetResult|GetByValueResult)
+ // that we don't see any private field symbols.
+ if (idVal_.isSymbol() && idVal_.toSymbol()->isPrivateName()) {
+ return AttachDecision::NoAction;
+ }
+
JSObject* handlerObj = ScriptedProxyHandler::handlerObject(obj);
if (!handlerObj) {
return AttachDecision::NoAction;
diff --git a/js/src/jit/IonCacheIRCompiler.cpp b/js/src/jit/IonCacheIRCompiler.cpp
@@ -1056,6 +1056,16 @@ bool IonCacheIRCompiler::emitCallScriptedProxyGetShared(
if constexpr (std::is_same_v<IdType, ValOperandId>) {
// Same for the id, assuming it's not baked in
storeTracedValue(masm, idVal);
+# ifdef DEBUG
+ Label notPrivateSymbol;
+ masm.branchTestSymbol(Assembler::NotEqual, idVal, ¬PrivateSymbol);
+ masm.unboxSymbol(idVal, scratch);
+ masm.branch32(
+ Assembler::NotEqual, Address(scratch, JS::Symbol::offsetOfCode()),
+ Imm32(uint32_t(JS::SymbolCode::PrivateNameSymbol)), ¬PrivateSymbol);
+ masm.assumeUnreachable("Unexpected private field in callScriptedProxy");
+ masm.bind(¬PrivateSymbol);
+# endif
}
uint32_t framePushedBeforeArgs = masm.framePushed();
diff --git a/js/src/vm/SymbolType.h b/js/src/vm/SymbolType.h
@@ -110,6 +110,7 @@ class Symbol
#endif
static constexpr size_t offsetOfHash() { return offsetof(Symbol, hash_); }
+ static constexpr size_t offsetOfCode() { return offsetof(Symbol, code_); }
};
} /* namespace JS */