1 #include <cxxabi.h>
2 #include <utility>
3 
4 #include "FrozenObject.h"
5 #include "MutableValue.h"
6 #include "MutableValueSetterProxy.h"
7 #include "RemoteObject.h"
8 #include "RuntimeDecorator.h"
9 #include "RuntimeManager.h"
10 #include "ShareableValue.h"
11 #include "SharedParent.h"
12 
13 namespace reanimated {
14 class ShareableValue;
15 const char *HIDDEN_HOST_OBJECT_PROP = "__reanimatedHostObjectRef";
16 const char *ALREADY_CONVERTED = "__alreadyConverted";
17 const char *CALL_ASYNC = "__callAsync";
18 const char *PRIMAL_FUNCTION = "__primalFunction";
19 const char *CALLBACK_ERROR_SUFFIX =
20     "\n\nPossible solutions are:\n"
21     "a) If you want to synchronously execute this method, mark it as a Worklet\n"
22     "b) If you want to execute this method on the JS thread, wrap it using runOnJS";
23 
24 void addHiddenProperty(
25     jsi::Runtime &rt,
26     jsi::Value &&value,
27     const jsi::Object &obj,
28     const char *name) {
29   jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object");
30   jsi::Function defineProperty =
31       globalObject.getPropertyAsFunction(rt, "defineProperty");
32   jsi::String internalPropName = jsi::String::createFromUtf8(rt, name);
33   jsi::Object paramForDefineProperty(rt);
34   paramForDefineProperty.setProperty(rt, "enumerable", false);
35   paramForDefineProperty.setProperty(rt, "value", value);
36   defineProperty.call(rt, obj, internalPropName, paramForDefineProperty);
37 }
38 
39 void freeze(jsi::Runtime &rt, const jsi::Object &obj) {
40   jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object");
41   jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze");
42   freeze.call(rt, obj);
43 }
44 
45 void ShareableValue::adaptCache(jsi::Runtime &rt, const jsi::Value &value) {
46   // when adapting from host object we can assign cached value immediately such
47   // that we avoid running `toJSValue` in the future when given object is
48   // accessed
49   if (RuntimeDecorator::isWorkletRuntime(rt)) {
50     if (remoteValue.expired()) {
51       remoteValue = getWeakRef(rt);
52     }
53     (*remoteValue.lock()) = jsi::Value(rt, value);
54   } else {
55     hostValue = std::make_unique<jsi::Value>(rt, value);
56   }
57 }
58 
59 void ShareableValue::adapt(
60     jsi::Runtime &rt,
61     const jsi::Value &value,
62     ValueType objectType) {
63   if (value.isObject()) {
64     jsi::Object object = value.asObject(rt);
65     jsi::Value hiddenValue = object.getProperty(rt, HIDDEN_HOST_OBJECT_PROP);
66     if (!(hiddenValue.isUndefined())) {
67       jsi::Object hiddenProperty = hiddenValue.asObject(rt);
68       if (hiddenProperty.isHostObject<FrozenObject>(rt)) {
69         type = ValueType::FrozenObjectType;
70         if (object.hasProperty(rt, "__workletHash") && object.isFunction(rt)) {
71           type = ValueType::WorkletFunctionType;
72         }
73         valueContainer = std::make_unique<FrozenObjectWrapper>(
74             hiddenProperty.getHostObject<FrozenObject>(rt));
75         if (object.hasProperty(rt, ALREADY_CONVERTED)) {
76           adaptCache(rt, value);
77         }
78         return;
79       }
80     }
81   }
82 
83   if (objectType == ValueType::MutableValueType) {
84     type = ValueType::MutableValueType;
85     valueContainer =
86         std::make_unique<MutableValueWrapper>(std::make_shared<MutableValue>(
87             rt, value, runtimeManager, runtimeManager->scheduler));
88   } else if (value.isUndefined()) {
89     type = ValueType::UndefinedType;
90   } else if (value.isNull()) {
91     type = ValueType::NullType;
92   } else if (value.isBool()) {
93     type = ValueType::BoolType;
94     valueContainer = std::make_unique<BooleanValueWrapper>(value.getBool());
95   } else if (value.isNumber()) {
96     type = ValueType::NumberType;
97     valueContainer = std::make_unique<NumberValueWrapper>(value.asNumber());
98   } else if (value.isString()) {
99     type = ValueType::StringType;
100     valueContainer =
101         std::make_unique<StringValueWrapper>(value.asString(rt).utf8(rt));
102   } else if (value.isObject()) {
103     auto object = value.asObject(rt);
104     if (object.isFunction(rt)) {
105       if (object.getProperty(rt, "__workletHash").isUndefined()) {
106         // not a worklet, we treat this as a host function
107         type = ValueType::HostFunctionType;
108         containsHostFunction = true;
109 
110         // Check if it's a hostFunction wrapper
111         jsi::Value primalFunction = object.getProperty(rt, PRIMAL_FUNCTION);
112         if (!primalFunction.isUndefined()) {
113           jsi::Object handlerAsObject = primalFunction.asObject(rt);
114           std::shared_ptr<HostFunctionHandler> handler =
115               handlerAsObject.getHostObject<HostFunctionHandler>(rt);
116           valueContainer = std::make_unique<HostFunctionWrapper>(handler);
117         } else {
118           valueContainer = std::make_unique<HostFunctionWrapper>(
119               std::make_shared<HostFunctionHandler>(
120                   std::make_shared<jsi::Function>(object.asFunction(rt)), rt));
121         }
122 
123       } else {
124         // a worklet
125         type = ValueType::WorkletFunctionType;
126         valueContainer = std::make_unique<FrozenObjectWrapper>(
127             std::make_shared<FrozenObject>(rt, object, runtimeManager));
128         auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer);
129         containsHostFunction |= frozenObject->containsHostFunction;
130         if (RuntimeDecorator::isReactRuntime(rt) && !containsHostFunction) {
131           addHiddenProperty(
132               rt,
133               createHost(rt, frozenObject),
134               object,
135               HIDDEN_HOST_OBJECT_PROP);
136         }
137       }
138     } else if (object.isArray(rt)) {
139       type = ValueType::FrozenArrayType;
140       auto array = object.asArray(rt);
141       valueContainer = std::make_unique<FrozenArrayWrapper>();
142       auto &frozenArray = ValueWrapper::asFrozenArray(valueContainer);
143       for (size_t i = 0, size = array.size(rt); i < size; i++) {
144         auto sv = adapt(rt, array.getValueAtIndex(rt, i), runtimeManager);
145         containsHostFunction |= sv->containsHostFunction;
146         frozenArray.push_back(sv);
147       }
148     } else if (object.isHostObject<MutableValue>(rt)) {
149       type = ValueType::MutableValueType;
150       valueContainer = std::make_unique<MutableValueWrapper>(
151           object.getHostObject<MutableValue>(rt));
152       adaptCache(rt, value);
153     } else if (object.isHostObject<RemoteObject>(rt)) {
154       type = ValueType::RemoteObjectType;
155       valueContainer = std::make_unique<RemoteObjectWrapper>(
156           object.getHostObject<RemoteObject>(rt));
157       adaptCache(rt, value);
158     } else if (objectType == ValueType::RemoteObjectType) {
159       type = ValueType::RemoteObjectType;
160       valueContainer =
161           std::make_unique<RemoteObjectWrapper>(std::make_shared<RemoteObject>(
162               rt, object, runtimeManager, runtimeManager->scheduler));
163     } else {
164       // create frozen object based on a copy of a given object
165       type = ValueType::FrozenObjectType;
166       valueContainer = std::make_unique<FrozenObjectWrapper>(
167           std::make_shared<FrozenObject>(rt, object, runtimeManager));
168       auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer);
169       containsHostFunction |= frozenObject->containsHostFunction;
170       if (RuntimeDecorator::isReactRuntime(rt)) {
171         if (!containsHostFunction) {
172           addHiddenProperty(
173               rt,
174               createHost(rt, frozenObject),
175               object,
176               HIDDEN_HOST_OBJECT_PROP);
177         }
178         freeze(rt, object);
179       }
180     }
181   } else if (value.isSymbol()) {
182     type = ValueType::StringType;
183     valueContainer =
184         std::make_unique<StringValueWrapper>(value.asSymbol(rt).toString(rt));
185   } else {
186     throw "Invalid value type";
187   }
188 }
189 
190 std::shared_ptr<ShareableValue> ShareableValue::adapt(
191     jsi::Runtime &rt,
192     const jsi::Value &value,
193     RuntimeManager *runtimeManager,
194     ValueType valueType) {
195   auto sv = std::shared_ptr<ShareableValue>(
196       new ShareableValue(runtimeManager, runtimeManager->scheduler));
197   sv->adapt(rt, value, valueType);
198   return sv;
199 }
200 
201 jsi::Value ShareableValue::getValue(jsi::Runtime &rt) {
202   // TODO: maybe we can cache toJSValue results on a per-runtime basis, need to
203   // avoid ref loops
204   if (&rt == runtimeManager->runtime.get()) {
205     // Getting value on the same runtime where it was created, prepare
206     // remoteValue
207     if (remoteValue.expired()) {
208       remoteValue = getWeakRef(rt);
209     }
210 
211     if (remoteValue.lock()->isUndefined()) {
212       (*remoteValue.lock()) = toJSValue(rt);
213     }
214     return jsi::Value(rt, *remoteValue.lock());
215   } else {
216     // Getting value on a different runtime than where it was created from,
217     // prepare hostValue
218     if (hostValue.get() == nullptr) {
219       hostValue = std::make_unique<jsi::Value>(toJSValue(rt));
220     }
221     return jsi::Value(rt, *hostValue);
222   }
223 }
224 
225 jsi::Object ShareableValue::createHost(
226     jsi::Runtime &rt,
227     std::shared_ptr<jsi::HostObject> host) {
228   return jsi::Object::createFromHostObject(rt, host);
229 }
230 
231 jsi::Value createFrozenWrapper(
232     jsi::Runtime &rt,
233     std::shared_ptr<FrozenObject> frozenObject) {
234   jsi::Object __reanimatedHiddenHost =
235       jsi::Object::createFromHostObject(rt, frozenObject);
236   jsi::Object obj = frozenObject->shallowClone(rt);
237   jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object");
238   jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze");
239   if (!frozenObject->containsHostFunction) {
240     addHiddenProperty(
241         rt, std::move(__reanimatedHiddenHost), obj, HIDDEN_HOST_OBJECT_PROP);
242     addHiddenProperty(rt, true, obj, ALREADY_CONVERTED);
243   }
244   return freeze.call(rt, obj);
245 }
246 
247 jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) {
248   switch (type) {
249     case ValueType::UndefinedType:
250       return jsi::Value::undefined();
251     case ValueType::NullType:
252       return jsi::Value::null();
253     case ValueType::BoolType:
254       return jsi::Value(ValueWrapper::asBoolean(valueContainer));
255     case ValueType::NumberType:
256       return jsi::Value(ValueWrapper::asNumber(valueContainer));
257     case ValueType::StringType: {
258       auto &stringValue = ValueWrapper::asString(valueContainer);
259       return jsi::Value(rt, jsi::String::createFromUtf8(rt, stringValue));
260     }
261     case ValueType::FrozenObjectType: {
262       auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer);
263       return createFrozenWrapper(rt, frozenObject);
264     }
265     case ValueType::FrozenArrayType: {
266       auto &frozenArray = ValueWrapper::asFrozenArray(valueContainer);
267       jsi::Array array(rt, frozenArray.size());
268       for (size_t i = 0; i < frozenArray.size(); i++) {
269         array.setValueAtIndex(rt, i, frozenArray[i]->toJSValue(rt));
270       }
271       return array;
272     }
273     case ValueType::RemoteObjectType: {
274       auto &remoteObject = ValueWrapper::asRemoteObject(valueContainer);
275       if (RuntimeDecorator::isWorkletRuntime(rt)) {
276         remoteObject->maybeInitializeOnWorkletRuntime(rt);
277       }
278       return createHost(rt, remoteObject);
279     }
280     case ValueType::MutableValueType: {
281       auto &mutableObject = ValueWrapper::asMutableValue(valueContainer);
282       return createHost(rt, mutableObject);
283     }
284     case ValueType::HostFunctionType: {
285       auto hostFunctionWrapper =
286           ValueWrapper::asHostFunctionWrapper(valueContainer);
287       auto &hostRuntime = hostFunctionWrapper->value->hostRuntime;
288       if (hostRuntime == &rt) {
289         // function is accessed from the same runtime it was crated, we just
290         // return same function obj
291         return jsi::Value(
292             rt, *hostFunctionWrapper->value->getPureFunction().get());
293       } else {
294         // function is accessed from a different runtime, we wrap function in
295         // host func that'd enqueue call on an appropriate thread
296 
297         auto runtimeManager = this->runtimeManager;
298         auto hostFunction = hostFunctionWrapper->value;
299 
300         auto warnFunction = [runtimeManager, hostFunction](
301                                 jsi::Runtime &rt,
302                                 const jsi::Value &thisValue,
303                                 const jsi::Value *args,
304                                 size_t count) -> jsi::Value {
305           jsi::Value jsThis = rt.global().getProperty(rt, "jsThis");
306           std::string workletLocation = jsThis.asObject(rt)
307                                             .getProperty(rt, "__location")
308                                             .toString(rt)
309                                             .utf8(rt);
310           std::string exceptionMessage = "Tried to synchronously call ";
311           if (hostFunction->functionName.empty()) {
312             exceptionMessage += "anonymous function";
313           } else {
314             exceptionMessage += "function {" + hostFunction->functionName + "}";
315           }
316           exceptionMessage +=
317               " from a different thread.\n\nOccurred in worklet location: ";
318           exceptionMessage += workletLocation;
319           exceptionMessage += CALLBACK_ERROR_SUFFIX;
320           runtimeManager->errorHandler->setError(exceptionMessage);
321           runtimeManager->errorHandler->raise();
322 
323           return jsi::Value::undefined();
324         };
325 
326         auto clb = [runtimeManager, hostFunction, hostRuntime](
327                        jsi::Runtime &rt,
328                        const jsi::Value &thisValue,
329                        const jsi::Value *args,
330                        size_t count) -> jsi::Value {
331           // TODO: we should find thread based on runtime such that we could
332           // also call UI methods from RN and not only RN methods from UI
333 
334           std::vector<std::shared_ptr<ShareableValue>> params;
335           for (int i = 0; i < count; ++i) {
336             params.push_back(
337                 ShareableValue::adapt(rt, args[i], runtimeManager));
338           }
339 
340           std::function<void()> job = [hostFunction, hostRuntime, params] {
341             jsi::Value *args = new jsi::Value[params.size()];
342             for (int i = 0; i < params.size(); ++i) {
343               args[i] = params[i]->getValue(*hostRuntime);
344             }
345             jsi::Value returnedValue =
346                 hostFunction->getPureFunction().get()->call(
347                     *hostRuntime,
348                     static_cast<const jsi::Value *>(args),
349                     static_cast<size_t>(params.size()));
350 
351             delete[] args;
352             // ToDo use returned value to return promise
353           };
354 
355           runtimeManager->scheduler->scheduleOnJS(job);
356           return jsi::Value::undefined();
357         };
358         jsi::Function wrapperFunction = jsi::Function::createFromHostFunction(
359             rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, warnFunction);
360         jsi::Function res = jsi::Function::createFromHostFunction(
361             rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, clb);
362         addHiddenProperty(rt, std::move(res), wrapperFunction, CALL_ASYNC);
363         jsi::Object functionHandler =
364             createHost(rt, hostFunctionWrapper->value);
365         addHiddenProperty(
366             rt, std::move(functionHandler), wrapperFunction, PRIMAL_FUNCTION);
367         return wrapperFunction;
368       }
369     }
370     case ValueType::WorkletFunctionType: {
371       auto runtimeManager = this->runtimeManager;
372       auto &frozenObject = ValueWrapper::asFrozenObject(this->valueContainer);
373       if (RuntimeDecorator::isWorkletRuntime(rt)) {
374         // when running on worklet thread we prep a function
375 
376         auto jsThis = std::make_shared<jsi::Object>(
377             frozenObject->shallowClone(*runtimeManager->runtime));
378         std::shared_ptr<jsi::Function> funPtr(
379             runtimeManager->workletsCache->getFunction(rt, frozenObject));
380         auto name = funPtr->getProperty(rt, "name").asString(rt).utf8(rt);
381 
382         auto clb = [=](jsi::Runtime &rt,
383                        const jsi::Value &thisValue,
384                        const jsi::Value *args,
385                        size_t count) mutable -> jsi::Value {
386           const jsi::String jsThisName =
387               jsi::String::createFromAscii(rt, "jsThis");
388           jsi::Object global = rt.global();
389           jsi::Value oldJSThis = global.getProperty(rt, jsThisName);
390           global.setProperty(rt, jsThisName, *jsThis); // set jsThis
391 
392           jsi::Value res = jsi::Value::undefined();
393           try {
394             if (thisValue.isObject()) {
395               res =
396                   funPtr->callWithThis(rt, thisValue.asObject(rt), args, count);
397             } else {
398               res = funPtr->call(rt, args, count);
399             }
400           } catch (std::exception &e) {
401             std::string str = e.what();
402             runtimeManager->errorHandler->setError(str);
403             runtimeManager->errorHandler->raise();
404           } catch (...) {
405             if (demangleExceptionName(
406                     abi::__cxa_current_exception_type()->name()) ==
407                 "facebook::jsi::JSError") {
408               throw jsi::JSError(rt, "Javascript worklet error");
409             }
410             // TODO find out a way to get the error's message on hermes
411             jsi::Value location = jsThis->getProperty(rt, "__location");
412             std::string str = "Javascript worklet error";
413             if (location.isString()) {
414               str += "\nIn file: " + location.asString(rt).utf8(rt);
415             }
416             runtimeManager->errorHandler->setError(str);
417             runtimeManager->errorHandler->raise();
418           }
419           global.setProperty(rt, jsThisName, oldJSThis); // clean jsThis
420           return res;
421         };
422         return jsi::Function::createFromHostFunction(
423             rt, jsi::PropNameID::forAscii(rt, name.c_str()), 0, clb);
424       } else {
425         // when run outside of UI thread we enqueue a call on the UI thread
426         auto clb = [=](jsi::Runtime &rt,
427                        const jsi::Value &thisValue,
428                        const jsi::Value *args,
429                        size_t count) -> jsi::Value {
430           // TODO: we should find thread based on runtime such that we could
431           // also call UI methods from RN and not only RN methods from UI
432 
433           std::vector<std::shared_ptr<ShareableValue>> params;
434           for (int i = 0; i < count; ++i) {
435             params.push_back(
436                 ShareableValue::adapt(rt, args[i], runtimeManager));
437           }
438 
439           runtimeManager->scheduler->scheduleOnUI([=] {
440             jsi::Runtime &rt = *runtimeManager->runtime.get();
441             auto jsThis = createFrozenWrapper(rt, frozenObject).getObject(rt);
442             auto code =
443                 jsThis.getProperty(rt, "asString").asString(rt).utf8(rt);
444             std::shared_ptr<jsi::Function> funPtr(
445                 runtimeManager->workletsCache->getFunction(rt, frozenObject));
446 
447             jsi::Value *args = new jsi::Value[params.size()];
448             for (int i = 0; i < params.size(); ++i) {
449               args[i] = params[i]->getValue(rt);
450             }
451 
452             jsi::Value returnedValue;
453             const jsi::String jsThisName =
454                 jsi::String::createFromAscii(rt, "jsThis");
455             jsi::Object global = rt.global();
456             jsi::Value oldJSThis = global.getProperty(rt, jsThisName);
457             global.setProperty(rt, jsThisName, jsThis); // set jsThis
458             try {
459               returnedValue = funPtr->call(
460                   rt,
461                   static_cast<const jsi::Value *>(args),
462                   static_cast<size_t>(params.size()));
463             } catch (std::exception &e) {
464               std::string str = e.what();
465               runtimeManager->errorHandler->setError(str);
466               runtimeManager->errorHandler->raise();
467             } catch (...) {
468               if (demangleExceptionName(
469                       abi::__cxa_current_exception_type()->name()) ==
470                   "facebook::jsi::JSError") {
471                 throw jsi::JSError(rt, "Javascript worklet error");
472               }
473               // TODO find out a way to get the error's message on hermes
474               jsi::Value location = jsThis.getProperty(rt, "__location");
475               std::string str = "Javascript worklet error";
476               if (location.isString()) {
477                 str += "\nIn file: " + location.asString(rt).utf8(rt);
478               }
479               runtimeManager->errorHandler->setError(str);
480               runtimeManager->errorHandler->raise();
481             }
482             global.setProperty(rt, jsThisName, oldJSThis); // clean jsThis
483 
484             delete[] args;
485             // ToDo use returned value to return promise
486           });
487           return jsi::Value::undefined();
488         };
489         return jsi::Function::createFromHostFunction(
490             rt, jsi::PropNameID::forAscii(rt, "_workletFunction"), 0, clb);
491       }
492     }
493     default: {
494       throw "Unable to find conversion method for this type";
495     }
496   }
497   throw "convert error";
498 }
499 
500 std::string ShareableValue::demangleExceptionName(std::string toDemangle) {
501   int status = 0;
502   char *buff =
503       __cxxabiv1::__cxa_demangle(toDemangle.c_str(), nullptr, nullptr, &status);
504   if (!buff) {
505     return toDemangle;
506   }
507   std::string demangled = buff;
508   std::free(buff);
509   return demangled;
510 }
511 
512 } // namespace reanimated
513