| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261 |
- /****************************************************************************
- 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_SM
- #include "Object.hpp"
- #include "Class.hpp"
- #include "Utils.hpp"
- #include "../MappingUtils.hpp"
- #include "../State.hpp"
- // for debug socket
- #ifdef _WIN32
- #include <io.h>
- #include <WS2tcpip.h>
- #else
- #include <sys/socket.h>
- #include <unistd.h>
- #include <netdb.h>
- #endif
- #include <mutex>
- #include <thread>
- #include <sstream>
- #if SE_DEBUG
- #define TRACE_DEBUGGER_SERVER(...) SE_LOGD(__VA_ARGS__)
- #else
- #define TRACE_DEBUGGER_SERVER(...)
- #endif // #if COCOS2D_DEBUG
- uint32_t __jsbInvocationCount = 0;
- namespace se {
- Class* __jsb_CCPrivateData_class = nullptr;
- namespace {
- const char* BYTE_CODE_FILE_EXT = ".jsc";
- ScriptEngine* __instance = nullptr;
- const JSClassOps __sandboxClassOps = {
- nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr,
- nullptr, nullptr,
- nullptr, nullptr, nullptr,
- JS_GlobalObjectTraceHook
- };
- JSClass __globalClass = {
- "global",
- JSCLASS_GLOBAL_FLAGS,
- &__sandboxClassOps
- };
- void reportWarning(JSContext* cx, JSErrorReport* report)
- {
- MOZ_RELEASE_ASSERT(report);
- MOZ_RELEASE_ASSERT(JSREPORT_IS_WARNING(report->flags));
- SE_LOGE("%s:%u:%s\n", report->filename ? report->filename : "<no filename>",
- (unsigned int) report->lineno,
- report->message().c_str());
- }
- void SetStandardCompartmentOptions(JS::CompartmentOptions& options)
- {
- bool enableSharedMemory = true;
- options.behaviors().setVersion(JSVERSION_LATEST);
- options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
- }
- bool __forceGC(JSContext *cx, uint32_t argc, JS::Value* vp)
- {
- JS_GC(cx);
- return true;
- }
- bool __log(JSContext *cx, uint32_t argc, JS::Value* vp)
- {
- JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- if (argc > 0) {
- JSString *string = JS::ToString(cx, args[0]);
- if (string) {
- JS::RootedString jsstr(cx, string);
- char* buffer = JS_EncodeStringToUTF8(cx, jsstr);
- SE_LOGD("JS: %s\n", buffer);
- JS_free(cx, buffer);
- }
- }
- args.rval().setUndefined();
- return true;
- }
- // Private data class
- bool privateDataContructor(JSContext* cx, uint32_t argc, JS::Value* vp)
- {
- return true;
- }
- void privateDataFinalize(JSFreeOp* fop, JSObject* obj)
- {
- internal::PrivateData* p = (internal::PrivateData*)JS_GetPrivate(obj);
- if (p == nullptr)
- return;
- JS_SetPrivate(obj, p->data);
- if (p->finalizeCb != nullptr)
- p->finalizeCb(fop, obj);
- free(p);
- }
- // ------------------------------------------------------- ScriptEngine
- void on_garbage_collect(JSContext* cx, JSGCStatus status, void* data)
- {
- /* We finalize any pending toggle refs before doing any garbage collection,
- * so that we can collect the JS wrapper objects, and in order to minimize
- * the chances of objects having a pending toggle up queued when they are
- * garbage collected. */
- if (status == JSGC_BEGIN)
- {
- ScriptEngine::getInstance()->_setGarbageCollecting(true);
- SE_LOGD("on_garbage_collect: begin, Native -> JS map count: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), (int)__objectMap.size());
- }
- else if (status == JSGC_END)
- {
- SE_LOGD("on_garbage_collect: end, Native -> JS map count: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), (int)__objectMap.size());
- ScriptEngine::getInstance()->_setGarbageCollecting(false);
- }
- }
- // For promise
- using JobQueue = JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>;
- // Per-context promise state.
- struct PromiseState
- {
- explicit PromiseState(JSContext* cx);
- JS::PersistentRooted<JobQueue> jobQueue;
- bool drainingJobQueue;
- bool quitting;
- };
- PromiseState::PromiseState(JSContext* cx)
- : drainingJobQueue(false)
- , quitting(false)
- {}
- PromiseState* getPromiseState(JSContext* cx)
- {
- PromiseState* sc = static_cast<PromiseState*>(JS_GetContextPrivate(cx));
- assert(sc);
- return sc;
- }
- JSObject* onGetIncumbentGlobalCallback(JSContext* cx)
- {
- return JS::CurrentGlobalOrNull(cx);
- }
- bool onEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job, JS::HandleObject allocationSite,
- JS::HandleObject incumbentGlobal, void* data)
- {
- PromiseState* sc = getPromiseState(cx);
- assert(job);
- return sc->jobQueue.append(job);
- }
- bool drainJobQueue()
- {
- JSContext* cx = ScriptEngine::getInstance()->_getContext();
- PromiseState* sc = getPromiseState(cx);
- if (sc->quitting || sc->drainingJobQueue)
- return true;
- sc->drainingJobQueue = true;
- JS::RootedObject job(cx);
- JS::HandleValueArray args(JS::HandleValueArray::empty());
- JS::RootedValue rval(cx);
- // Execute jobs in a loop until we've reached the end of the queue.
- // Since executing a job can trigger enqueuing of additional jobs,
- // it's crucial to re-check the queue length during each iteration.
- for (size_t i = 0; i < sc->jobQueue.length(); i++)
- {
- job = sc->jobQueue[i];
- JSAutoCompartment ac(cx, job);
- JS::Call(cx, JS::UndefinedHandleValue, job, args, &rval);
- ScriptEngine::getInstance()->clearException();
- sc->jobQueue[i].set(nullptr);
- }
- sc->jobQueue.clear();
- sc->drainingJobQueue = false;
- return true;
- }
- std::string removeFileExt(const std::string& filePath)
- {
- size_t pos = filePath.rfind('.');
- if (0 < pos)
- {
- return filePath.substr(0, pos);
- }
- return filePath;
- }
- bool getBytecodeBuildId(JS::BuildIdCharVector* buildId)
- {
- static const char* buildid = "cocos_xdr";
- bool ok = buildId->append(buildid, strlen(buildid));
- return ok;
- }
- // For console stuff
- 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;
- }
- bool JSB_console_log(State& s)
- {
- JSB_console_format_log(s, "");
- return true;
- }
- SE_BIND_FUNC(JSB_console_log)
- bool JSB_console_debug(State& s)
- {
- JSB_console_format_log(s, "[DEBUG]: ");
- return true;
- }
- SE_BIND_FUNC(JSB_console_debug)
- bool JSB_console_info(State& s)
- {
- JSB_console_format_log(s, "[INFO]: ");
- return true;
- }
- SE_BIND_FUNC(JSB_console_info)
- bool JSB_console_warn(State& s)
- {
- JSB_console_format_log(s, "[WARN]: ");
- return true;
- }
- SE_BIND_FUNC(JSB_console_warn)
- bool JSB_console_error(State& s)
- {
- JSB_console_format_log(s, "[ERROR]: ");
- 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);
- }
- }
- return true;
- }
- SE_BIND_FUNC(JSB_console_assert)
- } // namespace {
- AutoHandleScope::AutoHandleScope()
- {
- }
- AutoHandleScope::~AutoHandleScope()
- {
- drainJobQueue();
- }
- ScriptEngine *ScriptEngine::getInstance()
- {
- if (__instance == nullptr)
- {
- __instance = new ScriptEngine();
- }
- return __instance;
- }
- void ScriptEngine::destroyInstance()
- {
- delete __instance;
- __instance = nullptr;
- }
- ScriptEngine::ScriptEngine()
- : _cx(nullptr)
- , _globalObj(nullptr)
- , _debugGlobalObj(nullptr)
- , _oldCompartment(nullptr)
- , _exceptionCallback(nullptr)
- , _debuggerServerPort(0)
- , _vmId(0)
- , _isGarbageCollecting(false)
- , _isValid(false)
- , _isInCleanup(false)
- , _isErrorHandleWorking(false)
- {
- bool ok = JS_Init();
- assert(ok);
- }
- /* static */
- void ScriptEngine::onWeakPointerCompartmentCallback(JSContext* cx, JSCompartment* comp, void* data)
- {
- onWeakPointerZoneGroupCallback(cx, data);
- }
- /* static */
- void ScriptEngine::onWeakPointerZoneGroupCallback(JSContext* cx, void* data)
- {
- bool isInCleanup = getInstance()->isInCleanup();
- bool isIterUpdated = false;
- Object* obj = nullptr;
- auto iter = NativePtrToObjectMap::begin();
- while (iter != NativePtrToObjectMap::end())
- {
- obj = iter->second;
- isIterUpdated = false;
- if (!obj->isRooted())
- {
- if (obj->updateAfterGC(data))
- {
- obj->decRef();
- iter = NativePtrToObjectMap::erase(iter);
- isIterUpdated = true;
- }
- }
- else if (isInCleanup) // Rooted and in cleanup step
- {
- obj->unprotect();
- obj->decRef();
- iter = NativePtrToObjectMap::erase(iter);
- isIterUpdated = true;
- }
- if (!isIterUpdated)
- ++iter;
- }
- }
- bool ScriptEngine::init()
- {
- cleanup();
- SE_LOGD("Initializing SpiderMonkey, version: %s\n", JS_GetImplementationVersion());
- ++_vmId;
- for (const auto& hook : _beforeInitHookArray)
- {
- hook();
- }
- _beforeInitHookArray.clear();
- _cx = JS_NewContext(JS::DefaultHeapMaxBytes);
- if (nullptr == _cx)
- return false;
- NativePtrToObjectMap::init();
- NonRefNativePtrCreatedByCtorMap::init();
- Class::setContext(_cx);
- Object::setContext(_cx);
- JS_SetGCParameter(_cx, JSGC_MAX_BYTES, 0xffffffff);
- JS_SetGCParameter(_cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
- JS_SetNativeStackQuota(_cx, 5000000);
- JS_SetGCCallback(_cx, on_garbage_collect, nullptr);
- if (!JS::InitSelfHostedCode(_cx))
- return false;
- PromiseState* sc = new (std::nothrow) PromiseState(_cx);
- if (!sc)
- return false;
- JS_SetContextPrivate(_cx, sc);
- // Waiting is allowed on the shell's main thread, for now.
- JS_SetFutexCanWait(_cx);
- JS::SetWarningReporter(_cx, reportWarning);
- #if defined(JS_GC_ZEAL) && defined(DEBUG)
- // JS_SetGCZeal(_cx, 2, JS_DEFAULT_ZEAL_FREQ);
- #endif
- JS_SetDefaultLocale(_cx, "UTF-8");
- JS_BeginRequest(_cx);
- JS::CompartmentOptions options;
- SetStandardCompartmentOptions(options);
- #ifdef DEBUG
- JS::ContextOptionsRef(_cx)
- .setExtraWarnings(true)
- .setIon(false)
- .setBaseline(false)
- .setAsmJS(false);
- #else
- JS::ContextOptionsRef(_cx)
- // .setExtraWarnings(true)
- .setIon(true)
- .setBaseline(true)
- .setAsmJS(true)
- .setNativeRegExp(true);
- #endif
- JSObject* globalObj = JS_NewGlobalObject(_cx, &__globalClass, nullptr, JS::DontFireOnNewGlobalHook, options);
- if (nullptr == globalObj)
- return false;
- _globalObj = Object::_createJSObject(nullptr, globalObj);
- _globalObj->root();
- JS::RootedObject rootedGlobalObj(_cx, _globalObj->_getJSObject());
- _oldCompartment = JS_EnterCompartment(_cx, rootedGlobalObj);
- JS_InitStandardClasses(_cx, rootedGlobalObj) ;
- _globalObj->setProperty("window", Value(_globalObj));
- // SpiderMonkey isn't shipped with a console variable. Make a fake one.
- Value consoleVal;
- bool hasConsole = _globalObj->getProperty("console", &consoleVal) && consoleVal.isObject();
- assert(!hasConsole);
- HandleObject consoleObj(Object::createPlainObject());
- consoleObj->defineFunction("log", _SE(JSB_console_log));
- consoleObj->defineFunction("debug", _SE(JSB_console_debug));
- consoleObj->defineFunction("info", _SE(JSB_console_info));
- consoleObj->defineFunction("warn", _SE(JSB_console_warn));
- consoleObj->defineFunction("error", _SE(JSB_console_error));
- consoleObj->defineFunction("assert", _SE(JSB_console_assert));
- _globalObj->setProperty("console", Value(consoleObj));
- _globalObj->setProperty("scriptEngineType", Value("SpiderMonkey"));
- JS_DefineFunction(_cx, rootedGlobalObj, "log", __log, 0, JSPROP_PERMANENT);
- JS_DefineFunction(_cx, rootedGlobalObj, "forceGC", __forceGC, 0, JSPROP_READONLY | JSPROP_PERMANENT);
- // JS_AddWeakPointerZoneGroupCallback(_cx, ScriptEngine::onWeakPointerZoneGroupCallback, nullptr);
- JS_AddWeakPointerCompartmentCallback(_cx, ScriptEngine::onWeakPointerCompartmentCallback, nullptr);
- JS_FireOnNewGlobalObject(_cx, rootedGlobalObj);
- JS::SetBuildIdOp(_cx, getBytecodeBuildId);
- sc->jobQueue.init(_cx, JobQueue(js::SystemAllocPolicy()));
- JS::SetEnqueuePromiseJobCallback(_cx, onEnqueuePromiseJobCallback);
- JS::SetGetIncumbentGlobalCallback(_cx, onGetIncumbentGlobalCallback);
- __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();
- JS_ShutDown();
- }
- void ScriptEngine::cleanup()
- {
- if (!_isValid)
- return;
- _isInCleanup = true;
- for (const auto& hook : _beforeCleanupHookArray)
- {
- hook();
- }
- _beforeCleanupHookArray.clear();
- auto sc = getPromiseState(_cx);
- sc->quitting = true;
- SAFE_DEC_REF(_globalObj);
- Class::cleanup();
- Object::cleanup();
- // JS_RemoveWeakPointerZoneGroupCallback(_cx, ScriptEngine::onWeakPointerZoneGroupCallback);
- // JS_RemoveWeakPointerCompartmentCallback(_cx, ScriptEngine::onWeakPointerCompartmentCallback);
- JS_LeaveCompartment(_cx, _oldCompartment);
- JS::SetGetIncumbentGlobalCallback(_cx, nullptr);
- JS::SetEnqueuePromiseJobCallback(_cx, nullptr);
- sc->jobQueue.reset();
- JS_EndRequest(_cx);
- JS_DestroyContext(_cx);
- delete sc;
- _cx = nullptr;
- _globalObj = nullptr;
- _oldCompartment = nullptr;
- _isValid = false;
- _registerCallbackArray.clear();
- _filenameScriptMap.clear();
- for (const auto& hook : _afterCleanupHookArray)
- {
- hook();
- }
- _afterCleanupHookArray.clear();
- _isInCleanup = false;
- NativePtrToObjectMap::destroy();
- NonRefNativePtrCreatedByCtorMap::destroy();
- }
- 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::addBeforeInitHook(const std::function<void()>& hook)
- {
- _beforeInitHookArray.push_back(hook);
- }
- void ScriptEngine::addAfterInitHook(const std::function<void()>& hook)
- {
- _afterInitHookArray.push_back(hook);
- }
- bool ScriptEngine::isGarbageCollecting()
- {
- return _isGarbageCollecting;
- }
- void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting)
- {
- _isGarbageCollecting = isGarbageCollecting;
- }
- Object* ScriptEngine::getGlobalObject()
- {
- return _globalObj;
- }
- void ScriptEngine::addRegisterCallback(RegisterCallback cb)
- {
- assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end());
- _registerCallbackArray.push_back(cb);
- }
- #pragma mark - Debug
- static std::string inData;
- static std::string outData;
- static std::vector<std::string> g_queue;
- static std::mutex g_qMutex;
- static std::mutex g_rwMutex;
- static int clientSocket = -1;
- static uint32_t s_nestedLoopLevel = 0;
- static void cc_closesocket(int fd)
- {
- #ifdef _WIN32
- closesocket(fd);
- #else
- close(fd);
- #endif
- }
- void ScriptEngine::_debugProcessInput(const std::string& str)
- {
- JS::RootedObject debugGlobal(_cx, _debugGlobalObj->_getJSObject());
- JSCompartment *globalCpt = JS_EnterCompartment(_cx, debugGlobal);
- Value func;
- if (_debugGlobalObj->getProperty("processInput", &func) && func.isObject() && func.toObject()->isFunction())
- {
- ValueArray args;
- args.push_back(Value(str));
- func.toObject()->call(args, _debugGlobalObj);
- }
- JS_LeaveCompartment(_cx, globalCpt);
- }
- static bool NS_ProcessNextEvent()
- {
- std::string message;
- size_t messageCount = 0;
- while (true)
- {
- g_qMutex.lock();
- messageCount = g_queue.size();
- if (messageCount > 0)
- {
- auto first = g_queue.begin();
- message = *first;
- g_queue.erase(first);
- --messageCount;
- }
- g_qMutex.unlock();
- if (!message.empty())
- {
- ScriptEngine::getInstance()->_debugProcessInput(message);
- }
- if (messageCount == 0)
- break;
- }
- // std::this_thread::yield();
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- return true;
- }
- static bool JSBDebug_enterNestedEventLoop(State& s)
- {
- enum {
- NS_OK = 0,
- NS_ERROR_UNEXPECTED
- };
- #define NS_SUCCEEDED(v) ((v) == NS_OK)
- int rv = NS_OK;
- uint32_t nestLevel = ++s_nestedLoopLevel;
- while (NS_SUCCEEDED(rv) && s_nestedLoopLevel >= nestLevel) {
- drainJobQueue();
- if (!NS_ProcessNextEvent())
- rv = NS_ERROR_UNEXPECTED;
- }
- assert(s_nestedLoopLevel <= nestLevel);
- s.rval().setInt32(s_nestedLoopLevel);
- return true;
- }
- SE_BIND_FUNC(JSBDebug_enterNestedEventLoop)
- static bool JSBDebug_exitNestedEventLoop(State& s)
- {
- if (s_nestedLoopLevel > 0) {
- --s_nestedLoopLevel;
- } else {
- s.rval().setInt32(0);
- return true;
- }
- return true;
- }
- SE_BIND_FUNC(JSBDebug_exitNestedEventLoop)
- static bool JSBDebug_getEventLoopNestLevel(State& s)
- {
- s.rval().setInt32(s_nestedLoopLevel);
- return true;
- }
- SE_BIND_FUNC(JSBDebug_getEventLoopNestLevel)
- static void _clientSocketWriteAndClearString(std::string& s)
- {
- ::send(clientSocket, s.c_str(), s.length(), 0);
- s.clear();
- }
- static void processInput(const std::string& data) {
- std::lock_guard<std::mutex> lk(g_qMutex);
- g_queue.push_back(data);
- }
- static void clearBuffers() {
- std::lock_guard<std::mutex> lk(g_rwMutex);
- // only process input if there's something and we're not locked
- if (!inData.empty()) {
- processInput(inData);
- inData.clear();
- }
- if (!outData.empty()) {
- _clientSocketWriteAndClearString(outData);
- }
- }
- static void serverEntryPoint(uint32_t port)
- {
- // start a server, accept the connection and keep reading data from it
- struct addrinfo hints, *result = nullptr, *rp = nullptr;
- int s = 0;
- memset(&hints, 0, sizeof(struct addrinfo));
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
- hints.ai_flags = AI_PASSIVE; // fill in my IP for me
- std::stringstream portstr;
- portstr << port;
- int err = 0;
- #ifdef _WIN32
- WSADATA wsaData;
- err = WSAStartup(MAKEWORD(2, 2),&wsaData);
- #endif
- if ((err = getaddrinfo(NULL, portstr.str().c_str(), &hints, &result)) != 0) {
- SE_LOGD("getaddrinfo error : %s\n", gai_strerror(err));
- }
- for (rp = result; rp != NULL; rp = rp->ai_next) {
- if ((s = socket(rp->ai_family, rp->ai_socktype, 0)) < 0) {
- continue;
- }
- int optval = 1;
- if ((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval))) < 0) {
- cc_closesocket(s);
- TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_REUSEADDR\n");
- return;
- }
- #ifdef __APPLE__
- if ((setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval))) < 0) {
- close(s);
- TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_NOSIGPIPE\n");
- return;
- }
- #endif
- if ((::bind(s, rp->ai_addr, rp->ai_addrlen)) == 0) {
- break;
- }
- cc_closesocket(s);
- s = -1;
- }
- if (s < 0 || rp == NULL) {
- TRACE_DEBUGGER_SERVER("debug server : error creating/binding socket\n");
- return;
- }
- freeaddrinfo(result);
- listen(s, 1);
- #define MAX_RECEIVED_SIZE 1024
- #define BUF_SIZE MAX_RECEIVED_SIZE + 1
- char buf[BUF_SIZE] = {0};
- int readBytes = 0;
- while (true) {
- clientSocket = accept(s, NULL, NULL);
- if (clientSocket < 0)
- {
- TRACE_DEBUGGER_SERVER("debug server : error on accept\n");
- return;
- }
- else
- {
- // read/write data
- TRACE_DEBUGGER_SERVER("debug server : client connected\n");
- inData = "connected";
- // process any input, send any output
- clearBuffers();
- while ((readBytes = (int)::recv(clientSocket, buf, MAX_RECEIVED_SIZE, 0)) > 0)
- {
- buf[readBytes] = '\0';
- // TRACE_DEBUGGER_SERVER("debug server : received command >%s", buf);
- // no other thread is using this
- inData.append(buf);
- // process any input, send any output
- clearBuffers();
- } // while(read)
- cc_closesocket(clientSocket);
- }
- } // while(true)
- #undef BUF_SIZE
- #undef MAX_RECEIVED_SIZE
- }
- static bool JSBDebug_require(State& s)
- {
- const auto& args = s.args();
- int argc = (int)args.size();
- if (argc >= 1)
- {
- ScriptEngine::getInstance()->runScript(args[0].toString());
- return true;
- }
- SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 1);
- return false;
- }
- SE_BIND_FUNC(JSBDebug_require)
- static bool JSBDebug_BufferWrite(State& s)
- {
- const auto& args = s.args();
- int argc = (int)args.size();
- if (argc == 1)
- {
- // this is safe because we're already inside a lock (from clearBuffers)
- outData.append(args[0].toString());
- _clientSocketWriteAndClearString(outData);
- }
- return true;
- }
- SE_BIND_FUNC(JSBDebug_BufferWrite)
- bool ScriptEngine::start()
- {
- if (!init())
- return false;
- if (isDebuggerEnabled() && _debugGlobalObj == nullptr)
- {
- JS::CompartmentOptions options;
- options.behaviors().setVersion(JSVERSION_LATEST);
- options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
- JS::RootedObject debugGlobal(_cx, JS_NewGlobalObject(_cx, &__globalClass, nullptr, JS::DontFireOnNewGlobalHook, options));
- _debugGlobalObj = Object::_createJSObject(nullptr, debugGlobal);
- _debugGlobalObj->root();
- JSCompartment *globalCpt = JS_EnterCompartment(_cx, debugGlobal);
- JS_InitStandardClasses(_cx, debugGlobal);
- JS_FireOnNewGlobalObject(_cx, debugGlobal);
- JS_DefineDebuggerObject(_cx, debugGlobal);
- // these are used in the debug program
- JS_DefineFunction(_cx, debugGlobal, "log", __log, 0, JSPROP_PERMANENT);
- _debugGlobalObj->defineFunction("require", _SE(JSBDebug_require));
- _debugGlobalObj->defineFunction("_bufferWrite", _SE(JSBDebug_BufferWrite));
- _debugGlobalObj->defineFunction("_enterNestedEventLoop", _SE(JSBDebug_enterNestedEventLoop));
- _debugGlobalObj->defineFunction("_exitNestedEventLoop", _SE(JSBDebug_exitNestedEventLoop));
- _debugGlobalObj->defineFunction("_getEventLoopNestLevel", _SE(JSBDebug_getEventLoopNestLevel));
- JS::RootedObject globalObj(_cx, _globalObj->_getJSObject());
- JS_WrapObject(_cx, &globalObj);
- runScript("script/jsb_debugger.js");
- // prepare the debugger
- Value prepareDebuggerFunc;
- assert(_debugGlobalObj->getProperty("_prepareDebugger", &prepareDebuggerFunc) && prepareDebuggerFunc.isObject() && prepareDebuggerFunc.toObject()->isFunction());
- ValueArray args;
- args.push_back(Value(_globalObj));
- prepareDebuggerFunc.toObject()->call(args, _debugGlobalObj);
- // start bg thread
- auto t = std::thread(&serverEntryPoint, _debuggerServerPort);
- t.detach();
- JS_LeaveCompartment(_cx, globalCpt);
- }
- 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;
- }
- bool ScriptEngine::getScript(const std::string& path, JS::MutableHandleScript script)
- {
- std::string fullPath = _fileOperationDelegate.onGetFullPath(path);
- auto iter = _filenameScriptMap.find(fullPath);
- if (iter != _filenameScriptMap.end())
- {
- JS::PersistentRootedScript* rootedScript = iter->second;
- script.set(rootedScript->get());
- return true;
- }
- // // a) check jsc file first
- // std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
- // if (_filenameScriptMap.find(byteCodePath) != _filenameScriptMap.end())
- // {
- // script.set(_filenameScriptMap[byteCodePath]->get());
- // return true;
- // }
- //
- // // b) no jsc file, check js file
- // if (_filenameScriptMap.find(path) != _filenameScriptMap.end())
- // {
- // script.set(_filenameScriptMap[path]->get());
- // return true;
- // }
- return false;
- }
- bool ScriptEngine::compileScript(const std::string& path, JS::MutableHandleScript script)
- {
- if (path.empty())
- return false;
- bool ok = getScript(path, script);
- if (ok)
- return true;
- assert(_fileOperationDelegate.isValid());
- bool compileSucceed = false;
- // Creator v1.7 supports v8, spidermonkey, javascriptcore and chakracore as its script engine,
- // jsc file isn't bytecode format anymore, it's a xxtea encrpted binary format instead.
- // Therefore, for unifying the flow, ScriptEngine class will not support spidermonkey bytecode.
- // // a) check jsc file first
- // std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
- //
- // // Check whether '.jsc' files exist to avoid outputting log which says 'couldn't find .jsc file'.
- // if (_fileOperationDelegate.onCheckFileExist(byteCodePath))
- // {
- // _fileOperationDelegate.onGetDataFromFile(byteCodePath, [&](const uint8_t* data, size_t dataLen) {
- // if (data != nullptr && dataLen > 0)
- // {
- // JS::TranscodeBuffer buffer;
- // bool appended = buffer.append(data, dataLen);
- // JS::TranscodeResult result = JS::DecodeScript(_cx, buffer, script);
- // if (appended && result == JS::TranscodeResult::TranscodeResult_Ok)
- // {
- // compileSucceed = true;
- // _filenameScriptMap[byteCodePath] = new (std::nothrow) JS::PersistentRootedScript(_cx, script.get());
- // }
- // assert(compileSucceed);
- // }
- // });
- //
- // }
- // b) no jsc file, check js file
- if (!compileSucceed)
- {
- /* Clear any pending exception from previous failed decoding. */
- clearException();
- ok = false;
- std::string jsFileContent = _fileOperationDelegate.onGetStringFromFile(path);
- if (!jsFileContent.empty())
- {
- JS::CompileOptions op(_cx);
- op.setUTF8(true);
- op.setFileAndLine(path.c_str(), 1);
- ok = JS::Compile(_cx, op, jsFileContent.c_str(), jsFileContent.size(), script);
- if (ok)
- {
- compileSucceed = true;
- std::string fullPath = _fileOperationDelegate.onGetFullPath(path);
- _filenameScriptMap[fullPath] = new (std::nothrow) JS::PersistentRootedScript(_cx, script.get());
- }
- assert(compileSucceed);
- }
- }
-
- clearException();
-
- if (!compileSucceed)
- {
- SE_LOGD("ScriptEngine::compileScript fail:%s\n", path.c_str());
- }
- return compileSucceed;
- }
- bool ScriptEngine::evalString(const char* script, ssize_t length/* = -1 */, Value* ret/* = nullptr */, const char* fileName/* = nullptr */)
- {
- assert(script != nullptr);
- if (length < 0)
- length = strlen(script);
- if (fileName == nullptr)
- fileName = "(no filename)";
- JS::CompileOptions options(_cx);
- options.setFile(fileName)
- .setUTF8(true)
- .setVersion(JSVERSION_LATEST);
- JS::RootedValue rval(_cx);
- bool ok = JS::Evaluate(_cx, options, script, length, &rval);
- if (!ok)
- {
- clearException();
- }
- assert(ok);
- if (ok && ret != nullptr && !rval.isNullOrUndefined())
- {
- internal::jsToSeValue(_cx, rval, ret);
- }
- if (!ok)
- {
- SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName);
- }
- 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(_fileOperationDelegate.isValid());
- JS::RootedScript script(_cx);
- bool ok = compileScript(path, &script);
- if (ok)
- {
- JS::RootedValue rval(_cx);
- ok = JS_ExecuteScript(_cx, script, &rval);
- if (!ok)
- {
- SE_LOGE("Evaluating %s failed (evaluatedOK == JS_FALSE)\n", path.c_str());
- clearException();
- }
- if (ok && ret != nullptr && !rval.isNullOrUndefined())
- {
- internal::jsToSeValue(_cx, rval, ret);
- }
- }
-
- return ok;
- }
- void ScriptEngine::clearException()
- {
- if (_cx == nullptr)
- return;
- if (JS_IsExceptionPending(_cx))
- {
- JS::RootedValue exceptionValue(_cx);
- JS_GetPendingException(_cx, &exceptionValue);
- JS_ClearPendingException(_cx);
- assert(exceptionValue.isObject());
- JS::RootedObject exceptionObj(_cx, exceptionValue.toObjectOrNull());
- JSErrorReport* report = JS_ErrorFromException(_cx, exceptionObj);
- const char* message = report->message().c_str();
- const std::string filePath = report->filename != nullptr ? report->filename : "(no filename)";
- char line[50] = {0};
- snprintf(line, sizeof(line), "%u", report->lineno);
- char column[50] = {0};
- snprintf(column, sizeof(column), "%u", report->column);
- const std::string location = filePath + ":" + line + ":" + column;
- char* stack = nullptr; // Need to be freed by JS_free
- JS::RootedValue stackVal(_cx);
- if (JS_GetProperty(_cx, exceptionObj, "stack", &stackVal) && stackVal.isString())
- {
- JS::RootedString jsstackStr(_cx, stackVal.toString());
- stack = JS_EncodeStringToUTF8(_cx, jsstackStr);
- }
- std::string exceptionStr = message;
- exceptionStr += ", location: " + location;
- if (stack != nullptr)
- {
- exceptionStr += "\nSTACK:\n";
- exceptionStr += stack;
- }
- SE_LOGE("ERROR: %s\n", exceptionStr.c_str());
- if (_exceptionCallback != nullptr)
- {
- _exceptionCallback(location.c_str(), message, stack);
- }
- if (!_isErrorHandleWorking)
- {
- _isErrorHandleWorking = true;
- Value errorHandler;
- if (_globalObj->getProperty("__errorHandler", &errorHandler) && errorHandler.isObject() && errorHandler.toObject()->isFunction())
- {
- ValueArray args;
- args.push_back(Value(filePath));
- args.push_back(Value(report->lineno));
- args.push_back(Value(message));
- args.push_back(Value(stack));
- errorHandler.toObject()->call(args, _globalObj);
- }
- _isErrorHandleWorking = false;
- }
- else
- {
- SE_LOGE("ERROR: __errorHandler has exception\n");
- }
- if (stack != nullptr)
- {
- JS_free(_cx, stack);
- stack = nullptr;
- }
- }
- }
- void ScriptEngine::setExceptionCallback(const ExceptionCallback& cb)
- {
- _exceptionCallback = cb;
- }
- void ScriptEngine::enableDebugger(const std::string& serverAddr, uint32_t port, bool isWait)
- {
- _debuggerServerAddr = serverAddr;
- _debuggerServerPort = port;
- }
- bool ScriptEngine::isDebuggerEnabled() const
- {
- return !_debuggerServerAddr.empty() && _debuggerServerPort > 0;
- }
- void ScriptEngine::mainLoopUpdate()
- {
- std::string message;
- size_t messageCount = 0;
- while (true)
- {
- g_qMutex.lock();
- messageCount = g_queue.size();
- if (messageCount > 0)
- {
- auto first = g_queue.begin();
- message = *first;
- g_queue.erase(first);
- --messageCount;
- }
- g_qMutex.unlock();
- if (!message.empty())
- {
- _debugProcessInput(message);
- }
- if (messageCount == 0)
- break;
- }
- }
- } // namespace se {
- #endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
|