Skip to content

Commit 891ee78

Browse files
tsaichienfacebook-github-bot
authored andcommitted
Add JSI APIs setRuntimeData and getRuntimeData (facebook#50197)
Summary: Pull Request resolved: facebook#50197 Adds `setRuntimeData` and `getRuntimeData` JSI APIs. This provides a convenient way for users to store some custom data associated with an UUID. For the default implementation of this feature, store the runtime data in the global map that is shared between all VMs. This is done to keep JSI lightweight and stateless, instead of adding data members. Changelog: [Internal] Reviewed By: neildhar Differential Revision: D71579532 fbshipit-source-id: 553e28fbf80c93e268d475860197a00d2c5bacf7
1 parent 499fcfa commit 891ee78

File tree

5 files changed

+264
-0
lines changed

5 files changed

+264
-0
lines changed

packages/react-native/ReactCommon/jsi/jsi/decorator.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,17 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
393393
return plain_.callAsConstructor(f, args, count);
394394
};
395395

396+
void setRuntimeDataImpl(
397+
const UUID& uuid,
398+
const void* data,
399+
void (*deleter)(const void* data)) override {
400+
return plain_.setRuntimeDataImpl(uuid, data, deleter);
401+
}
402+
403+
const void* getRuntimeDataImpl(const UUID& uuid) override {
404+
return plain_.getRuntimeDataImpl(uuid);
405+
}
406+
396407
// Private data for managing scopes.
397408
Runtime::ScopeState* pushScope() override {
398409
return plain_.pushScope();
@@ -957,6 +968,19 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
957968
RD::setExternalMemoryPressure(obj, amount);
958969
};
959970

971+
void setRuntimeDataImpl(
972+
const UUID& uuid,
973+
const void* data,
974+
void (*deleter)(const void* data)) override {
975+
Around around{with_};
976+
RD::setRuntimeDataImpl(uuid, data, deleter);
977+
}
978+
979+
const void* getRuntimeDataImpl(const UUID& uuid) override {
980+
Around around{with_};
981+
return RD::getRuntimeDataImpl(uuid);
982+
}
983+
960984
private:
961985
// Wrap an RAII type around With& to guarantee after always happens.
962986
struct Around {

packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ inline const Runtime::PointerValue* Runtime::getPointerValue(
8484
return value.data_.pointer.ptr_;
8585
}
8686

87+
inline void Runtime::setRuntimeData(
88+
const UUID& uuid,
89+
const std::shared_ptr<void>& data) {
90+
auto* dataPtr = new std::shared_ptr<void>(data);
91+
setRuntimeDataImpl(uuid, dataPtr, [](const void* data) {
92+
delete (const std::shared_ptr<void>*)data;
93+
});
94+
}
95+
96+
inline std::shared_ptr<void> Runtime::getRuntimeData(const UUID& uuid) {
97+
auto* data = (const std::shared_ptr<void>*)getRuntimeDataImpl(uuid);
98+
return data ? *data : nullptr;
99+
}
100+
87101
Value Object::getPrototype(Runtime& runtime) const {
88102
return runtime.getPrototypeOf(*this);
89103
}

packages/react-native/ReactCommon/jsi/jsi/jsi.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <cassert>
99
#include <cmath>
1010
#include <cstdlib>
11+
#include <map>
12+
#include <mutex>
1113
#include <stdexcept>
1214

1315
#include <jsi/instrumentation.h>
@@ -18,6 +20,63 @@ namespace jsi {
1820

1921
namespace {
2022

23+
/// A global map used to store custom runtime data for VMs that do not provide
24+
/// their own default implementation of setRuntimeData and getRuntimeData.
25+
struct RuntimeDataGlobal {
26+
/// Mutex protecting the Runtime data map
27+
std::mutex mutex_{};
28+
/// Maps a runtime pointer to a map of its custom data. At destruction of the
29+
/// runtime, its entry will be removed from the global map.
30+
std::unordered_map<
31+
Runtime*,
32+
std::unordered_map<
33+
UUID,
34+
std::pair<const void*, void (*)(const void* data)>,
35+
UUID::Hash>>
36+
dataMap_;
37+
};
38+
39+
RuntimeDataGlobal& getRuntimeDataGlobal() {
40+
static RuntimeDataGlobal runtimeData{};
41+
return runtimeData;
42+
}
43+
44+
/// A host object that, when destructed, will remove the runtime's custom data
45+
/// entry from the global map of custom data.
46+
class RemoveRuntimeDataHostObject : public jsi::HostObject {
47+
public:
48+
explicit RemoveRuntimeDataHostObject(Runtime* runtime) : runtime_(runtime) {}
49+
50+
RemoveRuntimeDataHostObject(const RemoveRuntimeDataHostObject&) = default;
51+
RemoveRuntimeDataHostObject(RemoveRuntimeDataHostObject&&) = default;
52+
RemoveRuntimeDataHostObject& operator=(const RemoveRuntimeDataHostObject&) =
53+
default;
54+
RemoveRuntimeDataHostObject& operator=(RemoveRuntimeDataHostObject&&) =
55+
default;
56+
57+
~RemoveRuntimeDataHostObject() override {
58+
auto& runtimeDataGlobal = getRuntimeDataGlobal();
59+
std::lock_guard<std::mutex> lock(runtimeDataGlobal.mutex_);
60+
auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(runtime_);
61+
// We install the RemoveRuntimeDataHostObject only when the first custom
62+
// data for the runtime is added, and only this object is responsible for
63+
// clearing runtime data. Thus, we should always be able to find the data
64+
// entry.
65+
assert(
66+
runtimeMapIt != runtimeDataGlobal.dataMap_.end() &&
67+
"Custom runtime data not found for this runtime");
68+
69+
for (auto [_, entry] : runtimeMapIt->second) {
70+
auto* deleter = entry.second;
71+
deleter(entry.first);
72+
}
73+
runtimeDataGlobal.dataMap_.erase(runtime_);
74+
}
75+
76+
private:
77+
Runtime* runtime_;
78+
};
79+
2180
// This is used for generating short exception strings.
2281
std::string kindToString(const Value& v, Runtime* rt = nullptr) {
2382
if (v.isUndefined()) {
@@ -353,6 +412,62 @@ Object Runtime::createObjectWithPrototype(const Value& prototype) {
353412
return createFn.call(*this, prototype).asObject(*this);
354413
}
355414

415+
void Runtime::setRuntimeDataImpl(
416+
const UUID& uuid,
417+
const void* data,
418+
void (*deleter)(const void* data)) {
419+
auto& runtimeDataGlobal = getRuntimeDataGlobal();
420+
std::lock_guard<std::mutex> lock(runtimeDataGlobal.mutex_);
421+
if (auto it = runtimeDataGlobal.dataMap_.find(this);
422+
it != runtimeDataGlobal.dataMap_.end()) {
423+
auto& map = it->second;
424+
if (auto entryIt = map.find(uuid); entryIt != map.end()) {
425+
// Free the old data
426+
auto oldData = entryIt->second.first;
427+
auto oldDataDeleter = entryIt->second.second;
428+
oldDataDeleter(oldData);
429+
}
430+
map[uuid] = {data, deleter};
431+
return;
432+
}
433+
// No custom data entry exist for this runtime in the global map, so create
434+
// one.
435+
runtimeDataGlobal.dataMap_[this][uuid] = {data, deleter};
436+
437+
// The first time data is added for this runtime is added to the map, install
438+
// a host object on the global object of the runtime. This host object is used
439+
// to release the runtime's entry from the global custom data map when the
440+
// runtime is destroyed.
441+
// Also, try to protect the host object by making it non-configurable,
442+
// non-enumerable, and non-writable. These JSI operations are purposely
443+
// performed after runtime-specific data map is added and the host object is
444+
// created to prevent data leaks if any operations fail.
445+
Object ho = Object::createFromHostObject(
446+
*this, std::make_shared<RemoveRuntimeDataHostObject>(this));
447+
global().setProperty(*this, "_jsiRuntimeDataCleanUp", ho);
448+
auto definePropertyFn = global()
449+
.getPropertyAsObject(*this, "Object")
450+
.getPropertyAsFunction(*this, "defineProperty");
451+
auto desc = Object(*this);
452+
desc.setProperty(*this, "configurable", Value(false));
453+
desc.setProperty(*this, "enumerable", Value(false));
454+
desc.setProperty(*this, "writable", Value(false));
455+
definePropertyFn.call(*this, global(), "_jsiRuntimeDataCleanUp", desc);
456+
}
457+
458+
const void* Runtime::getRuntimeDataImpl(const UUID& uuid) {
459+
auto& runtimeDataGlobal = getRuntimeDataGlobal();
460+
std::lock_guard<std::mutex> lock(runtimeDataGlobal.mutex_);
461+
if (auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(this);
462+
runtimeMapIt != runtimeDataGlobal.dataMap_.end()) {
463+
if (auto customDataIt = runtimeMapIt->second.find(uuid);
464+
customDataIt != runtimeMapIt->second.end()) {
465+
return customDataIt->second.first;
466+
}
467+
}
468+
return nullptr;
469+
}
470+
356471
Pointer& Pointer::operator=(Pointer&& other) noexcept {
357472
if (ptr_) {
358473
ptr_->invalidate();

packages/react-native/ReactCommon/jsi/jsi/jsi.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,16 @@ class JSI_EXPORT Runtime {
349349
/// which returns no metrics.
350350
virtual Instrumentation& instrumentation();
351351

352+
/// Stores the pointer \p data with the \p uuid in the runtime. This can be
353+
/// used to store some custom data within the runtime. When the runtime is
354+
/// destroyed, or if an entry at an existing key is overwritten, the runtime
355+
/// will release its ownership of the held object.
356+
void setRuntimeData(const UUID& uuid, const std::shared_ptr<void>& data);
357+
358+
/// Returns the data associated with the \p uuid in the runtime. If there's no
359+
/// data associated with the uuid, return a null pointer.
360+
std::shared_ptr<void> getRuntimeData(const UUID& uuid);
361+
352362
protected:
353363
friend class Pointer;
354364
friend class PropNameID;
@@ -364,6 +374,19 @@ class JSI_EXPORT Runtime {
364374
friend class Scope;
365375
friend class JSError;
366376

377+
/// Stores the pointer \p data with the \p uuid in the runtime. This can be
378+
/// used to store some custom data within the runtime. When the runtime is
379+
/// destroyed, or if an entry at an existing key is overwritten, the runtime
380+
/// will release its ownership by calling \p deleter.
381+
virtual void setRuntimeDataImpl(
382+
const UUID& uuid,
383+
const void* data,
384+
void (*deleter)(const void* data));
385+
386+
/// Returns the data associated with the \p uuid in the runtime. If there's no
387+
/// data associated with the uuid, return a null pointer.
388+
virtual const void* getRuntimeDataImpl(const UUID& uuid);
389+
367390
// Potential optimization: avoid the cloneFoo() virtual dispatch,
368391
// and instead just fix the number of fields, and copy them, since
369392
// in practice they are trivially copyable. Sufficient use of

packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,94 @@ TEST_P(JSITest, ObjectCreateWithPrototype) {
17731773
EXPECT_TRUE(child.getPrototype(rd).isNull());
17741774
}
17751775

1776+
TEST_P(JSITest, SetRuntimeData) {
1777+
class RD : public RuntimeDecorator<Runtime, Runtime> {
1778+
public:
1779+
explicit RD(Runtime& rt) : RuntimeDecorator(rt) {}
1780+
1781+
void setRuntimeDataImpl(
1782+
const UUID& uuid,
1783+
const void* data,
1784+
void (*deleter)(const void* data)) override {
1785+
Runtime::setRuntimeDataImpl(uuid, data, deleter);
1786+
}
1787+
1788+
const void* getRuntimeDataImpl(const UUID& uuid) override {
1789+
return Runtime::getRuntimeDataImpl(uuid);
1790+
}
1791+
};
1792+
1793+
RD rd1 = RD(rt);
1794+
UUID uuid1{0xe67ab3d6, 0x09a0, 0x11f0, 0xa641, 0x325096b39f47};
1795+
auto str = std::make_shared<std::string>("hello world");
1796+
rd1.setRuntimeData(uuid1, str);
1797+
1798+
UUID uuid2{0xa12f99fc, 0x09a2, 0x11f0, 0x84de, 0x325096b39f47};
1799+
auto obj1 = std::make_shared<Object>(rd1);
1800+
rd1.setRuntimeData(uuid2, obj1);
1801+
1802+
auto storedStr =
1803+
std::static_pointer_cast<std::string>(rd1.getRuntimeData(uuid1));
1804+
auto storedObj = std::static_pointer_cast<Object>(rd1.getRuntimeData(uuid2));
1805+
EXPECT_EQ(storedStr, str);
1806+
EXPECT_EQ(storedObj, obj1);
1807+
1808+
// Override the existing value at uuid1
1809+
auto weakOldStr = std::weak_ptr<std::string>(str);
1810+
str = std::make_shared<std::string>("goodbye world");
1811+
rd1.setRuntimeData(uuid1, str);
1812+
storedStr = std::static_pointer_cast<std::string>(rd1.getRuntimeData(uuid1));
1813+
EXPECT_EQ(str, storedStr);
1814+
// Verify that the old data was not held on after it was overwritten.
1815+
EXPECT_EQ(weakOldStr.use_count(), 0);
1816+
1817+
auto rt2 = factory();
1818+
RD* rd2 = new RD(*rt2);
1819+
UUID uuid3{0x16f55892, 0x1034, 0x11f0, 0x8f65, 0x325096b39f47};
1820+
auto obj2 = std::make_shared<Object>(*rd2);
1821+
rd2->setRuntimeData(uuid3, obj2);
1822+
1823+
auto storedObj2 =
1824+
std::static_pointer_cast<Object>(rd2->getRuntimeData(uuid3));
1825+
EXPECT_EQ(storedObj2, obj2);
1826+
1827+
// UUID1 is for some data in runtime rd1, not rd2
1828+
EXPECT_FALSE(rd2->getRuntimeData(uuid1));
1829+
1830+
// Verify that when runtime is deleted, its runtime data map gets removed from
1831+
// the global map. So nothing should be holding on to the stored data.
1832+
auto weakObj2 = std::weak_ptr<Object>(obj2);
1833+
obj2.reset();
1834+
storedObj2.reset();
1835+
delete rd2;
1836+
rt2.reset();
1837+
EXPECT_EQ(weakObj2.use_count(), 0);
1838+
1839+
// Only the second runtime was destroyed, so custom data from the first
1840+
// runtime should remain unaffected.
1841+
storedStr = std::static_pointer_cast<std::string>(rd1.getRuntimeData(uuid1));
1842+
EXPECT_EQ(storedStr, str);
1843+
1844+
// Overwrite Object.defineProperty, which is called in the default
1845+
// implementation of setRuntimeDataImpl, to test that even if the JSI
1846+
// operations on this secret property fail, we are still able to properly
1847+
// clean up the custom data.
1848+
auto rt3 = factory();
1849+
RD* rd3 = new RD(*rt3);
1850+
UUID uuid4{0xa5682986, 0x1edc, 0x11f0, 0xa4fa, 0x325096b39f47};
1851+
rd3->global()
1852+
.getPropertyAsObject(*rd3, "Object")
1853+
.setProperty(*rd3, "defineProperty", Value(false));
1854+
1855+
auto obj3 = std::make_shared<Object>(*rd3);
1856+
auto weakObj3 = std::weak_ptr<Object>(obj3);
1857+
EXPECT_THROW(rd3->setRuntimeData(uuid4, obj3), JSIException);
1858+
obj3.reset();
1859+
delete rd3;
1860+
rt3.reset();
1861+
EXPECT_EQ(weakObj3.use_count(), 0);
1862+
}
1863+
17761864
INSTANTIATE_TEST_CASE_P(
17771865
Runtimes,
17781866
JSITest,

0 commit comments

Comments
 (0)