// Copyright (c) Facebook, Inc. and its affiliates. // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #include "JSCHelpers.h" #ifdef WITH_FBSYSTRACE #include #endif #include #if WITH_FBJSCEXTENSIONS #include #endif #include "JavaScriptCore.h" #include "Value.h" #include #if WITH_FBJSCEXTENSIONS #undef ASSERT #undef WTF_EXPORT_PRIVATE #include #include #undef TRUE #undef FALSE #endif namespace facebook { namespace react { namespace { class JSFunctionPrivateData : public PrivateDataBase { public: explicit JSFunctionPrivateData(JSFunction&& function) : jsFunction_{std::move(function)} {} JSFunction& getJSFunction() { return jsFunction_; } private: JSFunction jsFunction_; }; JSValueRef functionCaller( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { const bool isCustomJSC = isCustomJSCPtr(ctx); auto* privateData = PrivateDataBase::cast( JSC_JSObjectGetPrivate(isCustomJSC, function)); return (privateData->getJSFunction())(ctx, thisObject, argumentCount, arguments); } JSClassRef createFuncClass(JSContextRef ctx) { JSClassDefinition definition = kJSClassDefinitionEmpty; definition.attributes |= kJSClassAttributeNoAutomaticPrototype; // Need to duplicate the two different finalizer blocks, since there's no way // for it to capture this static information. const bool isCustomJSC = isCustomJSCPtr(ctx); if (isCustomJSC) { definition.finalize = [](JSObjectRef object) { auto* privateData = PrivateDataBase::cast( JSC_JSObjectGetPrivate(true, object)); delete privateData; }; } else { definition.finalize = [](JSObjectRef object) { auto* privateData = PrivateDataBase::cast( JSC_JSObjectGetPrivate(false, object)); delete privateData; }; } definition.callAsFunction = exceptionWrapMethod<&functionCaller>(); return JSC_JSClassCreate(isCustomJSC, &definition); } JSObjectRef makeFunction( JSContextRef ctx, JSStringRef name, JSFunction function) { static JSClassRef kClassDef = NULL, kCustomJSCClassDef = NULL; JSClassRef *classRef = isCustomJSCPtr(ctx) ? &kCustomJSCClassDef : &kClassDef; if (!*classRef) { *classRef = createFuncClass(ctx); } // dealloc in kClassDef.finalize JSFunctionPrivateData *functionDataPtr = new JSFunctionPrivateData(std::move(function)); auto functionObject = Object(ctx, JSC_JSObjectMake(ctx, *classRef, functionDataPtr)); functionObject.setProperty("name", Value(ctx, name)); return functionObject; } } void JSException::buildMessage(JSContextRef ctx, JSValueRef exn, JSStringRef sourceURL, const char* errorMsg) { std::ostringstream msgBuilder; if (errorMsg && strlen(errorMsg) > 0) { msgBuilder << errorMsg << ": "; } Object exnObject = Value(ctx, exn).asObject(); Value exnMessage = exnObject.getProperty("message"); msgBuilder << (exnMessage.isString() ? exnMessage : (Value)exnObject).toString().str(); // The null/empty-ness of source tells us if the JS came from a // file/resource, or was a constructed statement. The location // info will include that source, if any. std::string locationInfo = sourceURL != nullptr ? String::ref(ctx, sourceURL).str() : ""; auto line = exnObject.getProperty("line"); if (line != nullptr && line.isNumber()) { if (locationInfo.empty() && line.asInteger() != 1) { // If there is a non-trivial line number, but there was no // location info, we include a placeholder, and the line // number. locationInfo = folly::to(":", line.asInteger()); } else if (!locationInfo.empty()) { // If there is location info, we always include the line // number, regardless of its value. locationInfo += folly::to(":", line.asInteger()); } } if (!locationInfo.empty()) { msgBuilder << " (" << locationInfo << ")"; } auto exceptionText = msgBuilder.str(); LOG(ERROR) << "Got JS Exception: " << exceptionText; msg_ = std::move(exceptionText); Value jsStack = exnObject.getProperty("stack"); if (jsStack.isString()) { auto stackText = jsStack.toString().str(); LOG(ERROR) << "Got JS Stack: " << stackText; stack_ = std::move(stackText); } } namespace ExceptionHandling { PlatformErrorExtractor platformErrorExtractor; } JSObjectRef makeFunction( JSContextRef ctx, const char* name, JSFunction function) { return makeFunction(ctx, String(ctx, name), std::move(function)); } void installGlobalFunction( JSGlobalContextRef ctx, const char* name, JSFunction function) { auto jsName = String(ctx, name); auto functionObj = makeFunction(ctx, jsName, std::move(function)); Object::getGlobalObject(ctx).setProperty(jsName, Value(ctx, functionObj)); } JSObjectRef makeFunction( JSGlobalContextRef ctx, const char* name, JSObjectCallAsFunctionCallback callback) { auto jsName = String(ctx, name); return JSC_JSObjectMakeFunctionWithCallback(ctx, jsName, callback); } void installGlobalFunction( JSGlobalContextRef ctx, const char* name, JSObjectCallAsFunctionCallback callback) { String jsName(ctx, name); JSObjectRef functionObj = JSC_JSObjectMakeFunctionWithCallback( ctx, jsName, callback); Object::getGlobalObject(ctx).setProperty(jsName, Value(ctx, functionObj)); } void installGlobalProxy( JSGlobalContextRef ctx, const char* name, JSObjectGetPropertyCallback callback) { JSClassDefinition proxyClassDefintion = kJSClassDefinitionEmpty; proxyClassDefintion.attributes |= kJSClassAttributeNoAutomaticPrototype; proxyClassDefintion.getProperty = callback; const bool isCustomJSC = isCustomJSCPtr(ctx); JSClassRef proxyClass = JSC_JSClassCreate(isCustomJSC, &proxyClassDefintion); JSObjectRef proxyObj = JSC_JSObjectMake(ctx, proxyClass, nullptr); JSC_JSClassRelease(isCustomJSC, proxyClass); Object::getGlobalObject(ctx).setProperty(name, Value(ctx, proxyObj)); } void removeGlobal(JSGlobalContextRef ctx, const char* name) { Object::getGlobalObject(ctx).setProperty(name, Value::makeUndefined(ctx)); } JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef sourceURL) { JSValueRef exn, result; result = JSC_JSEvaluateScript(context, script, NULL, sourceURL, 0, &exn); if (result == nullptr) { throw JSException(context, exn, sourceURL); } return result; } #if WITH_FBJSCEXTENSIONS JSValueRef evaluateSourceCode(JSContextRef context, JSSourceCodeRef source, JSStringRef sourceURL) { JSValueRef exn, result; result = JSEvaluateSourceCode(context, source, NULL, &exn); if (result == nullptr) { throw JSException(context, exn, sourceURL); } return result; } #endif JSContextLock::JSContextLock(JSGlobalContextRef ctx) noexcept #if WITH_FBJSCEXTENSIONS : ctx_(ctx), globalLock_(PTHREAD_MUTEX_INITIALIZER) { WTFThreadData& threadData = wtfThreadData(); // Code below is responsible for acquiring locks. It should execute // atomically, thus none of the functions invoked from now on are allowed to // throw an exception try { if (!threadData.isDebuggerThread()) { CHECK(0 == pthread_mutex_lock(&globalLock_)); } JSLock(ctx_); } catch (...) { abort(); } } #else {} #endif JSContextLock::~JSContextLock() noexcept { #if WITH_FBJSCEXTENSIONS WTFThreadData& threadData = wtfThreadData(); JSUnlock(ctx_); if (!threadData.isDebuggerThread()) { CHECK(0 == pthread_mutex_unlock(&globalLock_)); } #endif } JSValueRef translatePendingCppExceptionToJSError(JSContextRef ctx, const char *exceptionLocation) { try { throw; } catch (const std::bad_alloc& ex) { throw; // We probably shouldn't try to handle this in JS } catch (const std::exception& ex) { if (ExceptionHandling::platformErrorExtractor) { auto extractedEror = ExceptionHandling::platformErrorExtractor(ex, exceptionLocation); if (extractedEror.message.length() > 0) { return Value::makeError(ctx, extractedEror.message.c_str(), extractedEror.stack.c_str()); } } auto msg = folly::to("C++ exception in '", exceptionLocation, "'\n\n", ex.what()); return Value::makeError(ctx, msg.c_str()); } catch (const char* ex) { auto msg = folly::to("C++ exception (thrown as a char*) in '", exceptionLocation, "'\n\n", ex); return Value::makeError(ctx, msg.c_str()); } catch (...) { auto msg = folly::to("Unknown C++ exception in '", exceptionLocation, "'"); return Value::makeError(ctx, msg.c_str()); } } JSValueRef translatePendingCppExceptionToJSError(JSContextRef ctx, JSObjectRef jsFunctionCause) { try { auto functionName = Object(ctx, jsFunctionCause).getProperty("name").toString().str(); return translatePendingCppExceptionToJSError(ctx, functionName.c_str()); } catch (...) { return Value::makeError(ctx, "Failed to translate native exception"); } } } }