Skip to content

Commit 731652a

Browse files
committed
[JSC] Fix Structure transition wrong checking issues
https://bugs.webkit.org/show_bug.cgi?id=323085 rdar://186339983 Reviewed by Marcus Plutowski and Keith Miller. Adding MonoProto condition to isHandlerPrototypeChainCacheable. Otherwise we may get a structure which has differnt [[Prototype]] (due to PolyProto). In that case, worst case is having jsNull as a JSObject*, so crash issue with a deterministic address. Also updating oldStructure by passing it to CommonSlowPaths::putDirectWithReify. This is not an actual issue, but it makes oldStructure more solid. Test: JSTests/stress/proxy-handler-traps-cache-poly-proto-handler.js * JSTests/stress/proxy-handler-traps-cache-poly-proto-handler.js: Added. (shouldBe): (makePolyProtoObject.foo.C): (makePolyProtoObject.foo): (return.foo): (shouldBe.handler): * Source/JavaScriptCore/jit/JITOperations.cpp: (JSC::directPutByValOptimize): * Source/JavaScriptCore/llint/LLIntSlowPaths.cpp: (JSC::LLInt::LLINT_SLOW_PATH_DECL): * Source/JavaScriptCore/runtime/ProxyObject.cpp: (JSC::ProxyObject::getHandlerTrap): * Source/JavaScriptCore/runtime/ProxyObject.h: (JSC::ProxyObject::isHandlerTrapsCacheValid): Canonical link: https://commits.webkit.org/320251@main
1 parent 8c5b747 commit 731652a

5 files changed

Lines changed: 83 additions & 5 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
function shouldBe(actual, expected) {
2+
if (actual !== expected)
3+
throw new Error(`bad value: ${actual}`);
4+
}
5+
6+
function makePolyProtoObject() {
7+
function foo() {
8+
class C {
9+
constructor() { this._field = 42; }
10+
};
11+
return new C;
12+
}
13+
for (let i = 0; i < 15; ++i)
14+
foo();
15+
return foo();
16+
}
17+
18+
// A poly-proto object stores its prototype in the object rather than in its structure, so
19+
// Object.setPrototypeOf leaves its StructureID untouched. The Proxy handler-traps cache must not
20+
// treat a matching StructureID as proof that the prototype is still an object.
21+
22+
{
23+
let handler = makePolyProtoObject();
24+
Object.setPrototypeOf(handler, Object.prototype);
25+
handler.get = (target, key) => `got ${key}`;
26+
27+
let proxy = new Proxy({ }, handler);
28+
shouldBe(proxy.foo, "got foo");
29+
30+
Object.setPrototypeOf(handler, null);
31+
shouldBe(proxy.foo, "got foo");
32+
shouldBe(proxy.bar, "got bar");
33+
}
34+
35+
{
36+
let handler = makePolyProtoObject();
37+
Object.setPrototypeOf(handler, Object.prototype);
38+
39+
let proxy = new Proxy({ a: 1 }, handler);
40+
shouldBe(Object.keys(proxy).join(), "a");
41+
42+
Object.setPrototypeOf(handler, null);
43+
shouldBe(Object.keys(proxy).join(), "a");
44+
45+
handler.ownKeys = () => ["b"];
46+
handler.getOwnPropertyDescriptor = () => ({ value: 2, enumerable: true, configurable: true });
47+
shouldBe(Object.keys(proxy).join(), "b");
48+
}
49+
50+
{
51+
let handler = makePolyProtoObject();
52+
Object.setPrototypeOf(handler, Object.prototype);
53+
handler.get = function (target, key) {
54+
Object.setPrototypeOf(handler, null);
55+
return 42;
56+
};
57+
58+
let proxy = new Proxy({ }, handler);
59+
shouldBe(proxy.foo, 42);
60+
shouldBe(proxy.foo, 42);
61+
}
62+
63+
// A poly-proto handler whose prototype gains a trap must pick the new trap up.
64+
{
65+
let handler = makePolyProtoObject();
66+
Object.setPrototypeOf(handler, Object.prototype);
67+
68+
let proxy = new Proxy({ foo: 1 }, handler);
69+
shouldBe(proxy.foo, 1);
70+
71+
Object.setPrototypeOf(handler, { get: () => "from prototype" });
72+
shouldBe(proxy.foo, "from prototype");
73+
}

‎Source/JavaScriptCore/jit/JITOperations.cpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,8 +1831,8 @@ static ALWAYS_INLINE void directPutByValOptimize(JSGlobalObject* globalObject, C
18311831
AccessType accessType = static_cast<AccessType>(propertyCache->accessType);
18321832
PutPropertySlot slot(baseValue, isStrict, codeBlock->putByIdContext());
18331833

1834-
Structure* structure = CommonSlowPaths::originalStructureBeforePut(baseValue);
1835-
CommonSlowPaths::putDirectWithReify(vm, globalObject, baseObject, identifier, value, slot);
1834+
Structure* structure = nullptr;
1835+
CommonSlowPaths::putDirectWithReify(vm, globalObject, baseObject, identifier, value, slot, &structure);
18361836

18371837
RETURN_IF_EXCEPTION(scope, void());
18381838

‎Source/JavaScriptCore/llint/LLIntSlowPaths.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,7 @@ LLINT_SLOW_PATH_DECL(slow_path_put_by_id)
10791079

10801080
Structure* oldStructure = baseValue.isCell() ? baseValue.asCell()->structure() : nullptr;
10811081
if (bytecode.m_flags.isDirect())
1082-
CommonSlowPaths::putDirectWithReify(vm, globalObject, asObject(baseValue), ident, getOperand(callFrame, bytecode.m_value), slot);
1082+
CommonSlowPaths::putDirectWithReify(vm, globalObject, asObject(baseValue), ident, getOperand(callFrame, bytecode.m_value), slot, &oldStructure);
10831083
else
10841084
baseValue.putInline(globalObject, ident, getOperand(callFrame, bytecode.m_value), slot);
10851085
LLINT_CHECK_EXCEPTION();

‎Source/JavaScriptCore/runtime/ProxyObject.cpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ JSObject* ProxyObject::getHandlerTrap(JSGlobalObject* globalObject, JSObject* ha
124124
bool isSlotCacheable = slot.isUnset() || (slot.isCacheableValue() && slot.slotBase() == handler);
125125
if (isSlotCacheable) {
126126
JSValue handlerPrototype = handler->getPrototypeDirect();
127-
bool isHandlerPrototypeChainCacheable = handler->type() == FinalObjectType && !handler->structure()->isDictionary()
127+
// isHandlerTrapsCacheValid treats a matching handler StructureID as proof that the prototype is
128+
// unchanged, which only holds for mono-proto: setPrototypeOf on a poly-proto object keeps its structure.
129+
bool isHandlerPrototypeChainCacheable = handler->type() == FinalObjectType && handler->structure()->hasMonoProto() && !handler->structure()->isDictionary()
128130
&& handlerPrototype.inherits<ObjectPrototype>() && !asObject(handlerPrototype)->structure()->isDictionary();
129131
if (isHandlerPrototypeChainCacheable) {
130132
ASSERT(slot.cachedOffset() != emptyHandlerTrapCache);

‎Source/JavaScriptCore/runtime/ProxyObject.h‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ class ProxyObject final : public JSInternalFieldObjectImpl<2> {
144144

145145
ALWAYS_INLINE bool ProxyObject::isHandlerTrapsCacheValid(JSObject* handler)
146146
{
147-
return handler->structureID() == m_handlerStructureID.value() && asObject(handler->getPrototypeDirect())->structureID() == m_handlerPrototypeStructureID.value();
147+
if (handler->structureID() != m_handlerStructureID.value())
148+
return false;
149+
ASSERT(handler->structure()->hasMonoProto());
150+
return asObject(handler->getPrototypeDirect())->structureID() == m_handlerPrototypeStructureID.value();
148151
}
149152

150153
} // namespace JSC

0 commit comments

Comments
 (0)