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
36 changes: 36 additions & 0 deletions NativeScript/ffi/objc/hermes/NativeApiJsi.mm
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,42 @@ throw JSError(runtime,
});
}

#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_RUNTIME_TEARDOWN 1

// Runtime data is destroyed with the runtime while its Values can still be
// released: Hermes frees it before tearing down its heap, and jsi's default
// implementation frees it from a finalizer of that runtime's own global.
class NativeApiRuntimeBridges final {
public:
~NativeApiRuntimeBridges() {
for (auto& bridge : bridges_) {
bridge->releaseRuntimeValues();
}
}

void add(std::shared_ptr<NativeApiBridge> bridge) {
bridges_.push_back(std::move(bridge));
}

private:
std::vector<std::shared_ptr<NativeApiBridge>> bridges_;
};

constexpr facebook::jsi::UUID kNativeApiRuntimeBridgesUUID{
0x5f0e2c71, 0x8b3d, 0x4a6e, 0x9c41, 0x2d7a6b13e8f4};

void BindNativeApiBridgeToRuntime(Runtime& runtime,
std::shared_ptr<NativeApiBridge> bridge) {
bridge->setOwnerRuntime(runtime);
auto bridges = std::static_pointer_cast<NativeApiRuntimeBridges>(
runtime.getRuntimeData(kNativeApiRuntimeBridgesUUID));
if (bridges == nullptr) {
bridges = std::make_shared<NativeApiRuntimeBridges>();
runtime.setRuntimeData(kNativeApiRuntimeBridgesUUID, bridges);
}
bridges->add(std::move(bridge));
}

} // namespace

#include "../shared/bridge/Install.mm"
Expand Down
30 changes: 28 additions & 2 deletions NativeScript/ffi/objc/shared/bridge/Callbacks.mm
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ throw JSError(runtime,
}

~NativeApiCallback() {
if (bridge_ != nullptr) {
bridge_->releaseOrAbandonValue(function_);
}
if (closure_ != nullptr) {
ffi_closure_free(closure_);
closure_ = nullptr;
Expand Down Expand Up @@ -483,7 +486,7 @@ void retainBlockCopy(const void* blockPointer) {
}
auto self = shared_from_this();
if (bridge_ != nullptr && runtime_ != nullptr && function_ != nullptr &&
blockPointer != nullptr) {
blockPointer != nullptr && !bridge_->runtimeValuesReleased()) {
bridge_->rememberRoundTripValue(*runtime_, blockPointer,
Value(*runtime_, *function_), false,
roundTripValidationKey_);
Expand Down Expand Up @@ -553,7 +556,7 @@ bool releaseBlockCopy(const void* blockPointer) {
}

if (bridge != nullptr && runtime != nullptr &&
pointerToForget != nullptr) {
pointerToForget != nullptr && !bridge->runtimeValuesReleased()) {
NativeApiRuntimeScope runtimeScope(*runtime);
bridge->forgetRoundTripValue(*runtime, pointerToForget);
}
Expand All @@ -577,6 +580,9 @@ bool releaseBlockCopy(const void* blockPointer) {
}

void invoke(void* ret, void* args[]) {
if (runtimeDestroyed(ret)) {
return;
}
if (runtime_ == nullptr || function_ == nullptr || signature_ == nullptr) {
throwNativeApiCallbackException("Invalid callback.");
}
Expand Down Expand Up @@ -735,7 +741,27 @@ void invoke(void* ret, void* args[]) {
}

private:
// A native caller can keep a block or method implementation past its
// runtime (e.g. across a JavaScript reload); it gets a zero result instead.
bool runtimeDestroyed(void* ret) {
if (bridge_ == nullptr || !bridge_->runtimeValuesReleased()) {
return false;
}
std::string name = signature_ != nullptr ? signature_->selectorName : "";
if (name.empty()) {
name = block_ ? "block" : "native callback";
}
NSLog(@"NativeScript: %s called after its JavaScript runtime was "
@"destroyed; returning zero/nil.",
name.c_str());
zeroReturnValue(ret);
return true;
}

void invokeOnCurrentThread(void* ret, void* args[], std::string* error) {
if (runtimeDestroyed(ret)) {
return;
}
try {
NativeApiRuntimeScope runtimeScope(*runtime_);
size_t nativeArgOffset = signature_->implicitArgumentCount;
Expand Down
2 changes: 2 additions & 0 deletions NativeScript/ffi/objc/shared/bridge/HostObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ explicit NativeApiHostObject(std::shared_ptr<NativeApiBridge> bridge)
: bridge_(std::move(bridge)) {}

Value get(Runtime& runtime, const PropNameID& name) override {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
if (property == "runtime") {
return makeString(runtime, NATIVESCRIPT_NATIVE_API_RUNTIME_NAME);
Expand Down Expand Up @@ -507,6 +508,7 @@ throw JSError(runtime,
}

std::vector<PropNameID> getPropertyNames(Runtime& runtime) override {
bridge_->requireOwnerRuntime(runtime);
std::vector<PropNameID> names;
names.reserve(11);
addPropertyName(runtime, names, "runtime");
Expand Down
6 changes: 6 additions & 0 deletions NativeScript/ffi/objc/shared/bridge/Install.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#ifndef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_RUNTIME_TEARDOWN
inline void BindNativeApiBridgeToRuntime(Runtime&,
std::shared_ptr<NativeApiBridge>) {}
#endif

Object CreateNativeApi(Runtime& runtime, const NativeApiConfig& config) {
auto bridge = std::make_shared<NativeApiBridge>(config);
BindNativeApiBridgeToRuntime(runtime, bridge);
return Object::createFromHostObject(runtime,
std::make_shared<NativeApiHostObject>(std::move(bridge)));
}
Expand Down
75 changes: 70 additions & 5 deletions NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,66 @@ explicit NativeApiBridge(const NativeApiConfig& config)
}
}

// Called while the bridge's runtime is being destroyed, when its Values can
// still be released. Host objects can be shared into another runtime and
// outlive this one, so from here on the bridge holds no Values.
void releaseRuntimeValues() {
decltype(roundTripValues_) roundTripValues;
decltype(roundTripCacheFramesByThread_) roundTripFrames;
decltype(recentRoundTripValues_) recentRoundTripValues;
#ifdef TARGET_ENGINE_HERMES
std::shared_ptr<Value> roundTripRoot;
#endif
{
std::lock_guard<std::mutex> lock(roundTripValuesMutex_);
runtimeValuesReleased_.store(true, std::memory_order_release);
roundTripValues.swap(roundTripValues_);
roundTripFrames.swap(roundTripCacheFramesByThread_);
recentRoundTripValues.swap(recentRoundTripValues_);
recentRoundTripValueOrder_.clear();
#ifdef TARGET_ENGINE_HERMES
rootedRoundTripValues_.clear();
roundTripRoot.swap(roundTripRootCache_);
#endif
roundTripValuesGeneration_.fetch_add(1, std::memory_order_release);
}
classValues_.clear();
classPrototypes_.clear();
pointerValues_.clear();
objectExpandos_.clear();
objectExpandoOwnerCounts_.clear();
objectExpandosGeneration_.fetch_add(1, std::memory_order_release);
}

bool runtimeValuesReleased() const {
return runtimeValuesReleased_.load(std::memory_order_acquire);
}

// A destroyed runtime freed its Values with its heap; releasing one would
// touch freed memory.
template <typename T>
void releaseOrAbandonValue(std::shared_ptr<T>& value) {
if (value != nullptr && runtimeValuesReleased()) {
new std::shared_ptr<T>(std::move(value));
}
value.reset();
}

void setOwnerRuntime(const Runtime& runtime) { ownerRuntime_ = &runtime; }

// Worklets shares host objects between runtimes by reference. Using one in
// a runtime other than its bridge's would mix the two heaps.
void requireOwnerRuntime(Runtime& runtime) const {
if (ownerRuntime_ != nullptr && ownerRuntime_ != &runtime) {
throw JSError(
runtime,
"This NativeScript object belongs to another JavaScript runtime, "
"such as the Worklets UI runtime. Pass a handle instead: "
"interop.handleof(object).toHexString() in the runtime that owns "
"it, then interop.object(handle) in this one.");
}
}

MDMetadataReader* metadata() const { return metadata_.get(); }

void* selfDl() const { return selfDl_; }
Expand Down Expand Up @@ -621,7 +681,7 @@ void rememberRoundTripValue(Runtime& runtime, const void* native,
const Value& value,
bool stringLikeNative = false,
uintptr_t validationKey = 0) {
if (native == nullptr) {
if (native == nullptr || runtimeValuesReleased()) {
return;
}
uintptr_t key =
Expand Down Expand Up @@ -666,7 +726,7 @@ void rememberScopedRoundTripValueWithValidationKey(Runtime& runtime,
bool stringLikeNative,
bool persistBeyondFrame,
uintptr_t validationKey) {
if (native == nullptr) {
if (native == nullptr || runtimeValuesReleased()) {
return;
}
uintptr_t key =
Expand Down Expand Up @@ -890,7 +950,7 @@ void endRoundTripCacheFrame() {
}

void rememberClassValue(Runtime& runtime, Class cls, const Value& value) {
if (cls == Nil) {
if (cls == Nil || runtimeValuesReleased()) {
return;
}
classValues_[normalizeRuntimePointer(reinterpret_cast<uintptr_t>(cls))] =
Expand All @@ -910,7 +970,7 @@ Value findClassValue(Runtime& runtime, Class cls) const {
}

void rememberClassPrototype(Runtime& runtime, Class cls, const Value& value) {
if (cls == Nil) {
if (cls == Nil || runtimeValuesReleased()) {
return;
}
classPrototypes_[normalizeRuntimePointer(reinterpret_cast<uintptr_t>(cls))] =
Expand All @@ -931,7 +991,7 @@ Value findClassPrototype(Runtime& runtime, Class cls) const {

void setObjectExpando(Runtime& runtime, const void* native,
const std::string& property, const Value& value) {
if (native == nullptr || property.empty()) {
if (native == nullptr || property.empty() || runtimeValuesReleased()) {
return;
}
objectExpandos_[normalizeRuntimePointer(reinterpret_cast<uintptr_t>(native))]
Expand Down Expand Up @@ -1095,6 +1155,9 @@ void cachePropertyGetter(Class cls, const std::string& property,

void rememberPointerValue(Runtime& runtime, const void* native,
const Value& value) {
if (runtimeValuesReleased()) {
return;
}
pointerValues_[reinterpret_cast<uintptr_t>(native)] =
std::make_shared<Value>(runtime, value);
}
Expand Down Expand Up @@ -2173,6 +2236,8 @@ static void appendSurfaceMember(
std::thread::id jsThreadId_ = std::this_thread::get_id();
std::mutex retainedLifetimesMutex_;
std::vector<std::shared_ptr<void>> retainedLifetimes_;
std::atomic<bool> runtimeValuesReleased_{false};
const Runtime* ownerRuntime_ = nullptr;
};

class NativeApiRoundTripCacheFrameGuard final {
Expand Down
5 changes: 5 additions & 0 deletions NativeScript/ffi/objc/shared/bridge/TypeConv.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,7 @@ throw JSError(runtime, "This native return type is not supported by "
}

Value NativeApiReferenceHostObject::get(Runtime& runtime, const PropNameID& name) {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
if (property == "kind") {
return makeString(runtime, "reference");
Expand Down Expand Up @@ -1258,6 +1259,7 @@ throw JSError(runtime, "This native return type is not supported by "

NativeApiHostSetResult NativeApiReferenceHostObject::set(Runtime& runtime, const PropNameID& name,
const Value& value) {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
auto index = parseArrayIndexProperty(property);
if (property != "value" && !index) {
Expand All @@ -1283,6 +1285,7 @@ throw JSError(runtime, "This native return type is not supported by "
}

Value NativeApiStructObjectHostObject::get(Runtime& runtime, const PropNameID& name) {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
if (property == "kind") {
return makeString(runtime, info_ != nullptr && info_->isUnion ? "union" : "struct");
Expand Down Expand Up @@ -1328,6 +1331,7 @@ throw JSError(runtime, "This native return type is not supported by "
NativeApiHostSetResult NativeApiStructObjectHostObject::set(Runtime& runtime,
const PropNameID& name,
const Value& value) {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
if (info_ == nullptr || data_ == nullptr) {
throw JSError(runtime, "Struct is not initialized.");
Expand All @@ -1345,6 +1349,7 @@ throw JSError(runtime, "This native return type is not supported by "
}

std::vector<PropNameID> NativeApiStructObjectHostObject::getPropertyNames(Runtime& runtime) {
bridge_->requireOwnerRuntime(runtime);
std::vector<PropNameID> names;
addPropertyName(runtime, names, "kind");
addPropertyName(runtime, names, "name");
Expand Down
3 changes: 3 additions & 0 deletions NativeScript/ffi/objc/shared/bridge/host_objects/Class.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ static Class classRespondingToClassSelector(Class cls, SEL selector) {
}

Value get(Runtime& runtime, const PropNameID& name) override {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
if (property == "kind") {
return makeString(runtime, "class");
Expand Down Expand Up @@ -249,6 +250,7 @@ throw JSError(
}

NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
Class cls = objc_lookUpClass(symbol_.runtimeName.c_str());
if (cls == nil) {
Expand Down Expand Up @@ -284,6 +286,7 @@ throw JSError(runtime,
}

std::vector<PropNameID> getPropertyNames(Runtime& runtime) override {
bridge_->requireOwnerRuntime(runtime);
std::vector<PropNameID> names;
names.reserve(8);
addPropertyName(runtime, names, "kind");
Expand Down
10 changes: 10 additions & 0 deletions NativeScript/ffi/objc/shared/bridge/host_objects/Interop.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
backingValue_(std::move(backingValue)) {}

~NativeApiPointerHostObject() override {
if (bridge_ != nullptr) {
bridge_->releaseOrAbandonValue(backingValue_);
}
if (adopted_ && pointer_ != nullptr) {
if (bridge_ != nullptr) {
bridge_->forgetPointerValue(pointer_);
Expand All @@ -39,6 +42,7 @@ void clearWithoutFree() {
}

Value get(Runtime& runtime, const PropNameID& name) override {
bridge_->requireOwnerRuntime(runtime);
std::string property = name.utf8(runtime);
if (property == "kind") {
return makeString(runtime, kind_);
Expand Down Expand Up @@ -144,6 +148,7 @@ Value get(Runtime& runtime, const PropNameID& name) override {
}

std::vector<PropNameID> getPropertyNames(Runtime& runtime) override {
bridge_->requireOwnerRuntime(runtime);
std::vector<PropNameID> names;
names.reserve(3);
addPropertyName(runtime, names, "kind");
Expand Down Expand Up @@ -186,6 +191,10 @@ Value get(Runtime& runtime, const PropNameID& name) override {
backingValue_(std::move(backingValue)) {}

~NativeApiReferenceHostObject() override {
if (bridge_ != nullptr) {
bridge_->releaseOrAbandonValue(pendingValue_);
bridge_->releaseOrAbandonValue(backingValue_);
}
for (id object : retainedObjects_) {
[object release];
}
Expand All @@ -205,6 +214,7 @@ void ensureStorage(Runtime& runtime, NativeApiType type,
Value get(Runtime& runtime, const PropNameID& name) override;
NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override;
std::vector<PropNameID> getPropertyNames(Runtime& runtime) override {
bridge_->requireOwnerRuntime(runtime);
std::vector<PropNameID> names;
addPropertyName(runtime, names, "kind");
addPropertyName(runtime, names, "value");
Expand Down
Loading
Loading