Skip to content
Open
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
4 changes: 3 additions & 1 deletion NativeScript/runtime/ArgConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ class ArgConverter {
static const ProtocolMeta* FindProtocolMeta(Protocol* protocol);
static void MethodCallback(ffi_cif* cif, void* retValue, void** argValues,
void* userData);
// returnSize is the ABI size of the return slot (cif->rtype->size); an
// empty, null or undefined value zeroes all of it.
static void SetValue(v8::Local<v8::Context> context, void* retValue,
v8::Local<v8::Value> value,
size_t returnSize, v8::Local<v8::Value> value,
const TypeEncoding* typeEncoding);
// Returns (lazily creating) the per-isolate interop.escapeException brand
// private stored in Caches. Empty handle if the isolate cache is invalid.
Expand Down
23 changes: 14 additions & 9 deletions NativeScript/runtime/ArgConverter.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "ArgConverter.h"
#include <Foundation/Foundation.h>
#include <algorithm>
#include <sstream>
#include "DictionaryAdapter.h"
#include "Helpers.h"
Expand Down Expand Up @@ -434,7 +435,7 @@
if (!success) {
memset(retValue, 0, cif->rtype->size);
} else {
ArgConverter::SetValue(context, retValue, result, data->typeEncoding_);
ArgConverter::SetValue(context, retValue, cif->rtype->size, result, data->typeEncoding_);
}
} else {
memset(retValue, 0, cif->rtype->size);
Expand All @@ -446,15 +447,18 @@
}
}

void ArgConverter::SetValue(Local<Context> context, void* retValue, Local<Value> value,
const TypeEncoding* typeEncoding) {
void ArgConverter::SetValue(Local<Context> context, void* retValue, size_t returnSize,
Local<Value> value, const TypeEncoding* typeEncoding) {
if (typeEncoding->type == BinaryTypeEncodingType::VoidEncoding) {
return;
}

// Runs inside an ffi closure, where a C++ throw cannot propagate; a revoked
// proxy returns nil. libffi sizes the return slot to at least ffi_arg even
// for narrower types, and the full slot must be written.
value = tns::UnwrapProxy(value);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (value.IsEmpty() || value->IsNullOrUndefined()) {
void* nullPtr = nullptr;
*(ffi_arg*)retValue = (unsigned long)nullPtr;
memset(retValue, 0, std::max(returnSize, sizeof(ffi_arg)));
return;
}

Expand Down Expand Up @@ -647,7 +651,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 +734,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 +814,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
2 changes: 1 addition & 1 deletion NativeScript/runtime/ClassBuilder.mm
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ void ScopeClassNameToIsolate(std::string& name, int isolateId) {
memset(retValue, 0, cif->rtype->size);
} else {
const TypeEncoding* typeEncoding = context->meta_->getter()->encodings()->first();
ArgConverter::SetValue(v8Context, retValue, res, typeEncoding);
ArgConverter::SetValue(v8Context, retValue, cif->rtype->size, res, typeEncoding);
}
}
if (pendingThrow != nil) {
Expand Down
24 changes: 24 additions & 0 deletions NativeScript/runtime/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +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);

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

Expand Down
79 changes: 59 additions & 20 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 @@ -243,11 +265,19 @@
return false;
}

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

// Wrapper state lives on the Proxy target so that it is found by the same
// identity GetValue resolves to.
Local<Value> target = tns::UnwrapProxy(val);
if (target.IsEmpty()) {
return;
}
Local<Object> obj = target.As<Object>();

Local<External> ext = External::New(isolate, value, v8::kExternalPointerTypeTagDefault);

if (obj->InternalFieldCount() > 0) {
Expand All @@ -263,6 +293,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 @@ -502,11 +539,12 @@ void WriteDebugLine(tns::LogCategory category, const char* message) {
}

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

Local<Object> obj = val.As<Object>();
Local<Object> obj = target.As<Object>();
if (obj->InternalFieldCount() > 0) {
obj->SetInternalField(0, v8::Undefined(isolate));
return;
Expand All @@ -518,12 +556,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 +573,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
Loading
Loading