Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NativeScript/runtime/AnimationFrame.mm
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ void FireFrame(double timestampSeconds) {
}
entry->scheduled = false;
Local<v8::Function> cb = entry->callback.Get(isolate);
Local<Context> context = cb->GetCreationContextChecked(v8::Isolate::GetCurrent());
Local<Context> context = tns::GetCreationContextOrCurrent(isolate, cb);
Context::Scope contextScope(context);
if (entry->raf) {
Local<Value> argv[] = {v8::Number::New(isolate, performanceMillis)};
Expand Down
12 changes: 8 additions & 4 deletions NativeScript/runtime/ArgConverter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@
return;
}

// Runs inside an ffi closure, where a C++ throw cannot propagate; a revoked
// proxy returns nil.
value = tns::UnwrapProxy(value);
if (value.IsEmpty() || value->IsNullOrUndefined()) {
void* nullPtr = nullptr;
*(ffi_arg*)retValue = (unsigned long)nullPtr;
Expand Down Expand Up @@ -647,7 +650,8 @@
std::vector<Local<Value>> initializerArgs;
std::string constructorTokens;
if (info.Length() == 1 && info[0]->IsObject() && tns::GetValue(isolate, info[0]) == nullptr) {
initializerArgs = GetInitializerArgs(info[0].As<Object>(), constructorTokens);
Local<Value> initializer = tns::UnwrapProxyOrThrow(isolate, info[0]);
initializerArgs = GetInitializerArgs(initializer.As<Object>(), constructorTokens);
}

std::shared_ptr<Caches> cache = Caches::Get(isolate);
Expand Down Expand Up @@ -729,6 +733,8 @@

bool ArgConverter::CanInvoke(Local<Context> context, const TypeEncoding* typeEncoding,
Local<Value> arg) {
// A revoked proxy matches anything so marshalling reports it as such.
arg = tns::UnwrapProxy(arg);
if (arg.IsEmpty() || arg->IsNullOrUndefined()) {
return true;
}
Expand Down Expand Up @@ -807,10 +813,8 @@
std::string& constructorTokens) {
std::vector<Local<Value>> args;
constructorTokens = "";
Local<Context> context;
bool success = obj->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success);
Isolate* isolate = v8::Isolate::GetCurrent();
Local<Context> context = tns::GetCreationContextOrCurrent(isolate, obj);
Local<v8::Array> properties;
if (obj->GetOwnPropertyNames(context).ToLocal(&properties)) {
std::stringstream ss;
Expand Down
23 changes: 23 additions & 0 deletions NativeScript/runtime/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,30 @@ void SetPrivateValue(const v8::Local<v8::Object>& obj, const v8::Local<v8::Strin
v8::Local<v8::Value> GetPrivateValue(const v8::Local<v8::Object>& obj,
const v8::Local<v8::String>& propName);

// Follows a Proxy chain to its innermost target; empty when any link is
// revoked. Non-proxies come back unchanged.
inline v8::Local<v8::Value> UnwrapProxy(v8::Local<v8::Value> value) {
while (!value.IsEmpty() && value->IsProxy()) {
v8::Local<v8::Proxy> proxy = value.As<v8::Proxy>();
if (proxy->IsRevoked()) {
return v8::Local<v8::Value>();
}
value = proxy->GetTarget();
}
return value;
}

// UnwrapProxy for values crossing into native code: a revoked proxy throws a
// NativeScriptException carrying a TypeError.
v8::Local<v8::Value> UnwrapProxyOrThrow(v8::Isolate* isolate, v8::Local<v8::Value> value);

// The object's creation context, or the isolate's current (else main) context
// for objects that have none, such as proxies.
v8::Local<v8::Context> GetCreationContextOrCurrent(v8::Isolate* isolate,
const v8::Local<v8::Object>& obj);

void SetValue(v8::Isolate* isolate, const v8::Local<v8::Object>& obj, BaseDataWrapper* value);
// Resolves through proxies: a proxied wrapper yields its target's wrapper.
BaseDataWrapper* GetValue(v8::Isolate* isolate, const v8::Local<v8::Value>& val);

// What happens when JS touches a wrapper whose native counterpart has already
Expand Down
62 changes: 46 additions & 16 deletions NativeScript/runtime/Helpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -198,24 +198,46 @@
return ok;
}

Local<Value> tns::UnwrapProxyOrThrow(Isolate* isolate, Local<Value> value) {
if (value.IsEmpty() || !value->IsProxy()) {
return value;
}
Local<Value> target = tns::UnwrapProxy(value);
if (target.IsEmpty()) {
std::string message = "Cannot pass a revoked Proxy to native code";
throw NativeScriptException(
isolate, v8::Exception::TypeError(tns::ToV8String(isolate, message)), message);
}
return target;
}

Local<Context> tns::GetCreationContextOrCurrent(Isolate* isolate, const Local<Object>& obj) {
Local<Context> context;
if (obj->GetCreationContext(isolate).ToLocal(&context)) {
return context;
}
context = isolate->GetCurrentContext();
if (context.IsEmpty()) {
context = Caches::Get(isolate)->GetContext();
}
return context;
}

void tns::SetPrivateValue(const Local<Object>& obj, const Local<v8::String>& propName,
const Local<Value>& value) {
Local<Context> context;
bool success = obj->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success);
Isolate* isolate = v8::Isolate::GetCurrent();
Local<Context> context = tns::GetCreationContextOrCurrent(isolate, obj);
Local<Private> privateKey = Private::ForApi(isolate, propName);

bool success = false;
if (!obj->SetPrivate(context, privateKey, value).To(&success) || !success) {
tns::Assert(false, isolate);
}
}

Local<Value> tns::GetPrivateValue(const Local<Object>& obj, const Local<v8::String>& propName) {
Local<Context> context;
bool success = obj->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success);
Isolate* isolate = v8::Isolate::GetCurrent();
Local<Context> context = tns::GetCreationContextOrCurrent(isolate, obj);
Local<Private> privateKey = Private::ForApi(isolate, propName);

Maybe<bool> hasPrivate = obj->HasPrivate(context, privateKey);
Expand Down Expand Up @@ -263,6 +285,13 @@
}

Local<Object> obj = val.As<Object>();
if (obj->IsProxy()) {
Local<Value> target = tns::UnwrapProxy(obj);
if (target.IsEmpty()) {
return nullptr;
}
obj = target.As<Object>();
}
if (obj->InternalFieldCount() > 0) {
Local<Value> field = obj->GetInternalField(0).As<v8::Value>();
if (field.IsEmpty() || field->IsNullOrUndefined() || !field->IsExternal()) {
Expand Down Expand Up @@ -518,12 +547,10 @@ void WriteDebugLine(tns::LogCategory category, const char* message) {
return;
}

Local<Context> context;
bool success = obj->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success, isolate);
Local<Context> context = tns::GetCreationContextOrCurrent(isolate, obj);
Local<Private> privateKey = Private::ForApi(isolate, metadataKey);

success = obj->DeletePrivate(context, privateKey).FromMaybe(false);
bool success = obj->DeletePrivate(context, privateKey).FromMaybe(false);
tns::Assert(success, isolate);
}

Expand All @@ -537,18 +564,21 @@ void WriteDebugLine(tns::LogCategory category, const char* message) {
}

bool tns::IsArrayOrArrayLike(Isolate* isolate, const Local<Value>& value) {
if (value->IsArray()) {
Local<Value> target = tns::UnwrapProxy(value);
if (target.IsEmpty()) {
return false;
}

if (target->IsArray()) {
return true;
}

if (!value->IsObject()) {
if (!target->IsObject()) {
return false;
}

Local<Object> obj = value.As<Object>();
Local<Context> context;
bool success = obj->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success, isolate);
Local<Object> obj = target.As<Object>();
Local<Context> context = tns::GetCreationContextOrCurrent(isolate, obj);
return obj->Has(context, ToV8String(isolate, "length")).FromMaybe(false);
}

Expand Down
16 changes: 11 additions & 5 deletions NativeScript/runtime/Interop.mm
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ inline bool isBool() {
void Interop::WriteTypeValue(Local<Context> context, BaseDataWrapper* typeWrapper, void* dest,
Local<Value> arg) {
Isolate* isolate = v8::Isolate::GetCurrent();
arg = tns::UnwrapProxyOrThrow(isolate, arg);
ValueCache argHelper(arg);
bool isEmptyOrUndefined = arg.IsEmpty() || arg->IsNullOrUndefined();
bool success = false;
Expand Down Expand Up @@ -258,6 +259,9 @@ inline bool isBool() {
void Interop::WriteValue(Local<Context> context, const TypeEncoding* typeEncoding, void* dest,
Local<Value> arg) {
Isolate* isolate = v8::Isolate::GetCurrent();
// Every branch below inspects the value's own type and internal fields,
// which a Proxy hides.
arg = tns::UnwrapProxyOrThrow(isolate, arg);
ExecuteWriteValueDebugValidationsIfInDebug(context, typeEncoding, dest, arg);
ValueCache argHelper(arg);
if (arg.IsEmpty() || arg->IsNullOrUndefined()) {
Expand Down Expand Up @@ -729,6 +733,9 @@ inline bool isBool() {

id Interop::ToObject(Local<Context> context, v8::Local<v8::Value> arg) {
Isolate* isolate = v8::Isolate::GetCurrent();
// Runs inside adapter callbacks invoked by native code, where a C++ throw
// cannot propagate; a revoked proxy reads as nil.
arg = tns::UnwrapProxy(arg);
if (arg.IsEmpty() || arg->IsNullOrUndefined()) {
return nil;
} else if (tns::IsString(arg)) {
Expand Down Expand Up @@ -1559,14 +1566,13 @@ inline bool isBool() {
}

Local<v8::Array> Interop::ToArray(Local<Object> object) {
Isolate* isolate = v8::Isolate::GetCurrent();
object = tns::UnwrapProxyOrThrow(isolate, object).As<Object>();
if (object->IsArray()) {
return object.As<v8::Array>();
}

Local<Context> context;
bool success = object->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success);
Isolate* isolate = v8::Isolate::GetCurrent();
Local<Context> context = tns::GetCreationContextOrCurrent(isolate, object);

Local<v8::Function> sliceFunc;
auto cache = Caches::Get(isolate);
Expand Down Expand Up @@ -1596,7 +1602,7 @@ inline bool isBool() {
Local<Value> sliceArgs[1]{object};

Local<Value> result;
success = sliceFunc->Call(context, object, 1, sliceArgs).ToLocal(&result);
bool success = sliceFunc->Call(context, object, 1, sliceArgs).ToLocal(&result);
tns::Assert(success, isolate);

return result.As<v8::Array>();
Expand Down
73 changes: 62 additions & 11 deletions NativeScript/runtime/MetadataBuilder.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,42 @@

namespace tns {

namespace {

// Swaps a Proxy receiver for the native object at the end of its chain, so the
// call dispatches as if made on that object directly. On failure a TypeError is
// thrown and false returned. `allowClass` admits a class constructor target.
bool ResolveProxyReceiver(Isolate* isolate, Local<Object>& receiver, const char* action,
const char* memberName, bool allowClass) {
if (!receiver->IsProxy()) {
return true;
}

Local<Value> target = tns::UnwrapProxy(receiver);
const char* reason = nullptr;
if (target.IsEmpty()) {
reason = "on a revoked Proxy";
} else if (!(allowClass && target->IsFunction())) {
BaseDataWrapper* wrapper =
target.As<Object>()->InternalFieldCount() > 0 ? tns::GetValue(isolate, target) : nullptr;
if (wrapper == nullptr || (wrapper->Type() != WrapperType::ObjCObject &&
wrapper->Type() != WrapperType::ObjCAllocObject)) {
reason = "on a Proxy whose target is not a native object";
}
}

if (reason != nullptr) {
std::string message = std::string("Cannot ") + action + " '" + memberName + "' " + reason;
isolate->ThrowException(Exception::TypeError(tns::ToV8String(isolate, message)));
return false;
}

receiver = target.As<Object>();
return true;
}

} // namespace

void MetadataBuilder::RegisterConstantsOnGlobalObject(Isolate* isolate,
Local<ObjectTemplate> globalTemplate,
bool isWorkerThread) {
Expand Down Expand Up @@ -251,14 +287,14 @@ throw NativeScriptException(
Local<Context> context = isolate->GetCurrentContext();
tns::Assert(info.Length() == 2, isolate);

Local<Object> arg1 = info[0].As<Object>();
Local<Object> arg2 = info[1].As<Object>();

if (arg1.IsEmpty() || !arg1->IsObject() || arg1->IsNullOrUndefined() || arg2.IsEmpty() ||
!arg2->IsObject() || arg2->IsNullOrUndefined()) {
Local<Value> value1 = tns::UnwrapProxy(info[0]);
Local<Value> value2 = tns::UnwrapProxy(info[1]);
if (value1.IsEmpty() || !value1->IsObject() || value2.IsEmpty() || !value2->IsObject()) {
info.GetReturnValue().Set(false);
return;
}
Local<Object> arg1 = value1.As<Object>();
Local<Object> arg2 = value2.As<Object>();

BaseDataWrapper* wrapper = tns::GetValue(isolate, info.This());
if (wrapper == nullptr || wrapper->Type() != WrapperType::StructType) {
Expand Down Expand Up @@ -472,6 +508,9 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta
void MetadataBuilder::ToStringFunctionCallback(const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
Local<Object> thiz = info.This();
if (!ResolveProxyReceiver(isolate, thiz, "call native method", "toString", false)) {
return;
}
BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz);

if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCObject) {
Expand Down Expand Up @@ -764,15 +803,19 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta
CacheItem<MethodMeta>* item = static_cast<CacheItem<MethodMeta>*>(
info.Data().As<External>()->Value(v8::kExternalPointerTypeTagDefault));

bool instanceMethod = info.This()->InternalFieldCount() > 0;
Local<Object> thiz = info.This();
if (!ResolveProxyReceiver(isolate, thiz, "call native method", item->meta_->jsName(), true)) {
return;
}

bool instanceMethod = thiz->InternalFieldCount() > 0;
V8FunctionCallbackArgs args(info);

// Only the class-side call rewrites the name, so the common path reads
// item->className_ in place rather than copying it on every invocation.
const std::string* className = &item->className_;
std::string classWrapperName;

Local<Object> thiz = info.This();
if (thiz->IsFunction()) {
if (BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz)) {
ObjCClassWrapper* classWrapper = static_cast<ObjCClassWrapper*>(wrapper);
Expand All @@ -784,7 +827,7 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta
Local<Context> context = isolate->GetCurrentContext();
Local<Value> result =
instanceMethod
? MetadataBuilder::InvokeMethod(context, item->meta_, info.This(), args, *className, true)
? MetadataBuilder::InvokeMethod(context, item->meta_, thiz, args, *className, true)
: MetadataBuilder::InvokeMethod(context, item->meta_, Local<Object>(), args, *className,
true);

Expand All @@ -794,15 +837,19 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta
}

void MetadataBuilder::PropertyGetterCallback(const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
CacheItem<PropertyMeta>* item = static_cast<CacheItem<PropertyMeta>*>(
info.Data().As<External>()->Value(v8::kExternalPointerTypeTagDefault));
Local<Object> receiver = info.This();
if (!ResolveProxyReceiver(isolate, receiver, "read native property", item->meta_->jsName(),
false)) {
return;
}

if (receiver->InternalFieldCount() < 1) {
return;
}

Isolate* isolate = info.GetIsolate();
CacheItem<PropertyMeta>* item = static_cast<CacheItem<PropertyMeta>*>(
info.Data().As<External>()->Value(v8::kExternalPointerTypeTagDefault));
if (!item->meta_->hasGetter()) {
Local<Value> error = Exception::Error(tns::ToV8String(isolate, "Property is not readable."));
isolate->ThrowException(error);
Expand Down Expand Up @@ -830,6 +877,10 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta
}

Local<Object> receiver = info.This();
if (!ResolveProxyReceiver(isolate, receiver, "set native property", item->meta_->jsName(),
false)) {
return;
}
Local<Value> value = info[0];
V8SimpleValueArgs args(value);
Local<Context> context = isolate->GetCurrentContext();
Expand Down
4 changes: 0 additions & 4 deletions NativeScript/runtime/Reference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,6 @@ void Reference::RegisterToStringMethod(Local<Context> context,
}

Reference::DataPair Reference::GetDataPair(Local<Object> obj) {
Local<Context> context;
bool success =
obj->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success);
Isolate* isolate = v8::Isolate::GetCurrent();
BaseDataWrapper* wrapper =
tns::GetValueOrReport(isolate, obj, "Reference indexed access");
Expand Down
Loading
Loading