Skip to content

Commit f97849d

Browse files
committed
fix(runtime): key wrapper state by the Proxy target in SetValue/DeleteValue
GetValue resolves through proxies while SetValue and DeleteValue addressed the object they were handed, so __releaseNativeCounterpart(proxy) deleted the target's wrapper and then cleared the slot on the Proxy, leaving the target's internal field dangling. All three now resolve to the same target. ResolveProxyReceiver accepts a function target only when it carries an ObjCClass wrapper; a proxied plain function is a TypeError instead of a static call on the metadata class.
1 parent 1c016d4 commit f97849d

5 files changed

Lines changed: 43 additions & 11 deletions

File tree

‎NativeScript/runtime/Helpers.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,9 @@ v8::Local<v8::Value> UnwrapProxyOrThrow(v8::Isolate* isolate, v8::Local<v8::Valu
280280
v8::Local<v8::Context> GetCreationContextOrCurrent(v8::Isolate* isolate,
281281
const v8::Local<v8::Object>& obj);
282282

283+
// Set/Get/DeleteValue all resolve through proxies: wrapper state is keyed by
284+
// the Proxy target, so a proxied wrapper yields its target's wrapper.
283285
void SetValue(v8::Isolate* isolate, const v8::Local<v8::Object>& obj, BaseDataWrapper* value);
284-
// Resolves through proxies: a proxied wrapper yields its target's wrapper.
285286
BaseDataWrapper* GetValue(v8::Isolate* isolate, const v8::Local<v8::Value>& val);
286287

287288
// What happens when JS touches a wrapper whose native counterpart has already

‎NativeScript/runtime/Helpers.mm‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,19 @@ throw NativeScriptException(
265265
return false;
266266
}
267267

268-
void tns::SetValue(Isolate* isolate, const Local<Object>& obj, BaseDataWrapper* value) {
269-
if (obj.IsEmpty() || obj->IsNullOrUndefined()) {
268+
void tns::SetValue(Isolate* isolate, const Local<Object>& val, BaseDataWrapper* value) {
269+
if (val.IsEmpty() || val->IsNullOrUndefined()) {
270270
return;
271271
}
272272

273+
// Wrapper state lives on the Proxy target so that it is found by the same
274+
// identity GetValue resolves to.
275+
Local<Value> target = tns::UnwrapProxy(val);
276+
if (target.IsEmpty()) {
277+
return;
278+
}
279+
Local<Object> obj = target.As<Object>();
280+
273281
Local<External> ext = External::New(isolate, value, v8::kExternalPointerTypeTagDefault);
274282

275283
if (obj->InternalFieldCount() > 0) {
@@ -531,11 +539,12 @@ void WriteDebugLine(tns::LogCategory category, const char* message) {
531539
}
532540

533541
void tns::DeleteValue(Isolate* isolate, const Local<Value>& val) {
534-
if (val.IsEmpty() || val->IsNullOrUndefined() || !val->IsObject()) {
542+
Local<Value> target = tns::UnwrapProxy(val);
543+
if (target.IsEmpty() || target->IsNullOrUndefined() || !target->IsObject()) {
535544
return;
536545
}
537546

538-
Local<Object> obj = val.As<Object>();
547+
Local<Object> obj = target.As<Object>();
539548
if (obj->InternalFieldCount() > 0) {
540549
obj->SetInternalField(0, v8::Undefined(isolate));
541550
return;

‎NativeScript/runtime/MetadataBuilder.mm‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ bool ResolveProxyReceiver(Isolate* isolate, Local<Object>& receiver, const char*
3434
const char* reason = nullptr;
3535
if (target.IsEmpty()) {
3636
reason = "on a revoked Proxy";
37-
} else if (!(allowClass && target->IsFunction())) {
38-
BaseDataWrapper* wrapper =
39-
target.As<Object>()->InternalFieldCount() > 0 ? tns::GetValue(isolate, target) : nullptr;
40-
if (wrapper == nullptr || (wrapper->Type() != WrapperType::ObjCObject &&
41-
wrapper->Type() != WrapperType::ObjCAllocObject)) {
37+
} else {
38+
BaseDataWrapper* wrapper = tns::GetValue(isolate, target);
39+
bool isClass = allowClass && target->IsFunction() && wrapper != nullptr &&
40+
wrapper->Type() == WrapperType::ObjCClass;
41+
bool isInstance = target.As<Object>()->InternalFieldCount() > 0 && wrapper != nullptr &&
42+
(wrapper->Type() == WrapperType::ObjCObject ||
43+
wrapper->Type() == WrapperType::ObjCAllocObject);
44+
if (!isClass && !isInstance) {
4245
reason = "on a Proxy whose target is not a native object";
4346
}
4447
}

‎NativeScript/runtime/ObjectManager.mm‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,9 @@ void DisposeHandle(v8::Isolate* isolate,
349349
return;
350350
}
351351

352-
Local<Value> value = info[0];
352+
// The lookup, the Instances key and the final SetValue must all address the
353+
// same object, so a Proxy is resolved once here.
354+
Local<Value> value = tns::UnwrapProxy(info[0]);
353355
BaseDataWrapper* wrapper = tns::GetValue(isolate, value);
354356

355357
if (wrapper == nullptr) {

‎TestRunner/app/tests/ProxyReceiverTests.js‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,23 @@ describe(module.id, function () {
140140
expect(function () {
141141
findAccessor(NSOperation.prototype, "name").set.call(proxy, "x");
142142
}).toThrowError(TypeError, /not a native object/);
143+
expect(function () {
144+
NSString.stringWithString.call(new Proxy(function () {}, {}), "x");
145+
}).toThrowError(TypeError, /not a native object/);
146+
});
147+
148+
it("releases the native counterpart of the proxy target", function () {
149+
var target = NSMutableString.alloc().init();
150+
var proxy = new Proxy(target, {});
151+
152+
__releaseNativeCounterpart(proxy);
153+
154+
expect(function () {
155+
__releaseNativeCounterpart(target);
156+
}).toThrowError(/not a native wrapper/);
157+
expect(function () {
158+
__releaseNativeCounterpart(proxy);
159+
}).toThrowError(/not a native wrapper/);
143160
});
144161

145162
it("consults proxy traps for the lookup and calls the target natively", function () {

0 commit comments

Comments
 (0)