ScriptEngine.cpp 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  1. /****************************************************************************
  2. Copyright (c) 2016 Chukong Technologies Inc.
  3. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
  4. http://www.cocos2d-x.org
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in
  12. all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. THE SOFTWARE.
  20. ****************************************************************************/
  21. #include "ScriptEngine.hpp"
  22. #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
  23. #include "Object.hpp"
  24. #include "Class.hpp"
  25. #include "Utils.hpp"
  26. #include "../MappingUtils.hpp"
  27. #include "../State.hpp"
  28. // for debug socket
  29. #ifdef _WIN32
  30. #include <io.h>
  31. #include <WS2tcpip.h>
  32. #else
  33. #include <sys/socket.h>
  34. #include <unistd.h>
  35. #include <netdb.h>
  36. #endif
  37. #include <mutex>
  38. #include <thread>
  39. #include <sstream>
  40. #if SE_DEBUG
  41. #define TRACE_DEBUGGER_SERVER(...) SE_LOGD(__VA_ARGS__)
  42. #else
  43. #define TRACE_DEBUGGER_SERVER(...)
  44. #endif // #if COCOS2D_DEBUG
  45. uint32_t __jsbInvocationCount = 0;
  46. namespace se {
  47. Class* __jsb_CCPrivateData_class = nullptr;
  48. namespace {
  49. const char* BYTE_CODE_FILE_EXT = ".jsc";
  50. ScriptEngine* __instance = nullptr;
  51. const JSClassOps __sandboxClassOps = {
  52. nullptr, nullptr, nullptr, nullptr,
  53. nullptr, nullptr,
  54. nullptr, nullptr,
  55. nullptr, nullptr, nullptr,
  56. JS_GlobalObjectTraceHook
  57. };
  58. JSClass __globalClass = {
  59. "global",
  60. JSCLASS_GLOBAL_FLAGS,
  61. &__sandboxClassOps
  62. };
  63. void reportWarning(JSContext* cx, JSErrorReport* report)
  64. {
  65. MOZ_RELEASE_ASSERT(report);
  66. MOZ_RELEASE_ASSERT(JSREPORT_IS_WARNING(report->flags));
  67. SE_LOGE("%s:%u:%s\n", report->filename ? report->filename : "<no filename>",
  68. (unsigned int) report->lineno,
  69. report->message().c_str());
  70. }
  71. void SetStandardCompartmentOptions(JS::CompartmentOptions& options)
  72. {
  73. bool enableSharedMemory = true;
  74. options.behaviors().setVersion(JSVERSION_LATEST);
  75. options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
  76. }
  77. bool __forceGC(JSContext *cx, uint32_t argc, JS::Value* vp)
  78. {
  79. JS_GC(cx);
  80. return true;
  81. }
  82. bool __log(JSContext *cx, uint32_t argc, JS::Value* vp)
  83. {
  84. JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
  85. if (argc > 0) {
  86. JSString *string = JS::ToString(cx, args[0]);
  87. if (string) {
  88. JS::RootedString jsstr(cx, string);
  89. char* buffer = JS_EncodeStringToUTF8(cx, jsstr);
  90. SE_LOGD("JS: %s\n", buffer);
  91. JS_free(cx, buffer);
  92. }
  93. }
  94. args.rval().setUndefined();
  95. return true;
  96. }
  97. // Private data class
  98. bool privateDataContructor(JSContext* cx, uint32_t argc, JS::Value* vp)
  99. {
  100. return true;
  101. }
  102. void privateDataFinalize(JSFreeOp* fop, JSObject* obj)
  103. {
  104. internal::PrivateData* p = (internal::PrivateData*)JS_GetPrivate(obj);
  105. if (p == nullptr)
  106. return;
  107. JS_SetPrivate(obj, p->data);
  108. if (p->finalizeCb != nullptr)
  109. p->finalizeCb(fop, obj);
  110. free(p);
  111. }
  112. // ------------------------------------------------------- ScriptEngine
  113. void on_garbage_collect(JSContext* cx, JSGCStatus status, void* data)
  114. {
  115. /* We finalize any pending toggle refs before doing any garbage collection,
  116. * so that we can collect the JS wrapper objects, and in order to minimize
  117. * the chances of objects having a pending toggle up queued when they are
  118. * garbage collected. */
  119. if (status == JSGC_BEGIN)
  120. {
  121. ScriptEngine::getInstance()->_setGarbageCollecting(true);
  122. SE_LOGD("on_garbage_collect: begin, Native -> JS map count: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), (int)__objectMap.size());
  123. }
  124. else if (status == JSGC_END)
  125. {
  126. SE_LOGD("on_garbage_collect: end, Native -> JS map count: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), (int)__objectMap.size());
  127. ScriptEngine::getInstance()->_setGarbageCollecting(false);
  128. }
  129. }
  130. // For promise
  131. using JobQueue = JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>;
  132. // Per-context promise state.
  133. struct PromiseState
  134. {
  135. explicit PromiseState(JSContext* cx);
  136. JS::PersistentRooted<JobQueue> jobQueue;
  137. bool drainingJobQueue;
  138. bool quitting;
  139. };
  140. PromiseState::PromiseState(JSContext* cx)
  141. : drainingJobQueue(false)
  142. , quitting(false)
  143. {}
  144. PromiseState* getPromiseState(JSContext* cx)
  145. {
  146. PromiseState* sc = static_cast<PromiseState*>(JS_GetContextPrivate(cx));
  147. assert(sc);
  148. return sc;
  149. }
  150. JSObject* onGetIncumbentGlobalCallback(JSContext* cx)
  151. {
  152. return JS::CurrentGlobalOrNull(cx);
  153. }
  154. bool onEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job, JS::HandleObject allocationSite,
  155. JS::HandleObject incumbentGlobal, void* data)
  156. {
  157. PromiseState* sc = getPromiseState(cx);
  158. assert(job);
  159. return sc->jobQueue.append(job);
  160. }
  161. bool drainJobQueue()
  162. {
  163. JSContext* cx = ScriptEngine::getInstance()->_getContext();
  164. PromiseState* sc = getPromiseState(cx);
  165. if (sc->quitting || sc->drainingJobQueue)
  166. return true;
  167. sc->drainingJobQueue = true;
  168. JS::RootedObject job(cx);
  169. JS::HandleValueArray args(JS::HandleValueArray::empty());
  170. JS::RootedValue rval(cx);
  171. // Execute jobs in a loop until we've reached the end of the queue.
  172. // Since executing a job can trigger enqueuing of additional jobs,
  173. // it's crucial to re-check the queue length during each iteration.
  174. for (size_t i = 0; i < sc->jobQueue.length(); i++)
  175. {
  176. job = sc->jobQueue[i];
  177. JSAutoCompartment ac(cx, job);
  178. JS::Call(cx, JS::UndefinedHandleValue, job, args, &rval);
  179. ScriptEngine::getInstance()->clearException();
  180. sc->jobQueue[i].set(nullptr);
  181. }
  182. sc->jobQueue.clear();
  183. sc->drainingJobQueue = false;
  184. return true;
  185. }
  186. std::string removeFileExt(const std::string& filePath)
  187. {
  188. size_t pos = filePath.rfind('.');
  189. if (0 < pos)
  190. {
  191. return filePath.substr(0, pos);
  192. }
  193. return filePath;
  194. }
  195. bool getBytecodeBuildId(JS::BuildIdCharVector* buildId)
  196. {
  197. static const char* buildid = "cocos_xdr";
  198. bool ok = buildId->append(buildid, strlen(buildid));
  199. return ok;
  200. }
  201. // For console stuff
  202. bool JSB_console_format_log(State& s, const char* prefix, int msgIndex = 0)
  203. {
  204. if (msgIndex < 0)
  205. return false;
  206. const auto& args = s.args();
  207. int argc = (int)args.size();
  208. if ((argc - msgIndex) == 1)
  209. {
  210. std::string msg = args[msgIndex].toStringForce();
  211. SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
  212. }
  213. else if (argc > 1)
  214. {
  215. std::string msg = args[msgIndex].toStringForce();
  216. size_t pos;
  217. for (int i = (msgIndex+1); i < argc; ++i)
  218. {
  219. pos = msg.find("%");
  220. if (pos != std::string::npos && pos != (msg.length()-1) && (msg[pos+1] == 'd' || msg[pos+1] == 's' || msg[pos+1] == 'f'))
  221. {
  222. msg.replace(pos, 2, args[i].toStringForce());
  223. }
  224. else
  225. {
  226. msg += " " + args[i].toStringForce();
  227. }
  228. }
  229. SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
  230. }
  231. return true;
  232. }
  233. bool JSB_console_log(State& s)
  234. {
  235. JSB_console_format_log(s, "");
  236. return true;
  237. }
  238. SE_BIND_FUNC(JSB_console_log)
  239. bool JSB_console_debug(State& s)
  240. {
  241. JSB_console_format_log(s, "[DEBUG]: ");
  242. return true;
  243. }
  244. SE_BIND_FUNC(JSB_console_debug)
  245. bool JSB_console_info(State& s)
  246. {
  247. JSB_console_format_log(s, "[INFO]: ");
  248. return true;
  249. }
  250. SE_BIND_FUNC(JSB_console_info)
  251. bool JSB_console_warn(State& s)
  252. {
  253. JSB_console_format_log(s, "[WARN]: ");
  254. return true;
  255. }
  256. SE_BIND_FUNC(JSB_console_warn)
  257. bool JSB_console_error(State& s)
  258. {
  259. JSB_console_format_log(s, "[ERROR]: ");
  260. return true;
  261. }
  262. SE_BIND_FUNC(JSB_console_error)
  263. bool JSB_console_assert(State& s)
  264. {
  265. const auto& args = s.args();
  266. if (!args.empty())
  267. {
  268. if (args[0].isBoolean() && !args[0].toBoolean())
  269. {
  270. JSB_console_format_log(s, "[ASSERT]: ", 1);
  271. }
  272. }
  273. return true;
  274. }
  275. SE_BIND_FUNC(JSB_console_assert)
  276. } // namespace {
  277. AutoHandleScope::AutoHandleScope()
  278. {
  279. }
  280. AutoHandleScope::~AutoHandleScope()
  281. {
  282. drainJobQueue();
  283. }
  284. ScriptEngine *ScriptEngine::getInstance()
  285. {
  286. if (__instance == nullptr)
  287. {
  288. __instance = new ScriptEngine();
  289. }
  290. return __instance;
  291. }
  292. void ScriptEngine::destroyInstance()
  293. {
  294. delete __instance;
  295. __instance = nullptr;
  296. }
  297. ScriptEngine::ScriptEngine()
  298. : _cx(nullptr)
  299. , _globalObj(nullptr)
  300. , _debugGlobalObj(nullptr)
  301. , _oldCompartment(nullptr)
  302. , _exceptionCallback(nullptr)
  303. , _debuggerServerPort(0)
  304. , _vmId(0)
  305. , _isGarbageCollecting(false)
  306. , _isValid(false)
  307. , _isInCleanup(false)
  308. , _isErrorHandleWorking(false)
  309. {
  310. bool ok = JS_Init();
  311. assert(ok);
  312. }
  313. /* static */
  314. void ScriptEngine::onWeakPointerCompartmentCallback(JSContext* cx, JSCompartment* comp, void* data)
  315. {
  316. onWeakPointerZoneGroupCallback(cx, data);
  317. }
  318. /* static */
  319. void ScriptEngine::onWeakPointerZoneGroupCallback(JSContext* cx, void* data)
  320. {
  321. bool isInCleanup = getInstance()->isInCleanup();
  322. bool isIterUpdated = false;
  323. Object* obj = nullptr;
  324. auto iter = NativePtrToObjectMap::begin();
  325. while (iter != NativePtrToObjectMap::end())
  326. {
  327. obj = iter->second;
  328. isIterUpdated = false;
  329. if (!obj->isRooted())
  330. {
  331. if (obj->updateAfterGC(data))
  332. {
  333. obj->decRef();
  334. iter = NativePtrToObjectMap::erase(iter);
  335. isIterUpdated = true;
  336. }
  337. }
  338. else if (isInCleanup) // Rooted and in cleanup step
  339. {
  340. obj->unprotect();
  341. obj->decRef();
  342. iter = NativePtrToObjectMap::erase(iter);
  343. isIterUpdated = true;
  344. }
  345. if (!isIterUpdated)
  346. ++iter;
  347. }
  348. }
  349. bool ScriptEngine::init()
  350. {
  351. cleanup();
  352. SE_LOGD("Initializing SpiderMonkey, version: %s\n", JS_GetImplementationVersion());
  353. ++_vmId;
  354. for (const auto& hook : _beforeInitHookArray)
  355. {
  356. hook();
  357. }
  358. _beforeInitHookArray.clear();
  359. _cx = JS_NewContext(JS::DefaultHeapMaxBytes);
  360. if (nullptr == _cx)
  361. return false;
  362. NativePtrToObjectMap::init();
  363. NonRefNativePtrCreatedByCtorMap::init();
  364. Class::setContext(_cx);
  365. Object::setContext(_cx);
  366. JS_SetGCParameter(_cx, JSGC_MAX_BYTES, 0xffffffff);
  367. JS_SetGCParameter(_cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
  368. JS_SetNativeStackQuota(_cx, 5000000);
  369. JS_SetGCCallback(_cx, on_garbage_collect, nullptr);
  370. if (!JS::InitSelfHostedCode(_cx))
  371. return false;
  372. PromiseState* sc = new (std::nothrow) PromiseState(_cx);
  373. if (!sc)
  374. return false;
  375. JS_SetContextPrivate(_cx, sc);
  376. // Waiting is allowed on the shell's main thread, for now.
  377. JS_SetFutexCanWait(_cx);
  378. JS::SetWarningReporter(_cx, reportWarning);
  379. #if defined(JS_GC_ZEAL) && defined(DEBUG)
  380. // JS_SetGCZeal(_cx, 2, JS_DEFAULT_ZEAL_FREQ);
  381. #endif
  382. JS_SetDefaultLocale(_cx, "UTF-8");
  383. JS_BeginRequest(_cx);
  384. JS::CompartmentOptions options;
  385. SetStandardCompartmentOptions(options);
  386. #ifdef DEBUG
  387. JS::ContextOptionsRef(_cx)
  388. .setExtraWarnings(true)
  389. .setIon(false)
  390. .setBaseline(false)
  391. .setAsmJS(false);
  392. #else
  393. JS::ContextOptionsRef(_cx)
  394. // .setExtraWarnings(true)
  395. .setIon(true)
  396. .setBaseline(true)
  397. .setAsmJS(true)
  398. .setNativeRegExp(true);
  399. #endif
  400. JSObject* globalObj = JS_NewGlobalObject(_cx, &__globalClass, nullptr, JS::DontFireOnNewGlobalHook, options);
  401. if (nullptr == globalObj)
  402. return false;
  403. _globalObj = Object::_createJSObject(nullptr, globalObj);
  404. _globalObj->root();
  405. JS::RootedObject rootedGlobalObj(_cx, _globalObj->_getJSObject());
  406. _oldCompartment = JS_EnterCompartment(_cx, rootedGlobalObj);
  407. JS_InitStandardClasses(_cx, rootedGlobalObj) ;
  408. _globalObj->setProperty("window", Value(_globalObj));
  409. // SpiderMonkey isn't shipped with a console variable. Make a fake one.
  410. Value consoleVal;
  411. bool hasConsole = _globalObj->getProperty("console", &consoleVal) && consoleVal.isObject();
  412. assert(!hasConsole);
  413. HandleObject consoleObj(Object::createPlainObject());
  414. consoleObj->defineFunction("log", _SE(JSB_console_log));
  415. consoleObj->defineFunction("debug", _SE(JSB_console_debug));
  416. consoleObj->defineFunction("info", _SE(JSB_console_info));
  417. consoleObj->defineFunction("warn", _SE(JSB_console_warn));
  418. consoleObj->defineFunction("error", _SE(JSB_console_error));
  419. consoleObj->defineFunction("assert", _SE(JSB_console_assert));
  420. _globalObj->setProperty("console", Value(consoleObj));
  421. _globalObj->setProperty("scriptEngineType", Value("SpiderMonkey"));
  422. JS_DefineFunction(_cx, rootedGlobalObj, "log", __log, 0, JSPROP_PERMANENT);
  423. JS_DefineFunction(_cx, rootedGlobalObj, "forceGC", __forceGC, 0, JSPROP_READONLY | JSPROP_PERMANENT);
  424. // JS_AddWeakPointerZoneGroupCallback(_cx, ScriptEngine::onWeakPointerZoneGroupCallback, nullptr);
  425. JS_AddWeakPointerCompartmentCallback(_cx, ScriptEngine::onWeakPointerCompartmentCallback, nullptr);
  426. JS_FireOnNewGlobalObject(_cx, rootedGlobalObj);
  427. JS::SetBuildIdOp(_cx, getBytecodeBuildId);
  428. sc->jobQueue.init(_cx, JobQueue(js::SystemAllocPolicy()));
  429. JS::SetEnqueuePromiseJobCallback(_cx, onEnqueuePromiseJobCallback);
  430. JS::SetGetIncumbentGlobalCallback(_cx, onGetIncumbentGlobalCallback);
  431. __jsb_CCPrivateData_class = Class::create("__PrivateData", _globalObj, nullptr, privateDataContructor);
  432. __jsb_CCPrivateData_class->defineFinalizeFunction(privateDataFinalize);
  433. __jsb_CCPrivateData_class->install();
  434. _isValid = true;
  435. for (const auto& hook : _afterInitHookArray)
  436. {
  437. hook();
  438. }
  439. _afterInitHookArray.clear();
  440. return true;
  441. }
  442. ScriptEngine::~ScriptEngine()
  443. {
  444. cleanup();
  445. JS_ShutDown();
  446. }
  447. void ScriptEngine::cleanup()
  448. {
  449. if (!_isValid)
  450. return;
  451. _isInCleanup = true;
  452. for (const auto& hook : _beforeCleanupHookArray)
  453. {
  454. hook();
  455. }
  456. _beforeCleanupHookArray.clear();
  457. auto sc = getPromiseState(_cx);
  458. sc->quitting = true;
  459. SAFE_DEC_REF(_globalObj);
  460. Class::cleanup();
  461. Object::cleanup();
  462. // JS_RemoveWeakPointerZoneGroupCallback(_cx, ScriptEngine::onWeakPointerZoneGroupCallback);
  463. // JS_RemoveWeakPointerCompartmentCallback(_cx, ScriptEngine::onWeakPointerCompartmentCallback);
  464. JS_LeaveCompartment(_cx, _oldCompartment);
  465. JS::SetGetIncumbentGlobalCallback(_cx, nullptr);
  466. JS::SetEnqueuePromiseJobCallback(_cx, nullptr);
  467. sc->jobQueue.reset();
  468. JS_EndRequest(_cx);
  469. JS_DestroyContext(_cx);
  470. delete sc;
  471. _cx = nullptr;
  472. _globalObj = nullptr;
  473. _oldCompartment = nullptr;
  474. _isValid = false;
  475. _registerCallbackArray.clear();
  476. _filenameScriptMap.clear();
  477. for (const auto& hook : _afterCleanupHookArray)
  478. {
  479. hook();
  480. }
  481. _afterCleanupHookArray.clear();
  482. _isInCleanup = false;
  483. NativePtrToObjectMap::destroy();
  484. NonRefNativePtrCreatedByCtorMap::destroy();
  485. }
  486. void ScriptEngine::addBeforeCleanupHook(const std::function<void()>& hook)
  487. {
  488. _beforeCleanupHookArray.push_back(hook);
  489. }
  490. void ScriptEngine::addAfterCleanupHook(const std::function<void()>& hook)
  491. {
  492. _afterCleanupHookArray.push_back(hook);
  493. }
  494. void ScriptEngine::addBeforeInitHook(const std::function<void()>& hook)
  495. {
  496. _beforeInitHookArray.push_back(hook);
  497. }
  498. void ScriptEngine::addAfterInitHook(const std::function<void()>& hook)
  499. {
  500. _afterInitHookArray.push_back(hook);
  501. }
  502. bool ScriptEngine::isGarbageCollecting()
  503. {
  504. return _isGarbageCollecting;
  505. }
  506. void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting)
  507. {
  508. _isGarbageCollecting = isGarbageCollecting;
  509. }
  510. Object* ScriptEngine::getGlobalObject()
  511. {
  512. return _globalObj;
  513. }
  514. void ScriptEngine::addRegisterCallback(RegisterCallback cb)
  515. {
  516. assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end());
  517. _registerCallbackArray.push_back(cb);
  518. }
  519. #pragma mark - Debug
  520. static std::string inData;
  521. static std::string outData;
  522. static std::vector<std::string> g_queue;
  523. static std::mutex g_qMutex;
  524. static std::mutex g_rwMutex;
  525. static int clientSocket = -1;
  526. static uint32_t s_nestedLoopLevel = 0;
  527. static void cc_closesocket(int fd)
  528. {
  529. #ifdef _WIN32
  530. closesocket(fd);
  531. #else
  532. close(fd);
  533. #endif
  534. }
  535. void ScriptEngine::_debugProcessInput(const std::string& str)
  536. {
  537. JS::RootedObject debugGlobal(_cx, _debugGlobalObj->_getJSObject());
  538. JSCompartment *globalCpt = JS_EnterCompartment(_cx, debugGlobal);
  539. Value func;
  540. if (_debugGlobalObj->getProperty("processInput", &func) && func.isObject() && func.toObject()->isFunction())
  541. {
  542. ValueArray args;
  543. args.push_back(Value(str));
  544. func.toObject()->call(args, _debugGlobalObj);
  545. }
  546. JS_LeaveCompartment(_cx, globalCpt);
  547. }
  548. static bool NS_ProcessNextEvent()
  549. {
  550. std::string message;
  551. size_t messageCount = 0;
  552. while (true)
  553. {
  554. g_qMutex.lock();
  555. messageCount = g_queue.size();
  556. if (messageCount > 0)
  557. {
  558. auto first = g_queue.begin();
  559. message = *first;
  560. g_queue.erase(first);
  561. --messageCount;
  562. }
  563. g_qMutex.unlock();
  564. if (!message.empty())
  565. {
  566. ScriptEngine::getInstance()->_debugProcessInput(message);
  567. }
  568. if (messageCount == 0)
  569. break;
  570. }
  571. // std::this_thread::yield();
  572. std::this_thread::sleep_for(std::chrono::milliseconds(10));
  573. return true;
  574. }
  575. static bool JSBDebug_enterNestedEventLoop(State& s)
  576. {
  577. enum {
  578. NS_OK = 0,
  579. NS_ERROR_UNEXPECTED
  580. };
  581. #define NS_SUCCEEDED(v) ((v) == NS_OK)
  582. int rv = NS_OK;
  583. uint32_t nestLevel = ++s_nestedLoopLevel;
  584. while (NS_SUCCEEDED(rv) && s_nestedLoopLevel >= nestLevel) {
  585. drainJobQueue();
  586. if (!NS_ProcessNextEvent())
  587. rv = NS_ERROR_UNEXPECTED;
  588. }
  589. assert(s_nestedLoopLevel <= nestLevel);
  590. s.rval().setInt32(s_nestedLoopLevel);
  591. return true;
  592. }
  593. SE_BIND_FUNC(JSBDebug_enterNestedEventLoop)
  594. static bool JSBDebug_exitNestedEventLoop(State& s)
  595. {
  596. if (s_nestedLoopLevel > 0) {
  597. --s_nestedLoopLevel;
  598. } else {
  599. s.rval().setInt32(0);
  600. return true;
  601. }
  602. return true;
  603. }
  604. SE_BIND_FUNC(JSBDebug_exitNestedEventLoop)
  605. static bool JSBDebug_getEventLoopNestLevel(State& s)
  606. {
  607. s.rval().setInt32(s_nestedLoopLevel);
  608. return true;
  609. }
  610. SE_BIND_FUNC(JSBDebug_getEventLoopNestLevel)
  611. static void _clientSocketWriteAndClearString(std::string& s)
  612. {
  613. ::send(clientSocket, s.c_str(), s.length(), 0);
  614. s.clear();
  615. }
  616. static void processInput(const std::string& data) {
  617. std::lock_guard<std::mutex> lk(g_qMutex);
  618. g_queue.push_back(data);
  619. }
  620. static void clearBuffers() {
  621. std::lock_guard<std::mutex> lk(g_rwMutex);
  622. // only process input if there's something and we're not locked
  623. if (!inData.empty()) {
  624. processInput(inData);
  625. inData.clear();
  626. }
  627. if (!outData.empty()) {
  628. _clientSocketWriteAndClearString(outData);
  629. }
  630. }
  631. static void serverEntryPoint(uint32_t port)
  632. {
  633. // start a server, accept the connection and keep reading data from it
  634. struct addrinfo hints, *result = nullptr, *rp = nullptr;
  635. int s = 0;
  636. memset(&hints, 0, sizeof(struct addrinfo));
  637. hints.ai_family = AF_UNSPEC;
  638. hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
  639. hints.ai_flags = AI_PASSIVE; // fill in my IP for me
  640. std::stringstream portstr;
  641. portstr << port;
  642. int err = 0;
  643. #ifdef _WIN32
  644. WSADATA wsaData;
  645. err = WSAStartup(MAKEWORD(2, 2),&wsaData);
  646. #endif
  647. if ((err = getaddrinfo(NULL, portstr.str().c_str(), &hints, &result)) != 0) {
  648. SE_LOGD("getaddrinfo error : %s\n", gai_strerror(err));
  649. }
  650. for (rp = result; rp != NULL; rp = rp->ai_next) {
  651. if ((s = socket(rp->ai_family, rp->ai_socktype, 0)) < 0) {
  652. continue;
  653. }
  654. int optval = 1;
  655. if ((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval))) < 0) {
  656. cc_closesocket(s);
  657. TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_REUSEADDR\n");
  658. return;
  659. }
  660. #ifdef __APPLE__
  661. if ((setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval))) < 0) {
  662. close(s);
  663. TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_NOSIGPIPE\n");
  664. return;
  665. }
  666. #endif
  667. if ((::bind(s, rp->ai_addr, rp->ai_addrlen)) == 0) {
  668. break;
  669. }
  670. cc_closesocket(s);
  671. s = -1;
  672. }
  673. if (s < 0 || rp == NULL) {
  674. TRACE_DEBUGGER_SERVER("debug server : error creating/binding socket\n");
  675. return;
  676. }
  677. freeaddrinfo(result);
  678. listen(s, 1);
  679. #define MAX_RECEIVED_SIZE 1024
  680. #define BUF_SIZE MAX_RECEIVED_SIZE + 1
  681. char buf[BUF_SIZE] = {0};
  682. int readBytes = 0;
  683. while (true) {
  684. clientSocket = accept(s, NULL, NULL);
  685. if (clientSocket < 0)
  686. {
  687. TRACE_DEBUGGER_SERVER("debug server : error on accept\n");
  688. return;
  689. }
  690. else
  691. {
  692. // read/write data
  693. TRACE_DEBUGGER_SERVER("debug server : client connected\n");
  694. inData = "connected";
  695. // process any input, send any output
  696. clearBuffers();
  697. while ((readBytes = (int)::recv(clientSocket, buf, MAX_RECEIVED_SIZE, 0)) > 0)
  698. {
  699. buf[readBytes] = '\0';
  700. // TRACE_DEBUGGER_SERVER("debug server : received command >%s", buf);
  701. // no other thread is using this
  702. inData.append(buf);
  703. // process any input, send any output
  704. clearBuffers();
  705. } // while(read)
  706. cc_closesocket(clientSocket);
  707. }
  708. } // while(true)
  709. #undef BUF_SIZE
  710. #undef MAX_RECEIVED_SIZE
  711. }
  712. static bool JSBDebug_require(State& s)
  713. {
  714. const auto& args = s.args();
  715. int argc = (int)args.size();
  716. if (argc >= 1)
  717. {
  718. ScriptEngine::getInstance()->runScript(args[0].toString());
  719. return true;
  720. }
  721. SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 1);
  722. return false;
  723. }
  724. SE_BIND_FUNC(JSBDebug_require)
  725. static bool JSBDebug_BufferWrite(State& s)
  726. {
  727. const auto& args = s.args();
  728. int argc = (int)args.size();
  729. if (argc == 1)
  730. {
  731. // this is safe because we're already inside a lock (from clearBuffers)
  732. outData.append(args[0].toString());
  733. _clientSocketWriteAndClearString(outData);
  734. }
  735. return true;
  736. }
  737. SE_BIND_FUNC(JSBDebug_BufferWrite)
  738. bool ScriptEngine::start()
  739. {
  740. if (!init())
  741. return false;
  742. if (isDebuggerEnabled() && _debugGlobalObj == nullptr)
  743. {
  744. JS::CompartmentOptions options;
  745. options.behaviors().setVersion(JSVERSION_LATEST);
  746. options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
  747. JS::RootedObject debugGlobal(_cx, JS_NewGlobalObject(_cx, &__globalClass, nullptr, JS::DontFireOnNewGlobalHook, options));
  748. _debugGlobalObj = Object::_createJSObject(nullptr, debugGlobal);
  749. _debugGlobalObj->root();
  750. JSCompartment *globalCpt = JS_EnterCompartment(_cx, debugGlobal);
  751. JS_InitStandardClasses(_cx, debugGlobal);
  752. JS_FireOnNewGlobalObject(_cx, debugGlobal);
  753. JS_DefineDebuggerObject(_cx, debugGlobal);
  754. // these are used in the debug program
  755. JS_DefineFunction(_cx, debugGlobal, "log", __log, 0, JSPROP_PERMANENT);
  756. _debugGlobalObj->defineFunction("require", _SE(JSBDebug_require));
  757. _debugGlobalObj->defineFunction("_bufferWrite", _SE(JSBDebug_BufferWrite));
  758. _debugGlobalObj->defineFunction("_enterNestedEventLoop", _SE(JSBDebug_enterNestedEventLoop));
  759. _debugGlobalObj->defineFunction("_exitNestedEventLoop", _SE(JSBDebug_exitNestedEventLoop));
  760. _debugGlobalObj->defineFunction("_getEventLoopNestLevel", _SE(JSBDebug_getEventLoopNestLevel));
  761. JS::RootedObject globalObj(_cx, _globalObj->_getJSObject());
  762. JS_WrapObject(_cx, &globalObj);
  763. runScript("script/jsb_debugger.js");
  764. // prepare the debugger
  765. Value prepareDebuggerFunc;
  766. assert(_debugGlobalObj->getProperty("_prepareDebugger", &prepareDebuggerFunc) && prepareDebuggerFunc.isObject() && prepareDebuggerFunc.toObject()->isFunction());
  767. ValueArray args;
  768. args.push_back(Value(_globalObj));
  769. prepareDebuggerFunc.toObject()->call(args, _debugGlobalObj);
  770. // start bg thread
  771. auto t = std::thread(&serverEntryPoint, _debuggerServerPort);
  772. t.detach();
  773. JS_LeaveCompartment(_cx, globalCpt);
  774. }
  775. bool ok = false;
  776. _startTime = std::chrono::steady_clock::now();
  777. for (auto cb : _registerCallbackArray)
  778. {
  779. ok = cb(_globalObj);
  780. assert(ok);
  781. if (!ok)
  782. break;
  783. }
  784. // After ScriptEngine is started, _registerCallbackArray isn't needed. Therefore, clear it here.
  785. _registerCallbackArray.clear();
  786. return ok;
  787. }
  788. bool ScriptEngine::getScript(const std::string& path, JS::MutableHandleScript script)
  789. {
  790. std::string fullPath = _fileOperationDelegate.onGetFullPath(path);
  791. auto iter = _filenameScriptMap.find(fullPath);
  792. if (iter != _filenameScriptMap.end())
  793. {
  794. JS::PersistentRootedScript* rootedScript = iter->second;
  795. script.set(rootedScript->get());
  796. return true;
  797. }
  798. // // a) check jsc file first
  799. // std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
  800. // if (_filenameScriptMap.find(byteCodePath) != _filenameScriptMap.end())
  801. // {
  802. // script.set(_filenameScriptMap[byteCodePath]->get());
  803. // return true;
  804. // }
  805. //
  806. // // b) no jsc file, check js file
  807. // if (_filenameScriptMap.find(path) != _filenameScriptMap.end())
  808. // {
  809. // script.set(_filenameScriptMap[path]->get());
  810. // return true;
  811. // }
  812. return false;
  813. }
  814. bool ScriptEngine::compileScript(const std::string& path, JS::MutableHandleScript script)
  815. {
  816. if (path.empty())
  817. return false;
  818. bool ok = getScript(path, script);
  819. if (ok)
  820. return true;
  821. assert(_fileOperationDelegate.isValid());
  822. bool compileSucceed = false;
  823. // Creator v1.7 supports v8, spidermonkey, javascriptcore and chakracore as its script engine,
  824. // jsc file isn't bytecode format anymore, it's a xxtea encrpted binary format instead.
  825. // Therefore, for unifying the flow, ScriptEngine class will not support spidermonkey bytecode.
  826. // // a) check jsc file first
  827. // std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
  828. //
  829. // // Check whether '.jsc' files exist to avoid outputting log which says 'couldn't find .jsc file'.
  830. // if (_fileOperationDelegate.onCheckFileExist(byteCodePath))
  831. // {
  832. // _fileOperationDelegate.onGetDataFromFile(byteCodePath, [&](const uint8_t* data, size_t dataLen) {
  833. // if (data != nullptr && dataLen > 0)
  834. // {
  835. // JS::TranscodeBuffer buffer;
  836. // bool appended = buffer.append(data, dataLen);
  837. // JS::TranscodeResult result = JS::DecodeScript(_cx, buffer, script);
  838. // if (appended && result == JS::TranscodeResult::TranscodeResult_Ok)
  839. // {
  840. // compileSucceed = true;
  841. // _filenameScriptMap[byteCodePath] = new (std::nothrow) JS::PersistentRootedScript(_cx, script.get());
  842. // }
  843. // assert(compileSucceed);
  844. // }
  845. // });
  846. //
  847. // }
  848. // b) no jsc file, check js file
  849. if (!compileSucceed)
  850. {
  851. /* Clear any pending exception from previous failed decoding. */
  852. clearException();
  853. ok = false;
  854. std::string jsFileContent = _fileOperationDelegate.onGetStringFromFile(path);
  855. if (!jsFileContent.empty())
  856. {
  857. JS::CompileOptions op(_cx);
  858. op.setUTF8(true);
  859. op.setFileAndLine(path.c_str(), 1);
  860. ok = JS::Compile(_cx, op, jsFileContent.c_str(), jsFileContent.size(), script);
  861. if (ok)
  862. {
  863. compileSucceed = true;
  864. std::string fullPath = _fileOperationDelegate.onGetFullPath(path);
  865. _filenameScriptMap[fullPath] = new (std::nothrow) JS::PersistentRootedScript(_cx, script.get());
  866. }
  867. assert(compileSucceed);
  868. }
  869. }
  870. clearException();
  871. if (!compileSucceed)
  872. {
  873. SE_LOGD("ScriptEngine::compileScript fail:%s\n", path.c_str());
  874. }
  875. return compileSucceed;
  876. }
  877. bool ScriptEngine::evalString(const char* script, ssize_t length/* = -1 */, Value* ret/* = nullptr */, const char* fileName/* = nullptr */)
  878. {
  879. assert(script != nullptr);
  880. if (length < 0)
  881. length = strlen(script);
  882. if (fileName == nullptr)
  883. fileName = "(no filename)";
  884. JS::CompileOptions options(_cx);
  885. options.setFile(fileName)
  886. .setUTF8(true)
  887. .setVersion(JSVERSION_LATEST);
  888. JS::RootedValue rval(_cx);
  889. bool ok = JS::Evaluate(_cx, options, script, length, &rval);
  890. if (!ok)
  891. {
  892. clearException();
  893. }
  894. assert(ok);
  895. if (ok && ret != nullptr && !rval.isNullOrUndefined())
  896. {
  897. internal::jsToSeValue(_cx, rval, ret);
  898. }
  899. if (!ok)
  900. {
  901. SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName);
  902. }
  903. return ok;
  904. }
  905. void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate& delegate)
  906. {
  907. _fileOperationDelegate = delegate;
  908. }
  909. const ScriptEngine::FileOperationDelegate& ScriptEngine::getFileOperationDelegate() const
  910. {
  911. return _fileOperationDelegate;
  912. }
  913. bool ScriptEngine::runScript(const std::string& path, Value* ret/* = nullptr */)
  914. {
  915. assert(_fileOperationDelegate.isValid());
  916. JS::RootedScript script(_cx);
  917. bool ok = compileScript(path, &script);
  918. if (ok)
  919. {
  920. JS::RootedValue rval(_cx);
  921. ok = JS_ExecuteScript(_cx, script, &rval);
  922. if (!ok)
  923. {
  924. SE_LOGE("Evaluating %s failed (evaluatedOK == JS_FALSE)\n", path.c_str());
  925. clearException();
  926. }
  927. if (ok && ret != nullptr && !rval.isNullOrUndefined())
  928. {
  929. internal::jsToSeValue(_cx, rval, ret);
  930. }
  931. }
  932. return ok;
  933. }
  934. void ScriptEngine::clearException()
  935. {
  936. if (_cx == nullptr)
  937. return;
  938. if (JS_IsExceptionPending(_cx))
  939. {
  940. JS::RootedValue exceptionValue(_cx);
  941. JS_GetPendingException(_cx, &exceptionValue);
  942. JS_ClearPendingException(_cx);
  943. assert(exceptionValue.isObject());
  944. JS::RootedObject exceptionObj(_cx, exceptionValue.toObjectOrNull());
  945. JSErrorReport* report = JS_ErrorFromException(_cx, exceptionObj);
  946. const char* message = report->message().c_str();
  947. const std::string filePath = report->filename != nullptr ? report->filename : "(no filename)";
  948. char line[50] = {0};
  949. snprintf(line, sizeof(line), "%u", report->lineno);
  950. char column[50] = {0};
  951. snprintf(column, sizeof(column), "%u", report->column);
  952. const std::string location = filePath + ":" + line + ":" + column;
  953. char* stack = nullptr; // Need to be freed by JS_free
  954. JS::RootedValue stackVal(_cx);
  955. if (JS_GetProperty(_cx, exceptionObj, "stack", &stackVal) && stackVal.isString())
  956. {
  957. JS::RootedString jsstackStr(_cx, stackVal.toString());
  958. stack = JS_EncodeStringToUTF8(_cx, jsstackStr);
  959. }
  960. std::string exceptionStr = message;
  961. exceptionStr += ", location: " + location;
  962. if (stack != nullptr)
  963. {
  964. exceptionStr += "\nSTACK:\n";
  965. exceptionStr += stack;
  966. }
  967. SE_LOGE("ERROR: %s\n", exceptionStr.c_str());
  968. if (_exceptionCallback != nullptr)
  969. {
  970. _exceptionCallback(location.c_str(), message, stack);
  971. }
  972. if (!_isErrorHandleWorking)
  973. {
  974. _isErrorHandleWorking = true;
  975. Value errorHandler;
  976. if (_globalObj->getProperty("__errorHandler", &errorHandler) && errorHandler.isObject() && errorHandler.toObject()->isFunction())
  977. {
  978. ValueArray args;
  979. args.push_back(Value(filePath));
  980. args.push_back(Value(report->lineno));
  981. args.push_back(Value(message));
  982. args.push_back(Value(stack));
  983. errorHandler.toObject()->call(args, _globalObj);
  984. }
  985. _isErrorHandleWorking = false;
  986. }
  987. else
  988. {
  989. SE_LOGE("ERROR: __errorHandler has exception\n");
  990. }
  991. if (stack != nullptr)
  992. {
  993. JS_free(_cx, stack);
  994. stack = nullptr;
  995. }
  996. }
  997. }
  998. void ScriptEngine::setExceptionCallback(const ExceptionCallback& cb)
  999. {
  1000. _exceptionCallback = cb;
  1001. }
  1002. void ScriptEngine::enableDebugger(const std::string& serverAddr, uint32_t port, bool isWait)
  1003. {
  1004. _debuggerServerAddr = serverAddr;
  1005. _debuggerServerPort = port;
  1006. }
  1007. bool ScriptEngine::isDebuggerEnabled() const
  1008. {
  1009. return !_debuggerServerAddr.empty() && _debuggerServerPort > 0;
  1010. }
  1011. void ScriptEngine::mainLoopUpdate()
  1012. {
  1013. std::string message;
  1014. size_t messageCount = 0;
  1015. while (true)
  1016. {
  1017. g_qMutex.lock();
  1018. messageCount = g_queue.size();
  1019. if (messageCount > 0)
  1020. {
  1021. auto first = g_queue.begin();
  1022. message = *first;
  1023. g_queue.erase(first);
  1024. --messageCount;
  1025. }
  1026. g_qMutex.unlock();
  1027. if (!message.empty())
  1028. {
  1029. _debugProcessInput(message);
  1030. }
  1031. if (messageCount == 0)
  1032. break;
  1033. }
  1034. }
  1035. } // namespace se {
  1036. #endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM