| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 |
- /****************************************************************************
- Copyright (c) 2016 Chukong Technologies Inc.
- Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
- http://www.cocos2d-x.org
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- ****************************************************************************/
- #include "ScriptEngine.hpp"
- #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
- #include "Object.hpp"
- #include "Class.hpp"
- #include "Utils.hpp"
- #include "../State.hpp"
- #include "../MappingUtils.hpp"
- #include "PlatformUtils.h"
- #import "EJConvertTypedArray.h"
- #if SE_DEBUG > 0
- extern "C" JS_EXPORT void JSSynchronousGarbageCollectForDebugging(JSContextRef);
- #endif
- namespace se {
- AutoHandleScope::AutoHandleScope()
- {
- }
- AutoHandleScope::~AutoHandleScope()
- {
- }
- Class* __jsb_CCPrivateData_class = nullptr;
- //
- namespace {
- ScriptEngine* __instance = nullptr;
- JSValueRef __forceGC(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
- size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
- {
- if (__instance != nullptr)
- {
- __instance->garbageCollect();
- }
- return JSValueMakeUndefined(ctx);
- }
- JSValueRef __log(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
- size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
- {
- if (argumentCount > 0)
- {
- std::string ret;
- internal::forceConvertJsValueToStdString(ctx, arguments[0], &ret);
- SE_LOGD("JS: %s\n", ret.c_str());
- }
- return JSValueMakeUndefined(ctx);
- }
- JSObjectRef privateDataContructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
- {
- return nullptr;
- }
- void privateDataFinalize(JSObjectRef obj)
- {
- internal::PrivateData* p = (internal::PrivateData*)JSObjectGetPrivate(obj);
- JSObjectSetPrivate(obj, p->data);
- if (p->finalizeCb != nullptr)
- p->finalizeCb(obj);
- free(p);
- }
- Value __consoleVal;
- Value __oldConsoleLog;
- Value __oldConsoleDebug;
- Value __oldConsoleInfo;
- Value __oldConsoleWarn;
- Value __oldConsoleError;
- Value __oldConsoleAssert;
- bool JSB_console_format_log(State& s, const char* prefix, int msgIndex = 0)
- {
- if (msgIndex < 0)
- return false;
- const auto& args = s.args();
- int argc = (int)args.size();
- if ((argc - msgIndex) == 1)
- {
- std::string msg = args[msgIndex].toStringForce();
- SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
- }
- else if (argc > 1)
- {
- std::string msg = args[msgIndex].toStringForce();
- size_t pos;
- for (int i = (msgIndex+1); i < argc; ++i)
- {
- pos = msg.find("%");
- if (pos != std::string::npos && pos != (msg.length()-1) && (msg[pos+1] == 'd' || msg[pos+1] == 's' || msg[pos+1] == 'f'))
- {
- msg.replace(pos, 2, args[i].toStringForce());
- }
- else
- {
- msg += " " + args[i].toStringForce();
- }
- }
- SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
- }
- return true;
- }
- // exist potential crash in iOS, when invoke log to javascript
- void JSB_invoke_js_log(Value& v, State& s)
- {
- #if SE_LOG_TO_JS_ENV
- v.toObject()->call(s.args(), __consoleVal.toObject());
- #endif
- }
- bool JSB_console_log(State& s)
- {
- JSB_console_format_log(s, "");
- JSB_invoke_js_log(__oldConsoleLog, s);
- return true;
- }
- SE_BIND_FUNC(JSB_console_log)
- bool JSB_console_debug(State& s)
- {
- JSB_console_format_log(s, "[DEBUG]: ");
- JSB_invoke_js_log(__oldConsoleDebug, s);
- return true;
- }
- SE_BIND_FUNC(JSB_console_debug)
- bool JSB_console_info(State& s)
- {
- JSB_console_format_log(s, "[INFO]: ");
- JSB_invoke_js_log(__oldConsoleInfo, s);
- return true;
- }
- SE_BIND_FUNC(JSB_console_info)
- bool JSB_console_warn(State& s)
- {
- JSB_console_format_log(s, "[WARN]: ");
- JSB_invoke_js_log(__oldConsoleWarn, s);
- return true;
- }
- SE_BIND_FUNC(JSB_console_warn)
- bool JSB_console_error(State& s)
- {
- JSB_console_format_log(s, "[ERROR]: ");
- JSB_invoke_js_log(__oldConsoleError, s);
- return true;
- }
- SE_BIND_FUNC(JSB_console_error)
- bool JSB_console_assert(State& s)
- {
- const auto& args = s.args();
- if (!args.empty())
- {
- if (args[0].isBoolean() && !args[0].toBoolean())
- {
- JSB_console_format_log(s, "[ASSERT]: ", 1);
- JSB_invoke_js_log(__oldConsoleAssert, s);
- }
- }
- return true;
- }
- SE_BIND_FUNC(JSB_console_assert)
- }
- ScriptEngine *ScriptEngine::getInstance()
- {
- if (__instance == nullptr)
- {
- __instance = new ScriptEngine();
- }
- return __instance;
- }
- void ScriptEngine::destroyInstance()
- {
- delete __instance;
- __instance = nullptr;
- }
- ScriptEngine::ScriptEngine()
- : _cx(nullptr)
- , _globalObj(nullptr)
- , _vmId(0)
- , _isGarbageCollecting(false)
- , _isValid(false)
- , _isInCleanup(false)
- , _isErrorHandleWorking(false)
- , _isDebuggerEnabled(false)
- {
- }
- bool ScriptEngine::init()
- {
- cleanup();
- SE_LOGD("Initializing JavaScriptCore \n");
- ++_vmId;
- _engineThreadId = std::this_thread::get_id();
- for (const auto& hook : _beforeInitHookArray)
- {
- hook();
- }
- _beforeInitHookArray.clear();
- _cx = JSGlobalContextCreate(nullptr);
- if (nullptr == _cx)
- return false;
- JSStringRef ctxName = JSStringCreateWithUTF8CString("Cocos2d-x JSB");
- JSGlobalContextSetName(_cx, ctxName);
- JSStringRelease(ctxName);
- NativePtrToObjectMap::init();
- NonRefNativePtrCreatedByCtorMap::init();
-
- internal::setContext(_cx);
- Class::setContext(_cx);
- Object::setContext(_cx);
- JSObjectRef globalObj = JSContextGetGlobalObject(_cx);
- if (nullptr == globalObj)
- return false;
- _globalObj = Object::_createJSObject(nullptr, globalObj);
- _globalObj->root();
- _globalObj->setProperty("window", Value(_globalObj));
- if (!isSupportTypedArrayAPI())
- EJJSContextPrepareTypedArrayAPI(_cx);
- if (_globalObj->getProperty("console", &__consoleVal) && __consoleVal.isObject())
- {
- #if SE_LOG_TO_JS_ENV
- __consoleVal.toObject()->getProperty("log", &__oldConsoleLog);
- __consoleVal.toObject()->getProperty("debug", &__oldConsoleDebug);
- __consoleVal.toObject()->getProperty("info", &__oldConsoleInfo);
- __consoleVal.toObject()->getProperty("warn", &__oldConsoleWarn);
- __consoleVal.toObject()->getProperty("error", &__oldConsoleError);
- __consoleVal.toObject()->getProperty("assert", &__oldConsoleAssert);
- #endif
- __consoleVal.toObject()->defineFunction("log", _SE(JSB_console_log));
- __consoleVal.toObject()->defineFunction("debug", _SE(JSB_console_debug));
- __consoleVal.toObject()->defineFunction("info", _SE(JSB_console_info));
- __consoleVal.toObject()->defineFunction("warn", _SE(JSB_console_warn));
- __consoleVal.toObject()->defineFunction("error", _SE(JSB_console_error));
- __consoleVal.toObject()->defineFunction("assert", _SE(JSB_console_assert));
- }
- _globalObj->setProperty("scriptEngineType", Value("JavaScriptCore"));
- JSStringRef propertyName = JSStringCreateWithUTF8CString("log");
- JSObjectSetProperty(_cx, globalObj, propertyName, JSObjectMakeFunctionWithCallback(_cx, propertyName, __log), kJSPropertyAttributeReadOnly, nullptr);
- JSStringRelease(propertyName);
- propertyName = JSStringCreateWithUTF8CString("forceGC");
- JSObjectSetProperty(_cx, globalObj, propertyName, JSObjectMakeFunctionWithCallback(_cx, propertyName, __forceGC), kJSPropertyAttributeReadOnly, nullptr);
- JSStringRelease(propertyName);
- __jsb_CCPrivateData_class = Class::create("__PrivateData", _globalObj, nullptr, privateDataContructor);
- __jsb_CCPrivateData_class->defineFinalizeFunction(privateDataFinalize);
- __jsb_CCPrivateData_class->install();
- _isValid = true;
- for (const auto& hook : _afterInitHookArray)
- {
- hook();
- }
- _afterInitHookArray.clear();
- return true;
- }
- ScriptEngine::~ScriptEngine()
- {
- cleanup();
- }
- void ScriptEngine::cleanup()
- {
- if (!_isValid)
- return;
- SE_LOGD("ScriptEngine::cleanup begin ...\n");
- _isInCleanup = true;
- for (const auto& hook : _beforeCleanupHookArray)
- {
- hook();
- }
- _beforeCleanupHookArray.clear();
- SAFE_DEC_REF(_globalObj);
- Object::cleanup();
- garbageCollect();
- __consoleVal.setUndefined();
- __oldConsoleLog.setUndefined();
- __oldConsoleDebug.setUndefined();
- __oldConsoleInfo.setUndefined();
- __oldConsoleWarn.setUndefined();
- __oldConsoleError.setUndefined();
- __oldConsoleAssert.setUndefined();
- JSGlobalContextRelease(_cx);
- Class::cleanup();
- _cx = nullptr;
- _globalObj = nullptr;
- _isValid = false;
- _registerCallbackArray.clear();
- for (const auto& hook : _afterCleanupHookArray)
- {
- hook();
- }
- _afterCleanupHookArray.clear();
- _isInCleanup = false;
- NativePtrToObjectMap::destroy();
- NonRefNativePtrCreatedByCtorMap::destroy();
- SE_LOGD("ScriptEngine::cleanup end ...\n");
- }
- ScriptEngine::ExceptionInfo ScriptEngine::_formatException(JSValueRef exception)
- {
- ExceptionInfo ret;
- // Ignore Exception in forceConvertJsValueToStdString invocation to avoid infinite loop.
- internal::forceConvertJsValueToStdString(_cx, exception, &ret.message, true);
- JSType type = JSValueGetType(_cx, exception);
- if (type == kJSTypeObject)
- {
- JSObjectRef obj = JSValueToObject(_cx, exception, nullptr);
- JSStringRef stackKey = JSStringCreateWithUTF8CString("stack");
- JSValueRef stackVal = JSObjectGetProperty(_cx, obj, stackKey, nullptr);
- if (stackKey != nullptr)
- {
- type = JSValueGetType(_cx, stackVal);
- if (type == kJSTypeString)
- {
- JSStringRef stackStr = JSValueToStringCopy(_cx, stackVal, nullptr);
- internal::jsStringToStdString(_cx, stackStr, &ret.stack);
- JSStringRelease(stackStr);
- }
- JSStringRelease(stackKey);
- }
- std::string line;
- std::string column;
- std::string filePath;
- JSPropertyNameArrayRef nameArr = JSObjectCopyPropertyNames(_cx, obj);
- size_t count =JSPropertyNameArrayGetCount(nameArr);
- for (size_t i = 0; i < count; ++i)
- {
- JSStringRef jsName = JSPropertyNameArrayGetNameAtIndex(nameArr, i);
- JSValueRef jsValue = JSObjectGetProperty(_cx, obj, jsName, nullptr);
- std::string name;
- internal::jsStringToStdString(_cx, jsName, &name);
- std::string value;
- JSStringRef jsstr = JSValueToStringCopy(_cx, jsValue, nullptr);
- internal::jsStringToStdString(_cx, jsstr, &value);
- JSStringRelease(jsstr);
- if (name == "line")
- {
- line = value;
- ret.lineno = (uint32_t)JSValueToNumber(_cx, jsValue, nullptr);
- }
- else if (name == "column")
- {
- column = value;
- }
- else if (name == "sourceURL")
- {
- filePath = value;
- ret.filePath = value;
- }
- }
- ret.location = filePath + ":" + line + ":" + column;
- JSPropertyNameArrayRelease(nameArr);
- }
- return ret;
- }
- void ScriptEngine::_clearException(JSValueRef exception)
- {
- if (exception != nullptr)
- {
- ExceptionInfo exceptionInfo = _formatException(exception);
- if (exceptionInfo.isValid())
- {
- std::string exceptionStr = exceptionInfo.message;
- exceptionStr += ", location: " + exceptionInfo.location;
- if (!exceptionInfo.stack.empty())
- {
- exceptionStr += "\nSTACK:\n" + exceptionInfo.stack;
- }
- SE_LOGE("ERROR: %s\n", exceptionStr.c_str());
- callExceptionCallback(exceptionInfo.location.c_str(), exceptionInfo.message.c_str(), exceptionInfo.stack.c_str());
- if (!_isErrorHandleWorking)
- {
- _isErrorHandleWorking = true;
- Value errorHandler;
- if (_globalObj->getProperty("__errorHandler", &errorHandler) && errorHandler.isObject() && errorHandler.toObject()->isFunction())
- {
- ValueArray args;
- args.push_back(Value(exceptionInfo.filePath));
- args.push_back(Value(exceptionInfo.lineno));
- args.push_back(Value(exceptionInfo.message));
- args.push_back(Value(exceptionInfo.stack));
- errorHandler.toObject()->call(args, _globalObj);
- }
- _isErrorHandleWorking = false;
- }
- else
- {
- SE_LOGE("ERROR: __errorHandler has exception\n");
- }
- }
- }
- }
- void ScriptEngine::callExceptionCallback(const char * location, const char * message, const char * stack)
- {
- if(_nativeExceptionCallback) {
- _nativeExceptionCallback(location, message, stack);
- }
- if(_jsExceptionCallback) {
- _jsExceptionCallback(location, message, stack);
- }
- }
- void ScriptEngine::setExceptionCallback(const ExceptionCallback& cb)
- {
- _nativeExceptionCallback = cb;
- }
- void ScriptEngine::setJSExceptionCallback(const ExceptionCallback& cb)
- {
- _jsExceptionCallback = cb;
- }
- bool ScriptEngine::isGarbageCollecting()
- {
- return _isGarbageCollecting;
- }
- void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting)
- {
- _isGarbageCollecting = isGarbageCollecting;
- }
- Object* ScriptEngine::getGlobalObject()
- {
- return _globalObj;
- }
- void ScriptEngine::addBeforeInitHook(const std::function<void()>& hook)
- {
- _beforeInitHookArray.push_back(hook);
- }
- void ScriptEngine::addAfterInitHook(const std::function<void()>& hook)
- {
- _afterInitHookArray.push_back(hook);
- }
- void ScriptEngine::addBeforeCleanupHook(const std::function<void()>& hook)
- {
- _beforeCleanupHookArray.push_back(hook);
- }
- void ScriptEngine::addAfterCleanupHook(const std::function<void()>& hook)
- {
- _afterCleanupHookArray.push_back(hook);
- }
- void ScriptEngine::addRegisterCallback(RegisterCallback cb)
- {
- assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end());
- _registerCallbackArray.push_back(cb);
- }
- bool ScriptEngine::start()
- {
- if (!init())
- return false;
- bool ok = false;
- _startTime = std::chrono::steady_clock::now();
- for (auto cb : _registerCallbackArray)
- {
- ok = cb(_globalObj);
- assert(ok);
- if (!ok)
- break;
- }
- // After ScriptEngine is started, _registerCallbackArray isn't needed. Therefore, clear it here.
- _registerCallbackArray.clear();
- return ok;
- }
- void ScriptEngine::garbageCollect()
- {
- // JSSynchronousGarbageCollectForDebugging is private API in JavaScriptCore framework.
- // AppStore will reject the apps reference non-public symbols.
- // Therefore, we use JSSynchronousGarbageCollectForDebugging for easily debugging memory
- // problems in debug mode, and use public API JSGarbageCollect in release mode to pass
- // the AppStore's verify check.
- #if SE_DEBUG > 0
- SE_LOGD("GC begin ..., (Native -> JS map) count: %d\n", (int)NativePtrToObjectMap::size());
- JSSynchronousGarbageCollectForDebugging(_cx);
- SE_LOGD("GC end ..., (Native -> JS map) count: %d\n", (int)NativePtrToObjectMap::size());
- #else
- JSGarbageCollect(_cx);
- #endif
- }
- bool ScriptEngine::evalString(const char* script, ssize_t length/* = -1 */, Value* ret/* = nullptr */, const char* fileName/* = nullptr */)
- {
- if(_engineThreadId != std::this_thread::get_id())
- {
- // `evalString` should run in main thread
- assert(false);
- return false;
- }
- assert(script != nullptr);
- if (length < 0)
- length = strlen(script);
- if (fileName == nullptr)
- fileName = "(no filename)";
- // Fix the source url is too long displayed in Safari debugger.
- std::string sourceUrl = fileName;
- static const std::string prefixKey = "/temp/quick-scripts/";
- size_t prefixPos = sourceUrl.find(prefixKey);
- if (prefixPos != std::string::npos)
- {
- sourceUrl = sourceUrl.substr(prefixPos + prefixKey.length());
- }
- std::string exceptionStr;
- std::string scriptStr(script, length);
- JSValueRef exception = nullptr;
- JSStringRef jsSourceUrl = JSStringCreateWithUTF8CString(sourceUrl.c_str());
- JSStringRef jsScript = JSStringCreateWithUTF8CString(scriptStr.c_str());
- JSValueRef result = nullptr;
- bool ok = JSCheckScriptSyntax(_cx, jsScript, jsSourceUrl, 1, &exception);;
- if (ok)
- {
- result = JSEvaluateScript(_cx, jsScript, nullptr, jsSourceUrl, 1, &exception);
- if (exception != nullptr)
- {
- ok = false;
- }
- }
- else
- {
- if (exception == nullptr)
- {
- SE_LOGD("Unknown syntax error parsing file %s\n", fileName);
- }
- }
- JSStringRelease(jsScript);
- JSStringRelease(jsSourceUrl);
- if (ok)
- {
- if (ret != nullptr)
- internal::jsToSeValue(_cx, result, ret);
- }
- if (!ok)
- {
- SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName);
- }
- _clearException(exception);
- return ok;
- }
- void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate& delegate)
- {
- _fileOperationDelegate = delegate;
- }
- const ScriptEngine::FileOperationDelegate& ScriptEngine::getFileOperationDelegate() const
- {
- return _fileOperationDelegate;
- }
- bool ScriptEngine::runScript(const std::string& path, Value* ret/* = nullptr */)
- {
- assert(!path.empty());
- assert(_fileOperationDelegate.isValid());
- std::string scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path);
- if (!scriptBuffer.empty())
- {
- return evalString(scriptBuffer.c_str(), scriptBuffer.length(), ret, path.c_str());
- }
- SE_LOGE("ScriptEngine::runScript script %s, buffer is empty!\n", path.c_str());
- return false;
- }
- void ScriptEngine::clearException()
- {
- //IDEA:
- }
- void ScriptEngine::enableDebugger(const std::string& serverAddr, uint32_t port, bool isWait)
- {
- // empty implementation
- _isDebuggerEnabled = true;
- }
- bool ScriptEngine::isDebuggerEnabled() const
- {
- return _isDebuggerEnabled;
- }
- void ScriptEngine::mainLoopUpdate()
- {
- // empty implementation
- }
- } // namespace se {
- #endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
|