no message

This commit is contained in:
gem
2025-02-18 15:21:31 +08:00
commit 2d133e56d7
1980 changed files with 465595 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -0,0 +1,66 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "BufferAllocator.h"
#include "base/Log.h"
#include "base/memory/Memory.h"
namespace se {
BufferAllocator::BufferAllocator(PoolType type)
: _type(type) {
}
BufferAllocator::~BufferAllocator() {
for (auto buffer : _buffers) {
buffer.second->decRef();
}
_buffers.clear();
}
se::Object *BufferAllocator::alloc(uint32_t index, uint32_t bytes) {
if (_buffers.count(index)) {
se::Object *oldObj = _buffers[index];
oldObj->decRef();
}
se::Object *obj = se::Object::createArrayBufferObject(nullptr, bytes);
_buffers[index] = obj;
uint8_t *ret = nullptr;
size_t len;
obj->getArrayBufferData(static_cast<uint8_t **>(&ret), &len);
return obj;
}
void BufferAllocator::free(uint32_t index) {
if (_buffers.count(index)) {
se::Object *oldObj = _buffers[index];
oldObj->decRef();
_buffers.erase(index);
}
}
} // namespace se

View File

@@ -0,0 +1,49 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "PoolType.h"
#include "cocos/base/Macros.h"
#include "cocos/base/std/container/unordered_map.h"
#include "cocos/bindings/jswrapper/Object.h"
namespace se {
class CC_DLL BufferAllocator final {
public:
explicit BufferAllocator(PoolType type);
~BufferAllocator();
se::Object *alloc(uint32_t index, uint32_t bytes);
void free(uint32_t index);
private:
static constexpr uint32_t BUFFER_MASK = ~(1 << 30);
ccstd::unordered_map<uint32_t, se::Object *> _buffers;
PoolType _type = PoolType::UNKNOWN;
};
} // namespace se

View File

@@ -0,0 +1,56 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "BufferPool.h"
#include "base/Macros.h"
#include "base/memory/Memory.h"
namespace se {
BufferPool::BufferPool(PoolType type, uint32_t entryBits, uint32_t bytesPerEntry)
: _allocator(type),
_entryBits(entryBits),
_bytesPerEntry(bytesPerEntry),
_type(type) {
_entriesPerChunk = 1 << entryBits;
_entryMask = _entriesPerChunk - 1;
_chunkMask = 0xffffffff & ~(_entryMask | POOL_FLAG);
_bytesPerChunk = _bytesPerEntry * _entriesPerChunk;
}
BufferPool::~BufferPool() = default;
se::Object *BufferPool::allocateNewChunk() {
se::Object *jsObj = _allocator.alloc(static_cast<uint32_t>(_chunks.size()), _bytesPerChunk);
uint8_t *realPtr = nullptr;
size_t len = 0;
jsObj->getArrayBufferData(&realPtr, &len);
_chunks.push_back(realPtr);
return jsObj;
}
} // namespace se

View File

@@ -0,0 +1,68 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "BufferAllocator.h"
#include "PoolType.h"
#include "base/std/container/vector.h"
#include "cocos/base/Macros.h"
#include "cocos/bindings/jswrapper/Object.h"
namespace se {
class CC_DLL BufferPool final {
public:
using Chunk = uint8_t *;
inline static uint32_t getPoolFlag() { return POOL_FLAG; }
BufferPool(PoolType type, uint32_t entryBits, uint32_t bytesPerEntry);
~BufferPool();
template <class T>
T *getTypedObject(uint32_t id) const {
uint32_t chunk = (_chunkMask & id) >> _entryBits;
uint32_t entry = _entryMask & id;
CC_ASSERT(chunk < _chunks.size() && entry < _entriesPerChunk);
return reinterpret_cast<T *>(_chunks[chunk] + (entry * _bytesPerEntry));
}
se::Object *allocateNewChunk();
private:
static constexpr uint32_t POOL_FLAG{1 << 30};
BufferAllocator _allocator;
ccstd::vector<Chunk> _chunks;
uint32_t _entryBits{1 << 8};
uint32_t _chunkMask{0};
uint32_t _entryMask{0};
uint32_t _bytesPerChunk{0};
uint32_t _entriesPerChunk{0};
uint32_t _bytesPerEntry{0};
PoolType _type{PoolType::UNKNOWN};
};
} // namespace se

View File

@@ -0,0 +1,37 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
namespace se {
#define CAST_POOL_TYPE(type) static_cast<uint32_t>(type)
#define GET_BUFFER_POOL_ID(type) CAST_POOL_TYPE(type)
enum class PoolType {
// Buffers
NODE,
UNKNOWN
};
} // namespace se

View File

@@ -0,0 +1,185 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "jsb_dop.h"
#include "BufferAllocator.h"
#include "BufferPool.h"
#include "cocos/bindings/manual/jsb_classtype.h"
#include "cocos/bindings/manual/jsb_conversions.h"
#include "cocos/bindings/manual/jsb_global.h"
/********************************************************
BufferPool binding
*******************************************************/
se::Class *jsb_BufferPool_class = nullptr; // NOLINT
static bool jsb_BufferPool_allocateNewChunk(se::State &s) { // NOLINT
auto *pool = static_cast<se::BufferPool *>(s.nativeThisObject());
SE_PRECONDITION2(pool, false, "Invalid Native Object");
s.rval().setObject(pool->allocateNewChunk());
return true;
}
SE_BIND_FUNC(jsb_BufferPool_allocateNewChunk);
SE_DECLARE_FINALIZE_FUNC(jsb_BufferPool_finalize)
static bool jsb_BufferPool_constructor(se::State &s) { // NOLINT
const auto &args = s.args();
size_t argc = args.size();
if (argc == 3) {
uint32_t poolType{0};
uint32_t entryBits{0};
uint32_t bytesPerEntry{0};
bool ok = true;
ok &= sevalue_to_native(args[0], &poolType);
ok &= sevalue_to_native(args[1], &entryBits);
ok &= sevalue_to_native(args[2], &bytesPerEntry);
if (!ok) {
SE_REPORT_ERROR("jsb_BufferPool_constructor: argument convertion error");
return false;
}
auto *pool = JSB_ALLOC(se::BufferPool, (se::PoolType)poolType, entryBits, bytesPerEntry);
s.thisObject()->setPrivateData(pool);
return true;
}
SE_REPORT_ERROR("jsb_BufferPool_constructor: wrong number of arguments: %d", (int)argc);
return false;
}
SE_BIND_CTOR(jsb_BufferPool_constructor, jsb_BufferPool_class, jsb_BufferPool_finalize) // NOLINT
static bool jsb_BufferPool_finalize(se::State &s) { // NOLINT
return true;
}
SE_BIND_FINALIZE_FUNC(jsb_BufferPool_finalize)
static bool js_register_se_BufferPool(se::Object *obj) { // NOLINT
se::Class *cls = se::Class::create("NativeBufferPool", obj, nullptr, _SE(jsb_BufferPool_constructor));
cls->defineFunction("allocateNewChunk", _SE(jsb_BufferPool_allocateNewChunk));
cls->install();
JSBClassType::registerClass<se::BufferPool>(cls);
jsb_BufferPool_class = cls; // NOLINT
se::ScriptEngine::getInstance()->clearException();
return true;
}
/*****************************************************
Array binding
******************************************************/
static se::Class *jsb_BufferAllocator_class = nullptr; // NOLINT
SE_DECLARE_FINALIZE_FUNC(jsb_BufferAllocator_finalize)
static bool jsb_BufferAllocator_constructor(se::State &s) { // NOLINT
const auto &args = s.args();
size_t argc = args.size();
if (argc == 1) {
uint32_t type{0};
auto *bufferAllocator = JSB_ALLOC(se::BufferAllocator, static_cast<se::PoolType>(type));
s.thisObject()->setPrivateData(bufferAllocator);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc);
return false;
}
SE_BIND_CTOR(jsb_BufferAllocator_constructor, jsb_BufferAllocator_class, jsb_BufferAllocator_finalize)
static bool jsb_BufferAllocator_finalize(se::State &s) { // NOLINT
return true;
}
SE_BIND_FINALIZE_FUNC(jsb_BufferAllocator_finalize)
static bool jsb_BufferAllocator_alloc(se::State &s) { // NOLINT
auto *bufferAllocator = static_cast<se::BufferAllocator *>(s.nativeThisObject());
SE_PRECONDITION2(bufferAllocator, false, "Invalid Native Object");
const auto &args = s.args();
size_t argc = args.size();
if (argc == 2) {
uint32_t index{0};
sevalue_to_native(args[0], &index);
uint32_t bytes{0};
sevalue_to_native(args[1], &bytes);
s.rval().setObject(bufferAllocator->alloc(index, bytes));
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc);
return false;
}
SE_BIND_FUNC(jsb_BufferAllocator_alloc);
static bool jsb_BufferAllocator_free(se::State &s) { // NOLINT
auto *bufferAllocator = static_cast<se::BufferAllocator *>(s.nativeThisObject());
SE_PRECONDITION2(bufferAllocator, false, "Invalid Native Object");
const auto &args = s.args();
size_t argc = args.size();
if (argc == 1) {
uint32_t index{0};
sevalue_to_native(args[0], &index);
bufferAllocator->free(index);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc);
return false;
}
SE_BIND_FUNC(jsb_BufferAllocator_free);
static bool js_register_se_BufferAllocator(se::Object *obj) { // NOLINT
se::Class *cls = se::Class::create("NativeBufferAllocator", obj, nullptr, _SE(jsb_BufferAllocator_constructor));
cls->defineFunction("alloc", _SE(jsb_BufferAllocator_alloc));
cls->defineFunction("free", _SE(jsb_BufferAllocator_free));
cls->install();
JSBClassType::registerClass<se::BufferAllocator>(cls);
jsb_BufferAllocator_class = cls; // NOLINT
se::ScriptEngine::getInstance()->clearException();
return true;
}
bool register_all_dop_bindings(se::Object *obj) { // NOLINT
// TODO(liuhang): Don't make dop into jsb namespace. Currently put it into jsb namesapce just to test codes.
se::Value nsVal;
if (!obj->getProperty("jsb", &nsVal)) {
se::HandleObject jsobj(se::Object::createPlainObject());
nsVal.setObject(jsobj);
obj->setProperty("jsb", nsVal);
}
se::Object *ns = nsVal.toObject();
js_register_se_BufferAllocator(ns); // NOLINT
js_register_se_BufferPool(ns); // NOLINT
return true;
}

View File

@@ -0,0 +1,31 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
namespace se {
class Object;
}
bool register_all_dop_bindings(se::Object *obj); // NOLINT

View File

@@ -0,0 +1,459 @@
/****************************************************************************
Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "EventDispatcher.h"
#include <cstdarg>
#include "cocos/application/ApplicationManager.h"
#include "cocos/bindings/jswrapper/HandleObject.h"
#include "cocos/bindings/jswrapper/SeApi.h"
#include "cocos/bindings/manual/jsb_global_init.h"
#include "cocos/platform/interfaces/modules/ISystemWindow.h"
#include "cocos/platform/interfaces/modules/ISystemWindowManager.h"
namespace {
se::Value tickVal;
se::ValueArray tickArgsValArr(1);
ccstd::vector<se::Object *> jsTouchObjPool;
se::Object *jsTouchObjArray = nullptr;
se::Object *jsMouseEventObj = nullptr;
se::Object *jsKeyboardEventObj = nullptr;
se::Object *jsControllerEventArray = nullptr;
se::Object *jsControllerChangeEventArray = nullptr;
se::Object *jsResizeEventObj = nullptr;
bool inited = false;
bool busListenerInited = false;
// attach the argument object to the function
void accessCacheArgObj(se::Object *func, se::Value *argObj, const char *cacheKey = "__reusedArgumentObject") {
func->getProperty(cacheKey, argObj);
if (argObj->isUndefined()) {
se::HandleObject argumentObj(se::Object::createPlainObject());
argObj->setObject(argumentObj);
}
}
} // namespace
namespace cc {
events::EnterForeground::Listener EventDispatcher::listenerEnterForeground;
events::EnterBackground::Listener EventDispatcher::listenerEnterBackground;
events::WindowChanged::Listener EventDispatcher::listenerWindowChanged;
events::LowMemory::Listener EventDispatcher::listenerLowMemory;
events::Touch::Listener EventDispatcher::listenerTouch;
events::Mouse::Listener EventDispatcher::listenerMouse;
events::Keyboard::Listener EventDispatcher::listenerKeyboard;
events::Controller::Listener EventDispatcher::listenerConroller;
events::ControllerChange::Listener EventDispatcher::listenerConrollerChange;
events::Tick::Listener EventDispatcher::listenerTick;
events::Resize::Listener EventDispatcher::listenerResize;
events::Orientation::Listener EventDispatcher::listenerOrientation;
events::RestartVM::Listener EventDispatcher::listenerRestartVM;
events::Close::Listener EventDispatcher::listenerClose;
events::PointerLock::Listener EventDispatcher::listenerPointerLock;
uint32_t EventDispatcher::hashListenerId = 1;
bool EventDispatcher::initialized() {
return inited && se::ScriptEngine::getInstance()->isValid();
}
void EventDispatcher::init() {
inited = true;
se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() {
EventDispatcher::destroy();
});
if (!busListenerInited) {
listenerTouch.bind(&dispatchTouchEvent);
listenerMouse.bind(&dispatchMouseEvent);
listenerKeyboard.bind(&dispatchKeyboardEvent);
listenerConroller.bind(&dispatchControllerEvent);
listenerConrollerChange.bind(&dispatchControllerChangeEvent);
listenerTick.bind(&dispatchTickEvent);
listenerResize.bind(&dispatchResizeEvent);
listenerOrientation.bind(&dispatchOrientationChangeEvent);
listenerEnterBackground.bind(&dispatchEnterBackgroundEvent);
listenerEnterForeground.bind(&dispatchEnterForegroundEvent);
listenerLowMemory.bind(&dispatchMemoryWarningEvent);
listenerClose.bind(&dispatchCloseEvent);
listenerRestartVM.bind(&dispatchRestartVM);
listenerPointerLock.bind(&dispatchPointerlockChangeEvent);
busListenerInited = true;
}
}
void EventDispatcher::destroy() {
for (auto *touchObj : jsTouchObjPool) {
touchObj->unroot();
touchObj->decRef();
}
jsTouchObjPool.clear();
if (jsTouchObjArray != nullptr) {
jsTouchObjArray->unroot();
jsTouchObjArray->decRef();
jsTouchObjArray = nullptr;
}
if (jsControllerEventArray != nullptr) {
jsControllerEventArray->unroot();
jsControllerEventArray->decRef();
jsControllerEventArray = nullptr;
}
if (jsControllerChangeEventArray != nullptr) {
jsControllerChangeEventArray->unroot();
jsControllerChangeEventArray->decRef();
jsControllerChangeEventArray = nullptr;
}
if (jsMouseEventObj != nullptr) {
jsMouseEventObj->unroot();
jsMouseEventObj->decRef();
jsMouseEventObj = nullptr;
}
if (jsKeyboardEventObj != nullptr) {
jsKeyboardEventObj->unroot();
jsKeyboardEventObj->decRef();
jsKeyboardEventObj = nullptr;
}
if (jsResizeEventObj != nullptr) {
jsResizeEventObj->unroot();
jsResizeEventObj->decRef();
jsResizeEventObj = nullptr;
}
inited = false;
tickVal.setUndefined();
}
void EventDispatcher::dispatchTouchEvent(const TouchEvent &touchEvent) {
se::AutoHandleScope scope;
if (!jsTouchObjArray) {
jsTouchObjArray = se::Object::createArrayObject(0);
jsTouchObjArray->root();
}
jsTouchObjArray->setProperty("length", se::Value(static_cast<uint32_t>(touchEvent.touches.size())));
while (jsTouchObjPool.size() < touchEvent.touches.size()) {
se::Object *touchObj = se::Object::createPlainObject();
touchObj->root();
jsTouchObjPool.emplace_back(touchObj);
}
uint32_t touchIndex = 0;
int poolIndex = 0;
for (const auto &touch : touchEvent.touches) {
se::Object *jsTouch = jsTouchObjPool.at(poolIndex++);
jsTouch->setProperty("identifier", se::Value(touch.index));
jsTouch->setProperty("clientX", se::Value(touch.x));
jsTouch->setProperty("clientY", se::Value(touch.y));
jsTouch->setProperty("pageX", se::Value(touch.x));
jsTouch->setProperty("pageY", se::Value(touch.y));
jsTouchObjArray->setArrayElement(touchIndex, se::Value(jsTouch));
++touchIndex;
}
const char *eventName = nullptr;
switch (touchEvent.type) {
case TouchEvent::Type::BEGAN:
eventName = "onTouchStart";
break;
case TouchEvent::Type::MOVED:
eventName = "onTouchMove";
break;
case TouchEvent::Type::ENDED:
eventName = "onTouchEnd";
break;
case TouchEvent::Type::CANCELLED:
eventName = "onTouchCancel";
break;
default:
CC_ABORT();
break;
}
se::ValueArray args;
args.emplace_back(se::Value(jsTouchObjArray));
args.emplace_back(se::Value(touchEvent.windowId));
EventDispatcher::doDispatchJsEvent(eventName, args);
}
void EventDispatcher::dispatchMouseEvent(const MouseEvent &mouseEvent) {
se::AutoHandleScope scope;
if (!jsMouseEventObj) {
jsMouseEventObj = se::Object::createPlainObject();
jsMouseEventObj->root();
}
const auto &xVal = se::Value(mouseEvent.x);
const auto &yVal = se::Value(mouseEvent.y);
const MouseEvent::Type type = mouseEvent.type;
if (type == MouseEvent::Type::WHEEL) {
jsMouseEventObj->setProperty("wheelDeltaX", xVal);
jsMouseEventObj->setProperty("wheelDeltaY", yVal);
} else {
if (type == MouseEvent::Type::DOWN || type == MouseEvent::Type::UP) {
jsMouseEventObj->setProperty("button", se::Value(mouseEvent.button));
}
jsMouseEventObj->setProperty("x", xVal);
jsMouseEventObj->setProperty("y", yVal);
if (type == MouseEvent::Type::MOVE) {
const auto &xDelta = se::Value(mouseEvent.xDelta);
const auto &yDelta = se::Value(mouseEvent.yDelta);
jsMouseEventObj->setProperty("xDelta", xDelta);
jsMouseEventObj->setProperty("yDelta", yDelta);
}
}
jsMouseEventObj->setProperty("windowId", se::Value(mouseEvent.windowId));
const char *eventName = nullptr;
const char *jsFunctionName = nullptr;
switch (type) {
case MouseEvent::Type::DOWN:
jsFunctionName = "onMouseDown";
break;
case MouseEvent::Type::MOVE:
jsFunctionName = "onMouseMove";
break;
case MouseEvent::Type::UP:
jsFunctionName = "onMouseUp";
break;
case MouseEvent::Type::WHEEL:
jsFunctionName = "onMouseWheel";
break;
default:
CC_ABORT();
break;
}
se::ValueArray args;
args.emplace_back(se::Value(jsMouseEventObj));
EventDispatcher::doDispatchJsEvent(jsFunctionName, args);
}
void EventDispatcher::dispatchKeyboardEvent(const KeyboardEvent &keyboardEvent) {
se::AutoHandleScope scope;
if (!jsKeyboardEventObj) {
jsKeyboardEventObj = se::Object::createPlainObject();
jsKeyboardEventObj->root();
}
const char *eventName = nullptr;
switch (keyboardEvent.action) {
case KeyboardEvent::Action::PRESS:
case KeyboardEvent::Action::REPEAT:
eventName = "onKeyDown";
break;
case KeyboardEvent::Action::RELEASE:
eventName = "onKeyUp";
break;
default:
CC_ABORT();
break;
}
jsKeyboardEventObj->setProperty("altKey", se::Value(keyboardEvent.altKeyActive));
jsKeyboardEventObj->setProperty("ctrlKey", se::Value(keyboardEvent.ctrlKeyActive));
jsKeyboardEventObj->setProperty("metaKey", se::Value(keyboardEvent.metaKeyActive));
jsKeyboardEventObj->setProperty("shiftKey", se::Value(keyboardEvent.shiftKeyActive));
jsKeyboardEventObj->setProperty("repeat", se::Value(keyboardEvent.action == KeyboardEvent::Action::REPEAT));
jsKeyboardEventObj->setProperty("keyCode", se::Value(keyboardEvent.key));
jsKeyboardEventObj->setProperty("windowId", se::Value(keyboardEvent.windowId));
jsKeyboardEventObj->setProperty("code", se::Value(keyboardEvent.code));
se::ValueArray args;
args.emplace_back(se::Value(jsKeyboardEventObj));
EventDispatcher::doDispatchJsEvent(eventName, args);
}
void EventDispatcher::dispatchControllerEvent(const ControllerEvent &controllerEvent) {
se::AutoHandleScope scope;
if (!jsControllerEventArray) {
jsControllerEventArray = se::Object::createArrayObject(0);
jsControllerEventArray->root();
}
const char *eventName = "onControllerInput";
if (controllerEvent.type == ControllerEvent::Type::HANDLE) {
eventName = "onHandleInput";
}
uint32_t controllerIndex = 0;
jsControllerEventArray->setProperty("length", se::Value(static_cast<uint32_t>(controllerEvent.controllerInfos.size())));
for (const auto &controller : controllerEvent.controllerInfos) {
se::HandleObject jsController{se::Object::createPlainObject()};
jsController->setProperty("id", se::Value(controller->napdId));
se::HandleObject jsButtonInfoList{se::Object::createArrayObject(static_cast<uint32_t>(controller->buttonInfos.size()))};
uint32_t buttonIndex = 0;
for (const auto &buttonInfo : controller->buttonInfos) {
se::HandleObject jsButtonInfo{se::Object::createPlainObject()};
jsButtonInfo->setProperty("code", se::Value(static_cast<uint32_t>(buttonInfo.key)));
jsButtonInfo->setProperty("isPressed", se::Value(static_cast<uint32_t>(buttonInfo.isPress)));
jsButtonInfoList->setArrayElement(buttonIndex, se::Value(jsButtonInfo));
buttonIndex++;
}
se::HandleObject jsAxisInfoList{se::Object::createArrayObject(static_cast<uint32_t>(controller->axisInfos.size()))};
uint32_t axisIndex = 0;
for (const auto &axisInfo : controller->axisInfos) {
se::HandleObject jsAxisInfo{se::Object::createPlainObject()};
jsAxisInfo->setProperty("code", se::Value(static_cast<uint32_t>(axisInfo.axis)));
jsAxisInfo->setProperty("value", se::Value(axisInfo.value));
jsAxisInfoList->setArrayElement(axisIndex, se::Value(jsAxisInfo));
axisIndex++;
}
se::HandleObject jsTouchInfoList{se::Object::createArrayObject(static_cast<uint32_t>(controller->touchInfos.size()))};
uint32_t touchIndex = 0;
for (const auto &touchInfo : controller->touchInfos) {
se::HandleObject jsTouchInfo{se::Object::createPlainObject()};
jsTouchInfo->setProperty("code", se::Value(static_cast<uint32_t>(touchInfo.key)));
jsTouchInfo->setProperty("value", se::Value(touchInfo.value));
jsTouchInfoList->setArrayElement(touchIndex, se::Value(jsTouchInfo));
touchIndex++;
}
jsController->setProperty("axisInfoList", se::Value(jsAxisInfoList));
jsController->setProperty("buttonInfoList", se::Value(jsButtonInfoList));
jsController->setProperty("touchInfoList", se::Value(jsTouchInfoList));
jsControllerEventArray->setArrayElement(controllerIndex, se::Value(jsController));
controllerIndex++;
}
se::ValueArray args;
args.emplace_back(se::Value(jsControllerEventArray));
EventDispatcher::doDispatchJsEvent(eventName, args);
}
void EventDispatcher::dispatchControllerChangeEvent(const ControllerChangeEvent &changeEvent) {
se::AutoHandleScope scope;
if (!jsControllerChangeEventArray) {
jsControllerChangeEventArray = se::Object::createArrayObject(0);
jsControllerChangeEventArray->root();
}
const char *eventName = "onControllerChange";
jsControllerChangeEventArray->setProperty("length", se::Value(static_cast<uint32_t>(changeEvent.controllerIds.size())));
int index = 0;
for (const auto id : changeEvent.controllerIds) {
jsControllerChangeEventArray->setArrayElement(index++, se::Value(id));
}
se::ValueArray args;
args.emplace_back(se::Value(jsControllerChangeEventArray));
EventDispatcher::doDispatchJsEvent(eventName, args);
}
void EventDispatcher::dispatchTickEvent(float /*dt*/) {
if (!se::ScriptEngine::getInstance()->isValid()) {
return;
}
se::AutoHandleScope scope;
if (tickVal.isUndefined()) {
se::ScriptEngine::getInstance()->getGlobalObject()->getProperty("gameTick", &tickVal);
}
static std::chrono::steady_clock::time_point prevTime;
prevTime = std::chrono::steady_clock::now();
int64_t milliSeconds = std::chrono::duration_cast<std::chrono::milliseconds>(prevTime - se::ScriptEngine::getInstance()->getStartTime()).count();
tickArgsValArr[0].setDouble(static_cast<double>(milliSeconds));
if (!tickVal.isUndefined()) {
tickVal.toObject()->call(tickArgsValArr, nullptr);
}
}
// NOLINTNEXTLINE
void EventDispatcher::dispatchResizeEvent(int width, int height, uint32_t windowId) {
se::AutoHandleScope scope;
if (!jsResizeEventObj) {
jsResizeEventObj = se::Object::createPlainObject();
jsResizeEventObj->root();
}
jsResizeEventObj->setProperty("windowId", se::Value(windowId));
jsResizeEventObj->setProperty("width", se::Value(width));
jsResizeEventObj->setProperty("height", se::Value(height));
se::ValueArray args;
args.emplace_back(se::Value(jsResizeEventObj));
EventDispatcher::doDispatchJsEvent("onResize", args);
}
void EventDispatcher::dispatchOrientationChangeEvent(int orientation) {
// Ts's logic is same as the 'onResize', so remove code here temporary.
}
void EventDispatcher::dispatchEnterBackgroundEvent() {
EventDispatcher::doDispatchJsEvent("onPause", se::EmptyValueArray);
}
void EventDispatcher::dispatchEnterForegroundEvent() {
EventDispatcher::doDispatchJsEvent("onResume", se::EmptyValueArray);
}
void EventDispatcher::dispatchMemoryWarningEvent() {
EventDispatcher::doDispatchJsEvent("onMemoryWarning", se::EmptyValueArray);
}
void EventDispatcher::dispatchRestartVM() {
EventDispatcher::doDispatchJsEvent("onRestartVM", se::EmptyValueArray);
}
void EventDispatcher::dispatchCloseEvent() {
EventDispatcher::doDispatchJsEvent("onClose", se::EmptyValueArray);
}
void EventDispatcher::dispatchPointerlockChangeEvent(bool value) {
se::ValueArray args;
args.emplace_back(se::Value(value));
EventDispatcher::doDispatchJsEvent("onPointerlockChange", args);
}
void EventDispatcher::doDispatchJsEvent(const char *jsFunctionName, const std::vector<se::Value> &args) {
if (!se::ScriptEngine::getInstance()->isValid()) {
return;
}
se::AutoHandleScope scope;
CC_ASSERT(inited);
se::Value func;
__jsbObj->getProperty(jsFunctionName, &func);
if (func.isObject() && func.toObject()->isFunction()) {
func.toObject()->call(args, nullptr);
}
}
} // end of namespace cc

View File

@@ -0,0 +1,79 @@
/****************************************************************************
Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include <functional>
#include <memory>
#include "engine/EngineEvents.h"
namespace se {
class Value;
}
namespace cc {
class EventDispatcher {
public:
static void init();
static void destroy();
static bool initialized();
static void doDispatchJsEvent(const char *jsFunctionName, const std::vector<se::Value> &args);
private:
static void dispatchTouchEvent(const TouchEvent &touchEvent);
static void dispatchMouseEvent(const MouseEvent &mouseEvent);
static void dispatchKeyboardEvent(const KeyboardEvent &keyboardEvent);
static void dispatchControllerEvent(const ControllerEvent &controllerEvent);
static void dispatchControllerChangeEvent(const ControllerChangeEvent &changeEvent);
static void dispatchTickEvent(float dt);
static void dispatchResizeEvent(int width, int height, uint32_t windowId = UINT32_MAX);
static void dispatchOrientationChangeEvent(int orientation);
static void dispatchEnterBackgroundEvent();
static void dispatchEnterForegroundEvent();
static void dispatchMemoryWarningEvent();
static void dispatchRestartVM();
static void dispatchCloseEvent();
static void dispatchPointerlockChangeEvent(bool value);
static uint32_t hashListenerId; // simple increment hash
static events::EnterForeground::Listener listenerEnterForeground;
static events::EnterBackground::Listener listenerEnterBackground;
static events::WindowChanged::Listener listenerWindowChanged;
static events::LowMemory::Listener listenerLowMemory;
static events::Touch::Listener listenerTouch;
static events::Mouse::Listener listenerMouse;
static events::Keyboard::Listener listenerKeyboard;
static events::Controller::Listener listenerConroller;
static events::ControllerChange::Listener listenerConrollerChange;
static events::Tick::Listener listenerTick;
static events::Resize::Listener listenerResize;
static events::Orientation::Listener listenerOrientation;
static events::RestartVM::Listener listenerRestartVM;
static events::Close::Listener listenerClose;
static events::PointerLock::Listener listenerPointerLock;
};
} // end of namespace cc

View File

@@ -0,0 +1,47 @@
/****************************************************************************
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "base/TypeDef.h"
namespace se {
/**
* PropertyAttribute.
* @note Use the same value as which defined in v8, we convert it to v8::PropertyAttribute directly, so don't change the enum value.
*/
enum class PropertyAttribute : uint8_t {
/** NONE. **/
NONE = 0,
/** READ_ONLY, i.e., not writable. **/
READ_ONLY = 1 << 0,
/** DONT_ENUM, i.e., not enumerable. **/
DONT_ENUM = 1 << 1,
/** DONT_DELETE, i.e., not configurable. **/
DONT_DELETE = 1 << 2
};
CC_ENUM_BITWISE_OPERATORS(PropertyAttribute);
} // namespace se

View File

@@ -0,0 +1,58 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "HandleObject.h"
#include "Object.h"
namespace se {
HandleObject::HandleObject(Object *obj)
: _obj(obj) {
if (_obj != nullptr) {
// se::HandleObject could not be used for native binding object.
CC_ASSERT(!_obj->_getClass());
_obj->root();
}
}
HandleObject::HandleObject(HandleObject &&o) noexcept{
_obj = o._obj;
o._obj = nullptr;
}
HandleObject::~HandleObject() {
if (_obj != nullptr) {
_obj->unroot();
_obj->decRef();
}
}
HandleObject& HandleObject::operator=(HandleObject &&o) noexcept {
_obj = o._obj;
o._obj = nullptr;
return *this;
}
} // namespace se

View File

@@ -0,0 +1,114 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include <cstddef>
namespace se {
class Object;
/**
* HandleObject is a helper class for easily release, root and unroot an non-native-binding se::Object.
{
se::HandleObject obj(se::Object::createPlainObject());
obj->setProperty(...);
otherObject->setProperty("foo", se::Value(obj));
}
is equal to:
{
se::Object* obj = se::Object::createPlainObject();
obj->root(); // root after object created to avoid object is garbage collected
obj->setProperty(...);
otherObject->setProperty("foo", se::Value(obj));
obj->unroot(); // unroot object after obj is used.
obj->decRef(); // Decrement referent count to avoid memory leak.
}
HandleObject should not be used to create a native binding object since the created binding object
should be holded by JavaScript VM and released in finalize callback internally.
*/
class HandleObject {
public:
/**
* @brief The constructor of HandleObject
* @param[in] obj The se::Object to attach.
*/
explicit HandleObject(Object *obj);
/**
* @brief The destructor of HandleObject
*/
~HandleObject();
/**
* @brief The pointer operator
* @return The se::Object attached.
*/
inline Object *operator->() const {
return _obj;
}
inline operator Object *() const { // NOLINT
return _obj;
}
/**
* @brief Gets the se::Object attached.
* @return The se::Object attached.
*/
inline Object *get() const {
return _obj;
}
/**
* @brief Tests whether HandleObject holds an invalid se::Object.
* @return true if HandleObject holds an invalid se::Object, otherwise false.
*/
inline bool isEmpty() const {
return (_obj == nullptr);
}
HandleObject(HandleObject &&) noexcept;
HandleObject& operator=(HandleObject &&) noexcept;
HandleObject(const HandleObject &) = delete;
HandleObject& operator=(const HandleObject &) = delete;
void *operator new(size_t size) = delete;
void operator delete(void *, size_t) = delete;
private:
Object *_obj;
friend class Object;
};
} // namespace se

View File

@@ -0,0 +1,104 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "MappingUtils.h"
#include "base/memory/Memory.h"
namespace se {
// NativePtrToObjectMap
NativePtrToObjectMap::Map *NativePtrToObjectMap::__nativePtrToObjectMap = nullptr; // NOLINT
bool NativePtrToObjectMap::__isValid = false; // NOLINT
bool NativePtrToObjectMap::init() {
if (__nativePtrToObjectMap == nullptr) {
__nativePtrToObjectMap = ccnew NativePtrToObjectMap::Map();
}
__isValid = true;
return __nativePtrToObjectMap != nullptr;
}
void NativePtrToObjectMap::destroy() {
if (__nativePtrToObjectMap != nullptr) {
delete __nativePtrToObjectMap;
__nativePtrToObjectMap = nullptr;
}
__isValid = false;
}
bool NativePtrToObjectMap::isValid() {
return __isValid;
}
CC_DEPRECATED(3.7)
NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::find(void *v) {
return __nativePtrToObjectMap->find(v);
}
CC_DEPRECATED(3.7)
NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::begin() {
return __nativePtrToObjectMap->begin();
}
CC_DEPRECATED(3.7)
NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::end() {
return __nativePtrToObjectMap->end();
}
void NativePtrToObjectMap::emplace(void *nativeObj, Object *seObj) {
__nativePtrToObjectMap->emplace(nativeObj, seObj);
}
NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::erase(Map::iterator iter) {
return __nativePtrToObjectMap->erase(iter);
}
void NativePtrToObjectMap::erase(void *nativeObj) {
__nativePtrToObjectMap->erase(nativeObj);
}
void NativePtrToObjectMap::erase(void *nativeObj, se::Object *obj) {
auto range = __nativePtrToObjectMap->equal_range(nativeObj);
for (auto itr = range.first; itr != range.second; itr++) {
if (itr->second == obj) {
__nativePtrToObjectMap->erase(itr);
break;
}
}
}
void NativePtrToObjectMap::clear() {
__nativePtrToObjectMap->clear();
}
size_t NativePtrToObjectMap::size() {
return __nativePtrToObjectMap->size();
}
const NativePtrToObjectMap::Map &NativePtrToObjectMap::instance() {
return *__nativePtrToObjectMap;
}
} // namespace se

View File

@@ -0,0 +1,190 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include <type_traits>
#include "base/std/container/unordered_map.h"
#include "bindings/manual/jsb_classtype.h"
namespace se {
class Object;
class NativePtrToObjectMap {
public:
// key: native ptr, value: se::Object
using Map = ccstd::unordered_multimap<void *, Object *>;
struct OptionalCallback {
se::Object *seObj{nullptr};
/**
* @brief Invoke callback function when object is empty
*/
template <typename Fn>
OptionalCallback orElse(const Fn &fn) {
if (!seObj) {
fn();
}
return *this;
}
/**
* @brief Invoke callback function when object is **NOT** empty
*/
template <typename Fn>
OptionalCallback forEach(const Fn &fn) {
if (seObj) {
fn(seObj);
}
return *this;
}
};
static bool init();
static void destroy();
static bool isValid();
/**
* @deprecated Use `contains` or `filter` to query or manipulate the elements of the map,
*/
CC_DEPRECATED(3.7)
static Map::iterator find(void *v);
/**
* @deprecated Use `contains` or `filter` to query or manipulate the elements of the map,
*/
CC_DEPRECATED(3.7)
static Map::iterator begin();
/**
* @deprecated Use `contains` or `filter` to query or manipulate the elements of the map,
*/
CC_DEPRECATED(3.7)
static Map::iterator end();
static Map::iterator erase(Map::iterator iter);
static void erase(void *nativeObj);
static void erase(void *nativeObj, se::Object *);
static void clear();
static size_t size();
static const Map &instance();
/**
* @brief Return the first element of the specified key
*/
template <typename T>
static se::Object *findFirst(T *nativeObj) {
auto itr = __nativePtrToObjectMap->find(nativeObj);
return itr == __nativePtrToObjectMap->end() ? nullptr : itr->second;
}
/**
* @brief Check if the key exists in the map
*/
template <typename T>
static bool contains(T *nativeObj) {
if constexpr (std::is_void_v<T>) {
return __nativePtrToObjectMap->count(nativeObj) > 0;
} else {
auto *kls = JSBClassType::findClass(nativeObj);
auto range = __nativePtrToObjectMap->equal_range(nativeObj);
for (auto itr = range.first; itr != range.second; itr++) {
if (itr->second->_getClass() == kls) {
return true;
}
}
return false;
}
}
/**
* @brief Iterate se::Object with specified key
*/
template <typename T, typename Fn>
static void forEach(T *nativeObj, const Fn &func) {
se::Class *kls = nullptr;
if constexpr (!std::is_void_v<T>) {
kls = JSBClassType::findClass(nativeObj);
}
auto range = __nativePtrToObjectMap->equal_range(nativeObj);
for (auto itr = range.first; itr != range.second; itr++) {
if (kls != nullptr && kls != itr->second->_getClass()) {
continue;
}
func(itr->second);
}
}
/**
* @brief Filter se::Object* with key and se::Class value
*/
template <typename T>
static OptionalCallback filter(T *nativeObj, se::Class *kls) {
se::Object *target{nullptr};
findWithCallback(
nativeObj, kls,
[&](se::Object *seObj) {
target = seObj;
},
nullptr);
return OptionalCallback{target};
}
private:
/**
* @brief Iterate se::Object with specified se::Class
*/
template <typename T, typename Fn1, typename Fn2>
static void findWithCallback(T *nativeObj, se::Class *kls, const Fn1 &eachCallback, const Fn2 &&emptyCallback) {
int eleCount = 0;
auto range = __nativePtrToObjectMap->equal_range(const_cast<std::remove_const_t<T> *>(nativeObj));
constexpr bool hasEmptyCallback = std::is_invocable<Fn2>::value;
if (range.first == range.second) { // empty
if constexpr (hasEmptyCallback) {
emptyCallback();
}
} else {
for (auto itr = range.first; itr != range.second; ++itr) {
if (kls != nullptr && kls != itr->second->_getClass()) {
continue;
}
eleCount++;
CC_ASSERT_LT(eleCount, 2);
eachCallback(itr->second);
}
if constexpr (hasEmptyCallback) {
if (eleCount == 0) {
emptyCallback();
}
}
}
}
static void emplace(void *nativeObj, Object *seObj);
static Map *__nativePtrToObjectMap; // NOLINT
static bool __isValid; // NOLINT
friend class Object;
};
} // namespace se

View File

@@ -0,0 +1,49 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "sm/Object.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "v8/Object.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "jsc/Object.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_CHAKRACORE
#include "chakracore/Object.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_NAPI
#include "napi/Object.h"
#endif

View File

@@ -0,0 +1,248 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include <cmath>
#include <memory>
#include <type_traits>
#include "base/Ptr.h"
#include "base/RefCounted.h"
#include "base/memory/Memory.h"
#include "base/HasMemberFunction.h"
namespace se {
class Object;
class State;
class ScriptEngine;
template <typename T>
class TypedPrivateObject;
class PrivateObjectBase {
public:
virtual ~PrivateObjectBase() = default;
template <typename T>
inline T *get() const {
return reinterpret_cast<T *>(getRaw());
}
template <typename T>
inline TypedPrivateObject<T> *typed() {
return reinterpret_cast<TypedPrivateObject<T> *>(this);
}
virtual const char *getName() const = 0;
virtual void *getRaw() const = 0;
virtual void allowDestroyInGC() const {
CC_ABORT();
}
virtual void tryAllowDestroyInGC() const {}
virtual bool isSharedPtr() const { return false; }
virtual bool isCCIntrusivePtr() const { return false; }
friend se::Object;
friend se::State;
friend se::ScriptEngine;
void *finalizerData{nullptr};
};
template <typename T>
class TypedPrivateObject : public PrivateObjectBase {
public:
inline std::shared_ptr<T> share();
inline cc::IntrusivePtr<T> &ccShared();
inline const char *getName() const override {
static_assert(!std::is_base_of<PrivateObjectBase, T>::value, ""); // NOLINT // remove after using c++17
return typeid(T).name();
}
};
template <typename T>
class SharedPtrPrivateObject final : public TypedPrivateObject<T> {
public:
SharedPtrPrivateObject() = default;
explicit SharedPtrPrivateObject(const std::shared_ptr<T> &ptr) : _data(ptr) {}
explicit SharedPtrPrivateObject(std::shared_ptr<T> &&ptr) : _data(std::move(ptr)) {}
inline const std::shared_ptr<T> &getData() const {
return _data;
}
inline std::shared_ptr<T> &getData() {
return _data;
}
constexpr bool isSharedPtr() const override { return true; }
void *getRaw() const override {
if constexpr (std::is_const_v<T>) {
return reinterpret_cast<void *>(const_cast<std::remove_const_t<T> *>(_data.get()));
} else {
return reinterpret_cast<void *>(_data.get());
}
}
private:
std::shared_ptr<T> _data{nullptr};
};
template <typename T>
class CCIntrusivePtrPrivateObject final : public TypedPrivateObject<T> {
public:
CCIntrusivePtrPrivateObject() = default;
explicit CCIntrusivePtrPrivateObject(const cc::IntrusivePtr<T> &p) : _ptr(p) {}
explicit CCIntrusivePtrPrivateObject(cc::IntrusivePtr<T> &&p) : _ptr(std::move(p)) {}
~CCIntrusivePtrPrivateObject() override {
if constexpr (cc::has_setScriptObject<T,void(se::Object *)>::value) {
_ptr->setScriptObject(nullptr);
}
}
inline const cc::IntrusivePtr<T> &getData() const { return _ptr; }
inline cc::IntrusivePtr<T> &getData() { return _ptr; }
inline void *getRaw() const override {
if constexpr (std::is_const_v<T>) {
return reinterpret_cast<void *>(const_cast<std::remove_const_t<T> *>(_ptr.get()));
} else {
return reinterpret_cast<void *>(_ptr.get());
}
}
inline bool isCCIntrusivePtr() const override { return true; }
private:
cc::IntrusivePtr<T> _ptr;
friend TypedPrivateObject<T>;
};
template <typename T>
class RawRefPrivateObject final : public TypedPrivateObject<T> {
public:
RawRefPrivateObject() = default;
explicit RawRefPrivateObject(T *p) : _ptr(p) {}
~RawRefPrivateObject() override {
static_assert(!std::is_same<T, void>::value, "void is not allowed!");
if constexpr (std::is_destructible<T>::value) {
if (_allowGC) {
delete _ptr;
}
}
_ptr = nullptr;
}
void allowDestroyInGC() const override {
_allowGC = true;
}
void tryAllowDestroyInGC() const override {
allowDestroyInGC();
}
void *getRaw() const override {
// CC_ASSERT(_validate);
if constexpr (std::is_const_v<T>) {
return reinterpret_cast<void *>(const_cast<std::remove_const_t<T> *>(_ptr));
} else {
return reinterpret_cast<void *>(_ptr);
}
}
private:
T *_ptr = nullptr;
// bool _validate = true;
mutable bool _allowGC = false;
};
template <typename T>
inline std::shared_ptr<T> TypedPrivateObject<T>::share() {
if (isSharedPtr()) {
return reinterpret_cast<SharedPtrPrivateObject<T> *>(this)->getData();
}
CC_ABORT();
return std::shared_ptr<T>(nullptr);
}
template <typename T>
inline cc::IntrusivePtr<T> &TypedPrivateObject<T>::ccShared() {
CC_ASSERT(isCCIntrusivePtr());
return reinterpret_cast<CCIntrusivePtrPrivateObject<T> *>(this)->_ptr;
}
#if CC_DEBUG
inline void inHeap(void *ptr) {
constexpr size_t r = 4 * 1024; // 4K
char a;
auto anchor = reinterpret_cast<intptr_t>(&a);
auto p = reinterpret_cast<intptr_t>(ptr);
// must be in heaps
CC_ASSERT(std::abs(anchor - p) > r);
}
#endif
template <typename T>
inline auto *make_shared_private_object(T *cobj) { // NOLINT
static_assert(!std::is_same<T, void>::value, "void * is not allowed");
// static_assert(!std::is_pointer_v<T> && !std::is_null_pointer_v<decltype(cobj)>, "bad pointer");
#if CC_DEBUG
inHeap(cobj);
#endif
if constexpr (std::is_base_of<cc::RefCounted, T>::value) {
return ccnew CCIntrusivePtrPrivateObject<T>(cc::IntrusivePtr<T>(cobj));
} else {
return ccnew SharedPtrPrivateObject<T>(std::shared_ptr<T>(cobj));
}
}
template <typename T>
inline auto *shared_ptr_private_object(std::shared_ptr<T> &&ptr) { // NOLINT
static_assert(!std::is_base_of<cc::RefCounted, T>::value, "cc::RefCounted is not acceptable for shared_ptr");
return ccnew SharedPtrPrivateObject<T>(std::forward<std::shared_ptr<T>>(ptr));
}
template <typename T>
inline auto *shared_ptr_private_object(const std::shared_ptr<T> &ptr) { // NOLINT
static_assert(!std::is_base_of<cc::RefCounted, T>::value, "cc::RefCounted is not acceptable for shared_ptr");
return ccnew SharedPtrPrivateObject<T>(ptr);
}
template <typename T>
inline auto *rawref_private_object(T *ptr) { // NOLINT
// static_assert(false, "always fail");
// static_assert(!std::is_base_of<cc::RefCounted, T>::value, "cc::RefCounted is not acceptable for shared_ptr");
#if CC_DEBUG
inHeap(ptr);
#endif
return ccnew RawRefPrivateObject<T>(ptr);
}
template <typename T>
inline auto *ccintrusive_ptr_private_object(const cc::IntrusivePtr<T> &ptr) { // NOLINT
static_assert(std::is_base_of<cc::RefCounted, T>::value, "cc::RefCounted expected!");
return ccnew CCIntrusivePtrPrivateObject<T>(ptr);
}
template <typename T>
inline auto *ccintrusive_ptr_private_object(T *cobj) { // NOLINT
static_assert(std::is_base_of<cc::RefCounted, T>::value, "cc::RefCounted expected!");
return ccnew CCIntrusivePtrPrivateObject<T>(cc::IntrusivePtr<T>(cobj));
}
} // namespace se

View File

@@ -0,0 +1,52 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "RefCounter.h"
namespace se {
void RefCounter::incRef() {
++_refCount;
}
void RefCounter::decRef() {
--_refCount;
if (_refCount == 0) {
delete this;
}
}
unsigned int RefCounter::getRefCount() {
return _refCount;
}
RefCounter::RefCounter()
: _refCount(1) {
}
RefCounter::~RefCounter() {
}
} // namespace se

View File

@@ -0,0 +1,65 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
namespace se {
/**
* This class is used to manage reference-counting providing a simple interface and a counter.
*
*/
class RefCounter {
public:
/**
* @brief Increases reference count by one.
*/
void incRef();
/**
* @brief Decrements the reference count, if it reaches zero, destroys this instance of RefCounter to release its memory.
* @note Please note that after calling this function, the caller should absolutely avoid to use the pointer to this instance since it may not be valid anymore.
*/
void decRef();
/**
* @brief Gets reference count.
* @return The reference count.
* @note When this goes to zero during a decRef() call, the object will auto-delete itself.
*/
unsigned int getRefCount();
protected:
// Default constructor
// Initialises the internal reference count to 1.
RefCounter();
// Destructor
virtual ~RefCounter();
private:
unsigned int _refCount;
};
} // namespace se

View File

@@ -0,0 +1,53 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "sm/SeApi.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "v8/SeApi.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "jsc/SeApi.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_CHAKRACORE
#include "chakracore/SeApi.h"
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_NAPI
#include "napi/SeApi.h"
#endif
#include "HandleObject.h"
#include "Object.h"
#include "State.h"
#include "Value.h"

View File

@@ -0,0 +1,111 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "Object.h"
#include "PrivateObject.h"
#include "Value.h"
namespace se {
class Object;
/**
* State represents an environment while a function or an accesstor is invoked from JavaScript.
*/
class State final {
public:
/**
* @brief Gets void* pointer of `this` object's private data.
* @return A void* pointer of `this` object's private data.
*/
inline void *nativeThisObject() const {
return _thisObject != nullptr ? _thisObject->getPrivateData() : nullptr;
}
/**
* @brief Gets the arguments of native binding functions or accesstors.
* @return The arguments of native binding functions or accesstors.
*/
inline const ValueArray &args() const {
return _args != nullptr ? (*_args) : EmptyValueArray;
}
/**
* @brief Gets the JavaScript `this` object wrapped in se::Object.
* @return The JavaScript `this` object wrapped in se::Object.
*/
inline Object *thisObject() const {
return _thisObject;
}
/**
* @brief Gets the return value reference. Used for setting return value for a function.
* @return The return value reference.
*/
inline const Value &rval() const {
return _retVal;
}
inline Value &rval() {
return _retVal;
}
// Private API used in wrapper
/**
* @brief
* @param[in]
* @return
*/
~State() {
// Inline to speed up high-frequency calls without significant impact on code size
SAFE_DEC_REF(_thisObject);
}
explicit State(Object *thisObject) : _thisObject(thisObject) {
if (_thisObject != nullptr) {
_thisObject->incRef();
}
}
State(Object *thisObject, const ValueArray &args) : _thisObject(thisObject),
_args(&args) {
if (_thisObject != nullptr) {
_thisObject->incRef();
}
}
// Disable copy/move constructor, copy/move assigment
State(const State &) = delete;
State(State &&) noexcept = delete;
State &operator=(const State &) = delete;
State &operator=(State &&) noexcept = delete;
private:
Object *_thisObject{nullptr}; // weak ref
const ValueArray *_args{nullptr}; // weak ref
Value _retVal; // weak ref
};
} // namespace se

View File

@@ -0,0 +1,576 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "Value.h"
#include <cctype>
#include <cmath>
#include <cstdint>
#include <sstream>
#include <type_traits>
#include "Object.h"
#include <cassert>
namespace se {
#ifndef LIKELY
#ifdef __GNUC__
#define LIKELY(expr) __builtin_expect(!!(expr), 1)
#else
#define LIKELY(expr) expr
#endif
#endif
template <typename T>
inline
typename std::enable_if<std::is_unsigned<T>::value, T>::type
fromDoubleToIntegral(double d) {
return static_cast<T>(static_cast<int64_t>(d));
}
template <typename T>
inline
typename std::enable_if<std::is_signed<T>::value, T>::type
fromDoubleToIntegral(double d) {
return static_cast<T>(d);
}
#define CONVERT_TO_TYPE(type) fromDoubleToIntegral<type>(toDouble())
ValueArray EmptyValueArray; // NOLINT(readability-identifier-naming)
Value Value::Null = Value(Type::Null); //NOLINT(readability-identifier-naming)
Value Value::Undefined = Value(Type::Undefined); //NOLINT(readability-identifier-naming)
Value::Value()
: _type(Type::Undefined),
_autoRootUnroot(false) {
memset(&_u, 0, sizeof(_u));
}
Value::Value(Type type)
: _type(Type::Undefined),
_autoRootUnroot(false) {
memset(&_u, 0, sizeof(_u));
reset(type);
}
Value::Value(const Value &v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
*this = v;
}
Value::Value(Value &&v) noexcept
: _type(Type::Undefined),
_autoRootUnroot(false) {
*this = std::move(v);
}
Value::Value(bool v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setBoolean(v);
}
Value::Value(int8_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setInt8(v);
}
Value::Value(uint8_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setUint8(v);
}
Value::Value(int32_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setInt32(v);
}
Value::Value(uint32_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setUint32(v);
}
Value::Value(int16_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setInt16(v);
}
Value::Value(uint16_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setUint16(v);
}
Value::Value(int64_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setInt64(v);
}
Value::Value(uint64_t v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setUint64(v);
}
Value::Value(float v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setFloat(v);
}
Value::Value(double v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setDouble(v);
}
Value::Value(const char *v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setString(v);
}
Value::Value(const ccstd::string &v)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setString(v);
}
Value::Value(Object *o, bool autoRootUnroot /* = false*/)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setObject(o, autoRootUnroot);
}
Value::Value(const HandleObject &o, bool autoRootUnroot /* = false*/)
: _type(Type::Undefined),
_autoRootUnroot(false) {
setObject(o, autoRootUnroot);
}
Value::~Value() {
reset(Type::Undefined);
}
Value &Value::operator=(const Value &v) {
if (this != &v) {
reset(v.getType());
switch (_type) {
case Type::Null:
case Type::Undefined: {
memset(&_u, 0, sizeof(_u));
break;
}
case Type::Number:
_u._number = v._u._number;
break;
case Type::BigInt:
_u._bigint = v._u._bigint;
break;
case Type::String:
*_u._string = *v._u._string;
break;
case Type::Boolean:
_u._boolean = v._u._boolean;
break;
case Type::Object: {
setObject(v._u._object, v._autoRootUnroot);
} break;
default:
break;
}
}
return *this;
}
Value &Value::operator=(Value &&v) noexcept {
if (this != &v) {
reset(v.getType());
switch (_type) {
case Type::Null:
case Type::Undefined: {
memset(&_u, 0, sizeof(_u));
break;
}
case Type::Number:
_u._number = v._u._number;
break;
case Type::BigInt:
_u._bigint = v._u._bigint;
break;
case Type::String:
*_u._string = std::move(*v._u._string);
break;
case Type::Boolean:
_u._boolean = v._u._boolean;
break;
case Type::Object: {
if (_u._object != nullptr) // When old value is also an Object, reset will take no effect, therefore, _u._object may not be nullptr.
{
if (_autoRootUnroot) {
_u._object->unroot();
}
_u._object->decRef();
}
_u._object = v._u._object;
_autoRootUnroot = v._autoRootUnroot;
v._u._object = nullptr; // Reset to nullptr here to avoid 'release' operation in v.reset(Type::Undefined) since it's a move operation here.
v._autoRootUnroot = false;
} break;
default:
break;
}
v.reset(Type::Undefined);
}
return *this;
}
// Value& Value::operator=(bool v)
// {
// setBoolean(v);
// return *this;
// }
//
// Value& Value::operator=(double v)
// {
// setNumber(v);
// return *this;
// }
//
// Value& Value::operator=(const ccstd::string& v)
// {
// setString(v);
// return *this;
// }
//
// Value& Value::operator=(Object* o)
// {
// setObject(o);
// return *this;
// }
//
// Value& Value::operator=(const HandleObject& o)
// {
// setObject(o);
// return *this;
// }
void Value::setUndefined() {
reset(Type::Undefined);
}
void Value::setNull() {
reset(Type::Null);
}
void Value::setBoolean(bool v) {
reset(Type::Boolean);
_u._boolean = v;
}
void Value::setInt8(int8_t v) {
reset(Type::Number);
_u._number = static_cast<double>(v);
}
void Value::setUint8(uint8_t v) {
reset(Type::Number);
_u._number = static_cast<double>(v);
}
void Value::setInt32(int32_t v) {
reset(Type::Number);
_u._number = static_cast<double>(v);
}
void Value::setUint32(uint32_t v) {
reset(Type::Number);
_u._number = static_cast<double>(v);
}
void Value::setInt16(int16_t v) {
reset(Type::Number);
_u._number = static_cast<double>(v);
}
void Value::setUint16(uint16_t v) {
reset(Type::Number);
_u._number = static_cast<double>(v);
}
void Value::setInt64(int64_t v) {
reset(Type::BigInt);
_u._bigint = v;
}
void Value::setUint64(uint64_t v) {
reset(Type::BigInt);
_u._bigint = static_cast<int64_t>(v);
}
void Value::setFloat(float v) {
reset(Type::Number);
_u._number = static_cast<double>(v);
}
void Value::setDouble(double v) {
reset(Type::Number);
_u._number = v;
}
void Value::setString(const char *v) {
if (v != nullptr) {
reset(Type::String);
*_u._string = v;
} else {
reset(Type::Null);
}
}
void Value::setString(const ccstd::string &v) {
reset(Type::String);
*_u._string = v;
}
void Value::setString(const std::string_view &v) {
reset(Type::String);
_u._string->assign(v.data(), 0, v.length());
}
void Value::setObject(Object *object, bool autoRootUnroot /* = false*/) {
if (object == nullptr) {
reset(Type::Null);
return;
}
if (_type != Type::Object) {
reset(Type::Object);
}
if (_u._object != object) {
if (object != nullptr) {
object->incRef();
if (autoRootUnroot) {
object->root();
}
}
if (_u._object != nullptr) // When old value is also an Object, reset will take no effect, therefore, _u._object may not be nullptr.
{
if (_autoRootUnroot) {
_u._object->unroot();
}
_u._object->decRef();
}
_u._object = object;
_autoRootUnroot = autoRootUnroot;
} else {
_autoRootUnroot = autoRootUnroot;
if (_autoRootUnroot) {
_u._object->root();
}
}
}
void Value::setObject(const HandleObject &o, bool autoRootUnroot /* = false*/) {
setObject(o.get(), autoRootUnroot);
}
int8_t Value::toInt8() const {
return CONVERT_TO_TYPE(int8_t);
}
uint8_t Value::toUint8() const {
return CONVERT_TO_TYPE(uint8_t);
}
int16_t Value::toInt16() const {
return CONVERT_TO_TYPE(int16_t);
}
uint16_t Value::toUint16() const {
return CONVERT_TO_TYPE(uint16_t);
}
int32_t Value::toInt32() const {
return CONVERT_TO_TYPE(int32_t);
}
uint32_t Value::toUint32() const {
return CONVERT_TO_TYPE(uint32_t);
}
int64_t Value::toInt64() const {
CC_ASSERT(isBigInt() || isNumber());
return _type == Type::BigInt ? _u._bigint : CONVERT_TO_TYPE(int64_t);
}
uint64_t Value::toUint64() const {
CC_ASSERT(isBigInt() || isNumber());
return _type == Type::BigInt ? static_cast<uint64_t>(_u._bigint) : CONVERT_TO_TYPE(uint64_t);
}
float Value::toFloat() const {
return static_cast<float>(toDouble());
}
double Value::toDouble() const {
CC_ASSERT(_type == Type::Number || _type == Type::Boolean || _type == Type::BigInt || _type == Type::String);
if (LIKELY(_type == Type::Number)) {
return _u._number;
}
if (_type == Type::BigInt) {
// CC_LOG_WARNING("convert int64 to double");
return static_cast<double>(_u._bigint);
}
if (_type == Type::String) {
return std::stod(*_u._string);
}
return _u._boolean ? 1.0 : 0.0;
}
bool Value::toBoolean() const {
CC_ASSERT_EQ(_type, Type::Boolean);
return _u._boolean;
}
const ccstd::string &Value::toString() const {
CC_ASSERT_EQ(_type, Type::String);
return *_u._string;
}
ccstd::string Value::toStringForce() const {
std::stringstream ss;
if (_type == Type::String) {
ss << *_u._string;
} else if (_type == Type::Boolean) {
ss << (_u._boolean ? "true" : "false");
} else if (_type == Type::Number) {
char tmp[50] = {0};
snprintf(tmp, sizeof(tmp), "%.17g", _u._number);
ss << tmp;
} else if (_type == Type::BigInt) {
ss << _u._bigint;
} else if (_type == Type::Object) {
ss << toObject()->toString();
} else if (_type == Type::Null) {
ss << "null";
} else if (_type == Type::Undefined) {
ss << "undefined";
} else {
CC_ABORT();
ss << "[[BadValueType]]";
}
return ss.str();
}
Object *Value::toObject() const {
assert(isObject());
return _u._object;
}
void Value::reset(Type type) {
if (_type != type) {
switch (_type) {
case Type::String:
delete _u._string;
break;
case Type::Object: {
if (_u._object != nullptr) {
if (_autoRootUnroot) {
_u._object->unroot();
}
_u._object->decRef();
_u._object = nullptr;
}
_autoRootUnroot = false;
break;
}
default:
break;
}
memset(&_u, 0, sizeof(_u));
switch (type) {
case Type::String:
_u._string = ccnew ccstd::string();
break;
default:
break;
}
_type = type;
}
}
/////////////////// deprecated methods ////////////////////
void Value::setLong(long v) { // NOLINT(google-runtime-int)
setDouble(static_cast<double>(v));
}
void Value::setUIntptr_t(uintptr_t v) {
setDouble(static_cast<double>(v));
}
void Value::setUlong(unsigned long v) { // NOLINT(google-runtime-int)
setDouble(static_cast<double>(v));
}
void Value::setNumber(double v) {
setDouble(v);
}
unsigned int Value::toUint() const {
return CONVERT_TO_TYPE(unsigned int);
}
long Value::toLong() const { // NOLINT(google-runtime-int)
return CONVERT_TO_TYPE(long);
}
unsigned long Value::toUlong() const { // NOLINT(google-runtime-int)
return CONVERT_TO_TYPE(unsigned long);
}
double Value::toNumber() const {
return toDouble();
}
} // namespace se

View File

@@ -0,0 +1,498 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include <string_view>
#include "HandleObject.h"
#include "base/Macros.h"
#include "base/std/container/string.h"
#include "base/std/container/vector.h"
namespace se {
class Object;
/**
* se::Value represents a JavaScript Value.
* It could be undefined, null, number, boolean, string and object which exists in JavaScript.
*/
class Value final {
public:
enum class Type : char {
Undefined = 0, // NOLINT(readability-identifier-naming)
Null, // NOLINT(readability-identifier-naming)
Number, // NOLINT(readability-identifier-naming)
Boolean, // NOLINT(readability-identifier-naming)
String, // NOLINT(readability-identifier-naming)
Object, // NOLINT(readability-identifier-naming)
BigInt, // NOLINT(readability-identifier-naming)
};
static Value Null; // NOLINT(readability-identifier-naming)
static Value Undefined; // NOLINT(readability-identifier-naming)
/**
* @brief The default constructor.
*/
Value();
/**
* @brief The copy constructor.
*/
Value(const Value &v);
/**
* @brief The move constructor.
*/
Value(Value &&v) noexcept;
/**
* @brief The constructor with a boolean argument.
*/
explicit Value(bool v);
/**
* @brief The constructor with a int8_t argument.
*/
explicit Value(int8_t v);
/**
* @brief The constructor with a uint8_t argument.
*/
explicit Value(uint8_t v);
/**
* @brief The constructor with a int16_t argument.
*/
explicit Value(int16_t v);
/**
* @brief The constructor with a uint16_t argument.
*/
explicit Value(uint16_t v);
/**
* @brief The constructor with a int32_t argument.
*/
explicit Value(int32_t v);
/**
* @brief The constructor with a uint32_t argument.
*/
explicit Value(uint32_t v);
/**
* @brief The constructor with a uint64_t argument.
*/
explicit Value(uint64_t v);
/**
* @brief The constructor with a int64_t argument.
*/
explicit Value(int64_t v);
/**
* @brief The constructor with a float argument.
*/
explicit Value(float v);
/**
* @brief The constructor with a double argument.
*/
explicit Value(double v);
/**
* @brief The constructor with an UTF8 null-terminated string argument.
*/
explicit Value(const char *v);
/**
* @brief The constructor with an UTF8 string argument.
*/
explicit Value(const ccstd::string &v);
/**
* @brief The constructor with an Object.
* @param o A se::Object to be set.
* @param[in] autoRootUnroot Whether to root se::Object and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false.
*/
explicit Value(Object *o, bool autoRootUnroot = false);
/**
* @brief The constructor with a HandleObject.
* @param o A se::HandleObject to be set.
* @param[in] autoRootUnroot Whether to root se::HandleObject and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false.
*/
explicit Value(const HandleObject &o, bool autoRootUnroot = false);
/**
* @brief The destructor of se::Value
*/
~Value();
/**
* @brief The copy assignment operator.
*/
Value &operator=(const Value &v);
/**
* @brief The move assignment operator.
*/
Value &operator=(Value &&v) noexcept;
/**
* @brief Sets se::Value to a long value.
* @param[in] v The long value to be set.
*/
CC_DEPRECATED(3.3)
void setLong(long v); // NOLINT(google-runtime-int)
/**
* @brief Sets se::Value to a uintptr_t value.
* @param[in] v The uintptr_t value to be set.
*/
CC_DEPRECATED(3.3)
void setUIntptr_t(uintptr_t v); // NOLINT(readability-identifier-naming)
/**
* @brief Sets se::Value to a unsigned long value.
* @param[in] v The unsigned long value to be set.
*/
CC_DEPRECATED(3.3)
void setUlong(unsigned long v); // NOLINT(google-runtime-int)
/**
* @brief Sets se::Value to a double value.
* @param[in] v The double value to be set.
*/
CC_DEPRECATED(3.3, "Use setDouble instead")
void setNumber(double v);
CC_DEPRECATED(3.3)
unsigned int toUint() const;
/**
* @brief Converts se::Value to long.
* @return long integer.
*/
CC_DEPRECATED(3.3)
long toLong() const; // NOLINT(google-runtime-int)
/**
* @brief Converts se::Value to unsigned long.
* @return unsigned long integer.
*/
CC_DEPRECATED(3.3)
unsigned long toUlong() const; // NOLINT(google-runtime-int)
/**
* @brief Converts se::Value to double number.
* @return double number.
*/
CC_DEPRECATED(3.3, "Use toDouble instead")
double toNumber() const;
/**
* @brief Sets se::Value to undefined.
*/
void setUndefined();
/**
* @brief Sets se::Value to null.
*/
void setNull();
/**
* @brief Sets se::Value to a boolean value.
* @param[in] v The boolean value to be set.
*/
void setBoolean(bool v);
/**
* @brief Sets se::Value to a int8_t value.
* @param[in] v The int8_t value to be set.
*/
void setInt8(int8_t v);
/**
* @brief Sets se::Value to a uint8_t value.
* @param[in] v The uint8_t value to be set.
*/
void setUint8(uint8_t v);
/**
* @brief Sets se::Value to a int16_t value.
* @param[in] v The int16_t value to be set.
*/
void setInt16(int16_t v);
/**
* @brief Sets se::Value to a uint16_t value.
* @param[in] v The uint16_t value to be set.
*/
void setUint16(uint16_t v);
/**
* @brief Sets se::Value to a int32_t value.
* @param[in] v The int32_t value to be set.
*/
void setInt32(int32_t v);
/**
* @brief Sets se::Value to a uint32_t value.
* @param[in] v The uint32_t value to be set.
*/
void setUint32(uint32_t v);
/**
* @brief Sets se::Value to a unsigned int64_t
* @param[in] v The unsigned int64_t value to be set.
*/
void setUint64(uint64_t v);
/**
* @brief Sets se::Value to a int64_t value.
* @param[in] v The int64_t value to be set.
*/
void setInt64(int64_t v);
/**
* @brief Sets se::Value to a float value.
* @param[in] v The float value to be set.
*/
void setFloat(float v);
/**
* @brief Sets se::Value to a double value.
* @param[in] v The double value to be set.
*/
void setDouble(double v);
/**
* @brief Sets se::Value to an UTF8 null-terminated string value.
* @param[in] v The UTF8 null-terminated string value to be set.
*/
void setString(const char *v);
/**
* @brief Sets se::Value to string value.
* @param[in] v The string value to be set.
*/
void setString(const ccstd::string &v);
/**
* @brief Sets se::Value to string value by string_view.
* @param[in] v The string_view
*/
void setString(const std::string_view &v);
/**
* @brief Sets se::Value to se::Object value.
* @param[in] o The se::Object to be set.
* @param[in] autoRootUnroot Whether to root se::Object and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false.
*/
void setObject(Object *o, bool autoRootUnroot = false);
/**
* @brief Sets se::Value to se::HandleObject value.
* @param[in] o The se::Object to be set.
* @param[in] autoRootUnroot Whether to root se::HandleObject and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false.
*/
void setObject(const HandleObject &o, bool autoRootUnroot = false);
/**
* @brief Converts se::Value to int8_t.
* @return int8_t integer.
*/
int8_t toInt8() const;
/**
* @brief Converts se::Value to uint8_t.
* @return uint8_t integer.
*/
uint8_t toUint8() const;
/**
* @brief Converts se::Value to int16_t.
* @return int16_t integer.
*/
int16_t toInt16() const;
/**
* @brief Converts se::Value to uint16_t.
* @return uint16_t integer.
*/
uint16_t toUint16() const;
/**
* @brief Converts se::Value to int32_t.
* @return int32_t integer.
*/
int32_t toInt32() const;
/**
* @brief Converts se::Value to uint32_t.
* @return uint32_t integer.
*/
uint32_t toUint32() const;
/**
* @brief Converts se::Value to int64_t
* @return signed int64
*/
int64_t toInt64() const;
/**
* @brief Converts se::Value to unsigned uint64_t.
* @return unsigned int64.
*/
uint64_t toUint64() const;
/**
* @brief Converts se::Value to float number.
* @return float number.
*/
float toFloat() const;
/**
* @brief Converts se::Value to double number.
* @return double number.
*/
double toDouble() const;
/**
* @brief Converts se::Value to boolean.
* @return boolean.
*/
bool toBoolean() const;
/**
* @brief Gets ccstd::string if se::Value stores a string. It will trigger an assertion if se::Value isn't a string.
* @return A ccstd::string reference.
* @see toStringForce
*/
const ccstd::string &toString() const;
/**
* @brief Converts a se::Value to ccstd::string. Could be invoked even when se::Value isn't a string.
* @return A copied ccstd::string value.
* @see toString
*/
ccstd::string toStringForce() const;
/**
* @brief Gets the se::Object pointer if se::Value stores an object. It will trigger an assertion if se::Value isn't an object.
* @return A se::Object pointer.
*/
Object *toObject() const;
/**
* @brief Gets the type of se::Value.
* @return The type of se::Value.
*/
inline Type getType() const { return _type; }
/**
* @brief Tests whether se::Value stores a number.
* @return true if se::Value stores a number, otherwise false.
*/
inline bool isNumber() const { return _type == Type::Number; }
/**
* @brief Tests whether se::Value stores a Bigint.
* @return true if se::Value stores a uint64_t or a int64_t, otherwise false.
*/
inline bool isBigInt() const { return _type == Type::BigInt; }
/**
* @brief Tests whether se::Value stores a string.
* @return true if se::Value stores a string, otherwise false.
*/
inline bool isString() const { return _type == Type::String; }
/**
* @brief Tests whether se::Value stores an object.
* @return true if se::Value stores an object, otherwise false.
*/
inline bool isObject() const { return _type == Type::Object; }
/**
* @brief Tests whether se::Value stores a boolean.
* @return true if se::Value stores a boolean, otherwise false.
*/
inline bool isBoolean() const { return _type == Type::Boolean; }
/**
* @brief Tests whether se::Value stores an undefined value.
* @return true if se::Value stores an undefined value, otherwise false.
*/
inline bool isUndefined() const { return _type == Type::Undefined; }
/**
* @brief Tests whether se::Value stores a null value.
* @return true if se::Value stores a null value, otherwise false.
*/
inline bool isNull() const { return _type == Type::Null; }
/**
* @brief Tests whether se::Value stores a null or an undefined value.
* @return true if se::Value stores a null or an undefined value, otherwise false.
*/
inline bool isNullOrUndefined() const { return (isNull() || isUndefined()); }
size_t toSize() const {
return static_cast<size_t>(toDouble());
}
void setSize(size_t v) {
setDouble(static_cast<double>(v));
}
uintptr_t asPtr() const {
return static_cast<uintptr_t>(toUint64());
}
private:
explicit Value(Type type);
void reset(Type type);
union {
bool _boolean;
double _number;
ccstd::string *_string;
Object *_object;
int64_t _bigint;
} _u;
Type _type;
bool _autoRootUnroot;
};
using ValueArray = ccstd::vector<Value>;
extern ValueArray EmptyValueArray; // NOLINT(readability-identifier-naming)
} // namespace se
using se_object_ptr = se::Object *; // NOLINT(readability-identifier-naming)

View File

@@ -0,0 +1,68 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "ValueArrayPool.h"
#include "base/Macros.h"
#include "base/memory/Memory.h"
#include "config.h"
namespace se {
ValueArrayPool gValueArrayPool;
#define SE_DEFAULT_MAX_DEPTH (5)
ValueArrayPool::ValueArrayPool() {
_pools.resize(SE_DEFAULT_MAX_DEPTH);
for (uint32_t i = 0; i < SE_DEFAULT_MAX_DEPTH; ++i) {
initPool(i);
}
}
ValueArray &ValueArrayPool::get(uint32_t argc, bool &outNeedDelete) {
if (SE_UNLIKELY(_depth >= _pools.size())) {
outNeedDelete = true;
auto *ret = ccnew ValueArray();
ret->resize(argc);
return *ret;
}
outNeedDelete = false;
CC_ASSERT_LE(argc, MAX_ARGS);
auto &ret = _pools[_depth][argc];
CC_ASSERT(ret.size() == argc);
return ret;
}
void ValueArrayPool::initPool(uint32_t index) {
auto &pool = _pools[index];
uint32_t i = 0;
for (auto &arr : pool) {
arr.resize(i);
++i;
}
}
} // namespace se

View File

@@ -0,0 +1,73 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "Value.h"
#include "base/std/container/array.h"
namespace se {
class CallbackDepthGuard final {
public:
CallbackDepthGuard(ValueArray &arr, uint32_t &depth, bool needDelete)
: _arr(arr), _depth(depth), _needDelete(needDelete) {
++_depth;
}
~CallbackDepthGuard() {
--_depth;
for (auto &e : _arr) {
e.setUndefined();
}
if (_needDelete) {
delete &_arr;
}
}
private:
ValueArray &_arr;
uint32_t &_depth;
const bool _needDelete{false};
};
class ValueArrayPool final {
public:
static const uint32_t MAX_ARGS = 20;
ValueArrayPool();
ValueArray &get(uint32_t argc, bool &outNeedDelete);
uint32_t _depth{0};
private:
void initPool(uint32_t index);
ccstd::vector<ccstd::array<ValueArray, MAX_ARGS + 1>> _pools;
};
extern ValueArrayPool gValueArrayPool;
} // namespace se

View File

@@ -0,0 +1,36 @@
/****************************************************************************
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "bindings/jswrapper/config.h"
#include <cstdarg>
#include "base/Log.h"
void selogMessage(cc::LogLevel level, const char *tag, const char *format, ...) {
char logbuf[512] = {0};
va_list argp;
va_start(argp, format);
(void)std::vsnprintf(logbuf, sizeof(logbuf), format, argp);
va_end(argp);
cc::Log::logMessage(cc::LogType::KERNEL, level, "%s %s", tag, logbuf);
}

View File

@@ -0,0 +1,107 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include <cstdio>
#include "base/Log.h"
#define SCRIPT_ENGINE_NONE 0
#define SCRIPT_ENGINE_SM 1
#define SCRIPT_ENGINE_V8 2
#define SCRIPT_ENGINE_NAPI 5
#ifndef SCRIPT_ENGINE_TYPE
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#define SCRIPT_ENGINE_TYPE SCRIPT_ENGINE_NAPI
#else
#define SCRIPT_ENGINE_TYPE SCRIPT_ENGINE_V8
#endif
#endif
#define SE_LOG_TO_JS_ENV 0 // print log to JavaScript environment, for example DevTools
#if !defined(ANDROID_INSTANT) && defined(USE_V8_DEBUGGER) && USE_V8_DEBUGGER > 0
#define SE_ENABLE_INSPECTOR 1
#define SE_DEBUG 2
#define HAVE_INSPECTOR 1
#else
#define SE_ENABLE_INSPECTOR 0
#define SE_DEBUG 0
#define HAVE_INSPECTOR 0
#endif
#if defined(__clang__) || defined(__GNUC__)
#define CC_FORMAT_HINT(si, fi) __attribute__((__format__(__printf__, si, fi)))
#else
#define CC_FORMAT_HINT(si, fi)
#endif
CC_FORMAT_HINT(3, 4)
void selogMessage(cc::LogLevel level, const char *tag, const char *format, ...);
#if CC_DEBUG
#define SE_LOGD(...) selogMessage(cc::LogLevel::LEVEL_DEBUG, "D/", ##__VA_ARGS__)
#else
#define SE_LOGD(...)
#endif
#define SE_LOGE(...) selogMessage(cc::LogLevel::ERR, "E/", ##__VA_ARGS__)
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#define __POSIX__ //NOLINT
#endif
#if defined(_WIN32) && defined(_WINDOWS)
#include <BaseTsd.h>
#if !defined(__SSIZE_T)
typedef SSIZE_T ssize_t;
#define __SSIZE_T
#define _SSIZE_T_DEFINED // libuv also defines ssize_t, use the one defined here.
#endif // __SSIZE_T
#endif // #if defined(_WIN32) && defined(_WINDOWS)
/** @def SE_DEPRECATED_ATTRIBUTE
* Only certain compilers support __attribute__((deprecated)).
*/
#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))
#define SE_DEPRECATED_ATTRIBUTE __attribute__((deprecated))
#elif _MSC_VER >= 1400 //vs 2005 or higher
#define SE_DEPRECATED_ATTRIBUTE __declspec(deprecated)
#else
#define SE_DEPRECATED_ATTRIBUTE
#endif // SE_DEPRECATED_ATTRIBUTE
#if defined(__GNUC__) && __GNUC__ >= 4
#define SE_LIKELY(x) (__builtin_expect((x), 1))
#define SE_UNLIKELY(x) (__builtin_expect((x), 0))
#else
#define SE_LIKELY(x) (x)
#define SE_UNLIKELY(x) (x)
#endif

View File

@@ -0,0 +1,248 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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 "Class.h"
#include <string>
#include "CommonHeader.h"
#include "ScriptEngine.h"
#include "Utils.h"
namespace se {
napi_value *Class::_exports = nullptr;
std::vector<Class *> __allClasses;
Class::Class() {
__allClasses.push_back(this);
};
Class::~Class() {
}
/* static */
Class *Class::create(const std::string &clsName, se::Object *parent, Object *parentProto, napi_callback ctor) {
Class *cls = new Class();
if (cls != nullptr && !cls->init(clsName, parent, parentProto, ctor)) {
delete cls;
cls = nullptr;
}
return cls;
}
Class* Class::create(const std::initializer_list<const char *> &classPath, se::Object *parent, Object *parentProto, napi_callback ctor) {
se::AutoHandleScope scope;
se::Object *currentParent = parent;
se::Value tmp;
for (auto i = 0; i < classPath.size() - 1; i++) {
bool ok = currentParent->getProperty(*(classPath.begin() + i), &tmp);
CC_ASSERT(ok); // class or namespace in path is not defined
currentParent = tmp.toObject();
}
return create(*(classPath.end() - 1), currentParent, parentProto, ctor);
}
bool Class::init(const std::string &clsName, Object *parent, Object *parentProto, napi_callback ctor) {
_name = clsName;
_parent = parent;
if (_parent != nullptr)
_parent->incRef();
_parentProto = parentProto;
if (_parentProto != nullptr)
_parentProto->incRef();
if (ctor) {
_ctorFunc = ctor;
}
return true;
}
napi_value Class::_defaultCtor(napi_env env, napi_callback_info info) {
LOGE("check default ctor called");
return nullptr;
}
void Class::defineProperty(const char* name, napi_callback g, napi_callback s) {
_properties.push_back({name, nullptr, nullptr, g, s, 0, napi_default_jsproperty, 0});
}
void Class::defineProperty(const std::initializer_list<const char *> &names, napi_callback g, napi_callback s) {
for (const auto *name : names) {
defineProperty(name, g, s);
}
}
void Class::defineStaticProperty(const char* name, napi_callback g, napi_callback s) {
if(g != nullptr && s != nullptr)
_properties.push_back({name, nullptr, nullptr, g, s, 0, napi_static, 0});
}
bool Class::defineStaticProperty(const char *name, const Value &v, PropertyAttribute attribute /* = PropertyAttribute::NONE */) {
// TODO(qgh): Assigning get and set to nullptr in openharmony will cause a crash
//napi_value value;
//internal::seToJsValue(v, &value);
//_properties.push_back({name, nullptr, nullptr, nullptr, nullptr, value, napi_static, 0});
return true;
}
void Class::defineFunction(const char* name, napi_callback func) {
// When Napi defines a function, it needs to add the enum attribute, otherwise JS cannot traverse the function
_properties.push_back({name, nullptr, func, nullptr, nullptr, nullptr, napi_default_jsproperty, nullptr});
}
void Class::defineStaticFunction(const char* name, napi_callback func) {
_properties.push_back({name, nullptr, func, nullptr, nullptr, 0, napi_static, 0});
}
void Class::defineFinalizeFunction(napi_finalize func) {
assert(func != nullptr);
_finalizeFunc = func;
}
napi_finalize Class::_getFinalizeFunction() const {
return _finalizeFunc;
}
void Class::install() {
napi_value cons;
napi_status status;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_define_class(ScriptEngine::getEnv(), _name.c_str(), -1, _ctorFunc, nullptr, _properties.size(), _properties.data(), &cons));
if (_parentProto) {
inherit(ScriptEngine::getEnv(), cons, _parentProto->_getJSObject());
}
NODE_API_CALL(status, ScriptEngine::getEnv(),
napi_create_reference(ScriptEngine::getEnv(), cons, 1, &_constructor));
NODE_API_CALL(status, ScriptEngine::getEnv(),
napi_set_named_property(ScriptEngine::getEnv(), _parent->_getJSObject(), _name.c_str(), cons));
napi_value proto;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_named_property(ScriptEngine::getEnv(), cons, "prototype", &proto));
if (status == napi_ok) {
_proto = Object::_createJSObject(ScriptEngine::getEnv(), proto, nullptr);
_proto->root();
}
}
napi_status Class::inherit(napi_env env, napi_value subclass, napi_value superProto) {
napi_value global, objectClass, setProto;
napi_value argv[2];
napi_value callbackResult = nullptr;
napi_get_global(env, &global);
napi_status status = napi_get_named_property(env, global, "Object", &objectClass);
if (status != napi_ok) {
LOGE("ace zbclog napi_get_named_property Object %{public}d", status);
return napi_ok;
}
status = napi_get_named_property(env, objectClass, "setPrototypeOf", &setProto);
if (status != napi_ok) {
LOGE("ace zbclog napi_get_named_property setPrototypeOf %{public}d", status);
return napi_ok;
}
status = napi_get_named_property(env, subclass, "prototype", &argv[0]);
if (status != napi_ok) {
LOGE("ace zbclog napi_get_named_property prototype arg0 %{public}d", status);
return napi_ok;
}
argv[1] = superProto;
status = napi_call_function(env, objectClass, setProto, 2, argv, &callbackResult);
if (status != napi_ok) {
LOGE("ace zbclog napi_call_function setProto 1 %{public}d", status);
return napi_ok;
}
return napi_ok;
}
napi_value Class::_createJSObjectWithClass(Class *cls) {
napi_value obj = nullptr;
napi_status status;
assert(cls);
napi_value clsCtor = cls->_getCtorFunc();
if (!clsCtor) {
LOGE("get ctor func err");
return nullptr;
}
se::ScriptEngine::getInstance()->_setNeedCallConstructor(false);
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_new_instance( ScriptEngine::getEnv(), clsCtor, 0, nullptr, &obj));
se::ScriptEngine::getInstance()->_setNeedCallConstructor(true);
return obj;
}
Object *Class::getProto() const {
//not impl
return _proto;
}
napi_ref Class::_getCtorRef() const {
return _constructor;
}
napi_value Class::_getCtorFunc() const {
assert(_constructor);
napi_value result = nullptr;
napi_status status;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_reference_value(ScriptEngine::getEnv(), _constructor, &result));
return result;
}
void Class::_setCtor(Object *obj) {
assert(!_ctor.has_value());
_ctor = obj;
if (obj != nullptr) {
obj->root();
obj->incRef();
}
}
void Class::destroy() {
SAFE_DEC_REF(_parent);
SAFE_DEC_REF(_proto);
SAFE_DEC_REF(_parentProto);
if (_ctor.has_value()) {
if (_ctor.value() != nullptr) {
_ctor.value()->unroot();
_ctor.value()->decRef();
}
_ctor.reset();
}
}
void Class::cleanup() {
for (auto cls : __allClasses) {
cls->destroy();
}
se::ScriptEngine::getInstance()->addAfterCleanupHook([]() {
for (auto cls : __allClasses) {
delete cls;
}
__allClasses.clear();
});
}
}; // namespace se

View File

@@ -0,0 +1,83 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include <string>
#include "CommonHeader.h"
#include "Object.h"
#include "../Define.h"
#include "base/std/optional.h"
namespace se {
class Class {
public:
static Class *create(const std::string &clsName, se::Object *parent, Object *parentProto, napi_callback ctor = nullptr);
static Class *create(const std::initializer_list<const char *> &classPath, se::Object *parent, Object *parentProto, napi_callback ctor = nullptr);
void defineFunction(const char* name, napi_callback func);
void defineProperty(const char* name, napi_callback g, napi_callback s);
void defineProperty(const std::initializer_list<const char *> &names, napi_callback g, napi_callback s);
void defineStaticFunction(const char* name, napi_callback func);
void defineStaticProperty(const char* name, napi_callback g, napi_callback s);
bool defineStaticProperty(const char *name, const Value &value, PropertyAttribute attribute = PropertyAttribute::NONE);
static napi_value _createJSObjectWithClass(Class *cls);
void defineFinalizeFunction(napi_finalize func);
napi_finalize _getFinalizeFunction() const;
Object * getProto() const;
void install();
napi_status inherit(napi_env env, napi_value subclass, napi_value superclass);
napi_ref _getCtorRef() const;
napi_value _getCtorFunc() const;
const char * getName() const { return _name.c_str(); }
static void setExports(napi_value *expPtr) { _exports = expPtr; }
static void cleanup();
// Private API used in wrapper
void _setCtor(Object *obj); // NOLINT(readability-identifier-naming)
inline const ccstd::optional<Object *> &_getCtor() const { return _ctor; } // NOLINT(readability-identifier-naming)
private:
Class();
~Class();
bool init(const std::string &clsName, Object *parent, Object *parentProto, napi_callback ctor = nullptr);
void destroy();
static napi_value _defaultCtor(napi_env env, napi_callback_info info);
private:
ccstd::optional<Object *> _ctor;
static napi_value * _exports;
std::string _name;
Object * _parent = nullptr;
Object * _proto = nullptr;
Object * _parentProto = nullptr;
napi_callback _ctorFunc = Class::_defaultCtor;
napi_ref _constructor = nullptr;
std::vector<napi_property_descriptor> _properties;
napi_finalize _finalizeFunc = nullptr;
};
}; // namespace se

View File

@@ -0,0 +1,74 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include <napi/native_api.h>
#include "native_common.h"
// Empty value so that macros here are able to return NULL or void
#define NODE_API_RETVAL_NOTHING // Intentionally blank #define
// Returns NULL on failed assertion.
// This is meant to be used inside napi_callback methods.
#define NODE_API_ASSERT(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, NULL)
// Returns empty on failed assertion.
// This is meant to be used inside functions with void return type.
#define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, NODE_API_RETVAL_NOTHING)
#define NODE_API_CALL_BASE(env, the_call, ret_val) \
do { \
if ((the_call) != napi_ok) { \
assert(false); \
} \
} while (0)
// Returns nullptr if the_call doesn't return napi_ok.
#define NODE_API_CALL(status, env, the_call) \
status = the_call; \
if (status != napi_ok) \
LOGI("error:%d", status); \
NODE_API_CALL_BASE(env, status, nullptr)
// Returns empty if the_call doesn't return napi_ok.
#define NODE_API_CALL_RETURN_VOID(env, the_call) \
NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING)
#define DECLARE_NODE_API_PROPERTY(name, func) \
{ (name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, nullptr }
#define DECLARE_NODE_API_GETTER(name, func) \
{ (name), nullptr, nullptr, (func), nullptr, nullptr, napi_default, nullptr }
void add_returned_status(napi_env env,
const char* key,
napi_value object,
char* expected_message,
napi_status expected_status,
napi_status actual_status);
void add_last_status(napi_env env, const char* key, napi_value return_value);

View File

@@ -0,0 +1,145 @@
/****************************************************************************
Copyright (c) 2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "HelperMacros.h"
#include "../State.h"
#include "../ValueArrayPool.h"
#include "Class.h"
#include "Object.h"
#include "ScriptEngine.h"
#include "Utils.h"
SE_HOT napi_value jsbFunctionWrapper(napi_env env, napi_callback_info info, se_function_ptr func, const char *funcName) {
napi_status status;
bool ret = false;
napi_value _this;
se::ValueArray seArgs;
seArgs.reserve(15);
size_t argc = 15;
napi_value args[15];
NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, NULL));
se::Object *nativeThisObject = nullptr;
status = napi_unwrap(env, _this, reinterpret_cast<void **>(&nativeThisObject));
se::internal::jsToSeArgs(argc, args, &seArgs);
se::State state(nativeThisObject, seArgs);
ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__);
return nullptr;
}
napi_value retVal;
if (se::internal::setReturnValue(state.rval(), retVal))
return retVal;
return nullptr;
}
SE_HOT void jsbFinalizeWrapper(void *thisObject, se_function_ptr func, const char *funcName) {
se::State state(reinterpret_cast<se::Object *>(thisObject));
bool ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__);
}
}
SE_HOT napi_value jsbConstructorWrapper(napi_env env, napi_callback_info info, se_function_ptr func, se_finalize_ptr finalizeCb, se::Class *cls, const char *funcName) {
napi_status status;
bool ret = false;
napi_value _this;
se::ValueArray seArgs;
seArgs.reserve(10);
size_t argc = 10;
napi_value args[10];
NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, NULL));
if (!se::ScriptEngine::getInstance()->_needCallConstructor()) {
return _this;
}
se::internal::jsToSeArgs(argc, args, &seArgs);
se::Object *thisObject = se::Object::_createJSObject(env, _this, cls);
thisObject->_setFinalizeCallback(finalizeCb);
se::State state(thisObject, seArgs);
ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__);
}
se::Value property;
bool foundCtor = false;
if (!cls->_getCtor().has_value()) {
foundCtor = thisObject->getProperty("_ctor", &property, true);
if (foundCtor) {
cls->_setCtor(property.toObject());
} else {
cls->_setCtor(nullptr);
}
} else {
auto *ctorObj = cls->_getCtor().value();
if (ctorObj != nullptr) {
property.setObject(ctorObj);
foundCtor = true;
}
}
if (foundCtor) {
property.toObject()->call(seArgs, thisObject);
}
return _this;
}
SE_HOT napi_value jsbGetterWrapper(napi_env env, napi_callback_info info, se_function_ptr func, const char *funcName) {
napi_value _this;
napi_status status;
NODE_API_CALL(status, env,
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));
se::Object *obj;
napi_unwrap(env, _this, reinterpret_cast<void **>(&obj));
se::State state(obj);
bool ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__);
return nullptr;
}
napi_value retVal;
se::internal::setReturnValue(state.rval(), retVal);
return retVal;
}
SE_HOT napi_value jsbSetterWrapper(napi_env env, napi_callback_info info, se_function_ptr func, const char *funcName) {
napi_status status;
size_t argc = 1;
napi_value args[1];
napi_value _this;
se::Value data;
NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr));
se::internal::jsToSeValue(args[0], &data);
se::ValueArray args2;
args2.reserve(10);
args2.push_back(std::move(data));
se::Object *nativeThisObject;
napi_unwrap(env, _this, reinterpret_cast<void **>(&nativeThisObject));
se::State state(nativeThisObject, args2);
bool ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__);
}
return nullptr;
}

View File

@@ -0,0 +1,153 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include "CommonHeader.h"
#if !defined(_WIN)
#include <hilog/log.h>
#ifndef LOGI
#define LOGI(...) ((void) OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__))
#define LOGW(...) ((void) OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__))
#define LOGE(...) ((void) OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__))
#define LOGD(...) ((void) OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__))
#endif
#else
#define LOGI
#define LOGW
#define LOGE
#endif
namespace se {
class Class;
class Object;
class State;
} // namespace se
using se_function_ptr = bool (*)(se::State &state);
using se_finalize_ptr = void (*)(napi_env env, void *nativeObject, void *hint);
napi_value jsbFunctionWrapper(napi_env, napi_callback_info,
se_function_ptr,
const char *);
void jsbFinalizeWrapper(void *thisObject,
se_function_ptr,
const char *);
napi_value jsbConstructorWrapper(napi_env, napi_callback_info,
se_function_ptr,
se_finalize_ptr finalizeCb,
se::Class *,
const char *);
napi_value jsbGetterWrapper(napi_env, napi_callback_info,
se_function_ptr,
const char *);
napi_value jsbSetterWrapper(napi_env, napi_callback_info,
se_function_ptr,
const char *);
#ifdef __GNUC__
#define SE_UNUSED __attribute__((unused))
#define SE_HOT __attribute__((hot))
#else
#define SE_UNUSED
#define SE_HOT
#endif
template <typename T, typename STATE>
constexpr inline T *SE_THIS_OBJECT(STATE &s) { // NOLINT(readability-identifier-naming)
return reinterpret_cast<T *>(s.nativeThisObject());
}
#define SAFE_INC_REF(obj) \
if (obj != nullptr) obj->incRef()
#define SAFE_DEC_REF(obj) \
if ((obj) != nullptr) { \
(obj)->decRef(); \
(obj) = nullptr; \
}
#define _SE(name) name##Registry // NOLINT(readability-identifier-naming, bugprone-reserved-identifier)
#define SE_QUOTEME_(x) #x // NOLINT(readability-identifier-naming)
#define SE_QUOTEME(x) SE_QUOTEME_(x)
#define SE_REPORT_ERROR(fmt, ...) SE_LOGE("[ERROR] (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
#define SE_BIND_PROP_GET_IMPL(funcName, postFix) \
napi_value funcName##postFix##Registry(napi_env env, napi_callback_info info) { \
return jsbGetterWrapper(env, info, funcName, #funcName); \
}
#define SE_BIND_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, )
#define SE_BIND_FUNC_AS_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, _asGetter)
#define SE_BIND_PROP_SET_IMPL(funcName, postFix) \
napi_value funcName##postFix##Registry(napi_env env, napi_callback_info info) { \
return jsbSetterWrapper(env, info, funcName, #funcName); \
}
#define SE_BIND_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, )
#define SE_BIND_FUNC_AS_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, _asSetter)
#define SE_DECLARE_FUNC(funcName) \
napi_value funcName##Registry(napi_env env, napi_callback_info info)
#define SE_BIND_FUNC(funcName) \
napi_value funcName##Registry( \
napi_env env, napi_callback_info info) { \
return jsbFunctionWrapper(env, info, funcName, #funcName); \
}
#define SE_BIND_FUNC_FAST(funcName) \
napi_value funcName##Registry(napi_env env, napi_callback_info info) { \
napi_status status; \
napi_value _this; \
size_t argc = 10; \
napi_value args[10]; \
se::Object *nativeThisObject = nullptr; \
NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, NULL)); \
status = napi_unwrap(env, _this, reinterpret_cast<void **>(&nativeThisObject)); \
auto *nativeObject = nativeThisObject != nullptr ? nativeThisObject->getPrivateData() : nullptr; \
funcName(nativeObject); \
return nullptr; \
}
#define SE_BIND_CTOR(funcName, cls, finalizeCb) \
napi_value funcName##Registry( \
napi_env env, napi_callback_info info) { \
return jsbConstructorWrapper(env, info, funcName, _SE(finalizeCb), cls, #funcName); \
}
#define SE_BIND_SUB_CLS_CTOR SE_BIND_CTOR
#define SE_DECLARE_FINALIZE_FUNC(funcName) \
void funcName##Registry( \
napi_env env, void *nativeObject, void * /*finalize_hint*/);
#define SE_BIND_FINALIZE_FUNC(funcName) \
void funcName##Registry( \
napi_env env, void *nativeObject, void *hint /*finalize_hint*/) { \
if (nativeObject == nullptr) { \
return; \
} \
jsbFinalizeWrapper(nativeObject, funcName, #funcName); \
}

View File

@@ -0,0 +1,744 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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 "Object.h"
#include <memory>
#include <unordered_map>
#include "../MappingUtils.h"
#include "Class.h"
#include "ScriptEngine.h"
#include "Utils.h"
#define MAX_STRING_LEN 512
namespace se {
std::unique_ptr<std::unordered_map<Object*, void*>> __objectMap; // Currently, the value `void*` is always nullptr
Object::Object() {}
Object::~Object() {
if (__objectMap) {
__objectMap->erase(this);
}
}
Object* Object::createObjectWithClass(Class* cls) {
napi_value jsobj = Class::_createJSObjectWithClass(cls);
Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), jsobj, cls);
return obj;
}
bool Object::setProperty(const char* name, const Value& data) {
napi_status status;
napi_value jsVal;
internal::seToJsValue(data, &jsVal);
NODE_API_CALL(status, _env, napi_set_named_property(_env, _objRef.getValue(_env), name, jsVal));
return status == napi_ok;
}
bool Object::getProperty(const char* name, Value* d) {
napi_status status;
napi_value jsVal;
Value data;
NODE_API_CALL(status, _env, napi_get_named_property(_env, _objRef.getValue(_env), name, &jsVal));
if (status == napi_ok) {
internal::jsToSeValue(jsVal, &data);
*d = data;
if (data.isUndefined()) {
return false;
}
return true;
}
return false;
}
bool Object::isArray() const {
napi_status status;
bool ret = false;
NODE_API_CALL(status, _env, napi_is_array(_env, _objRef.getValue(_env), &ret));
return ret;
}
bool Object::getArrayLength(uint32_t* length) const {
napi_status status;
uint32_t len = 0;
NODE_API_CALL(status, _env, napi_get_array_length(_env, _objRef.getValue(_env), &len));
if (length) {
*length = len;
}
return true;
}
bool Object::getArrayElement(uint32_t index, Value* data) const {
napi_status status;
napi_value val;
NODE_API_CALL(status, _env, napi_get_element(_env, _objRef.getValue(_env), index, &val));
internal::jsToSeValue(val, data);
return true;
}
bool Object::setArrayElement(uint32_t index, const Value& data) {
napi_status status;
napi_value val;
internal::seToJsValue(data, &val);
NODE_API_CALL(status, _env, napi_set_element(_env, _objRef.getValue(_env), index, val));
return true;
}
bool Object::isTypedArray() const {
napi_status status;
bool ret = false;
NODE_API_CALL(status, _env, napi_is_typedarray(_env, _objRef.getValue(_env), &ret));
return ret;
}
bool Object::isProxy() const {
//return const_cast<Object *>(this)->_obj.handle(__isolate)->IsProxy();
// todo:
return false;
}
Object *Object::createProxyTarget(se::Object *proxy) {
// SE_ASSERT(proxy->isProxy(), "parameter is not a Proxy object");
// v8::Local<v8::Object> jsobj = proxy->getProxyTarget().As<v8::Object>();
// Object *obj = Object::_createJSObject(nullptr, jsobj);
// return obj;
return nullptr;
}
Object::TypedArrayType Object::getTypedArrayType() const {
napi_status status;
napi_typedarray_type type;
napi_value inputBuffer;
size_t byteOffset;
size_t length;
NODE_API_CALL(status, _env, napi_get_typedarray_info(_env, _objRef.getValue(_env), &type, &length, NULL, &inputBuffer, &byteOffset));
TypedArrayType ret = TypedArrayType::NONE;
switch (type) {
case napi_int8_array:
ret = TypedArrayType::INT8;
break;
case napi_uint8_array:
ret = TypedArrayType::UINT8;
break;
case napi_uint8_clamped_array:
ret = TypedArrayType::UINT8_CLAMPED;
break;
case napi_int16_array:
ret = TypedArrayType::INT16;
break;
case napi_uint16_array:
ret = TypedArrayType::UINT16;
break;
case napi_int32_array:
ret = TypedArrayType::INT32;
break;
case napi_uint32_array:
ret = TypedArrayType::UINT32;
break;
case napi_float32_array:
ret = TypedArrayType::FLOAT32;
break;
case napi_float64_array:
ret = TypedArrayType::FLOAT64;
break;
default:
break;
}
return ret;
}
bool Object::getTypedArrayData(uint8_t** ptr, size_t* length) const {
napi_status status;
napi_typedarray_type type;
napi_value inputBuffer;
size_t byteOffset;
size_t byteLength;
void* data = nullptr;
NODE_API_CALL(status, _env, napi_get_typedarray_info(_env, _objRef.getValue(_env), &type, &byteLength, &data, &inputBuffer, &byteOffset));
*ptr = (uint8_t*)(data);
if (length) {
*length = byteLength;
}
return true;
}
bool Object::isArrayBuffer() const {
bool ret = false;
napi_status status;
NODE_API_CALL(status, _env, napi_is_arraybuffer(_env, _objRef.getValue(_env), &ret));
return ret;
}
bool Object::getArrayBufferData(uint8_t** ptr, size_t* length) const {
napi_status status;
size_t len = 0;
NODE_API_CALL(status, _env, napi_get_arraybuffer_info(_env, _objRef.getValue(_env), reinterpret_cast<void**>(ptr), &len));
if (length) {
*length = len;
}
return true;
}
Object* Object::createTypedArray(Object::TypedArrayType type, const void* data, size_t byteLength) {
napi_status status;
if (type == TypedArrayType::NONE) {
SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!");
return nullptr;
}
if (type == TypedArrayType::UINT8_CLAMPED) {
SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!");
return nullptr;
}
napi_typedarray_type napiType;
napi_value outputBuffer;
void* outputPtr = nullptr;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_arraybuffer(ScriptEngine::getEnv(), byteLength, &outputPtr, &outputBuffer));
if (outputPtr && data && byteLength > 0) {
memcpy(outputPtr, data, byteLength);
}
size_t sizeOfEle = 0;
switch (type) {
case TypedArrayType::INT8:
napiType = napi_int8_array;
sizeOfEle = 1;
break;
case TypedArrayType::UINT8:
napiType = napi_uint8_array;
sizeOfEle = 1;
break;
case TypedArrayType::INT16:
napiType = napi_int16_array;
sizeOfEle = 2;
break;
case TypedArrayType::UINT16:
napiType = napi_uint16_array;
sizeOfEle = 2;
break;
case TypedArrayType::INT32:
napiType = napi_int32_array;
sizeOfEle = 4;
break;
case TypedArrayType::UINT32:
napiType = napi_uint32_array;
sizeOfEle = 4;
break;
case TypedArrayType::FLOAT32:
napiType = napi_float32_array;
sizeOfEle = 4;
break;
case TypedArrayType::FLOAT64:
napiType = napi_float64_array;
sizeOfEle = 8;
break;
default:
assert(false); // Should never go here.
break;
}
size_t eleCounts = byteLength / sizeOfEle;
napi_value outputArray;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_typedarray(ScriptEngine::getEnv(), napiType, eleCounts, outputBuffer, 0, &outputArray));
Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), outputArray, nullptr);
return obj;
}
Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj) {
return Object::createTypedArrayWithBuffer(type, obj, 0);
}
Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset) {
size_t byteLength{0};
uint8_t *skip{nullptr};
if (obj->getArrayBufferData(&skip, &byteLength)) {
return Object::createTypedArrayWithBuffer(type, obj, offset, byteLength - offset);
}
CC_ASSERT(false);
return nullptr;
}
Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength) {
if (type == TypedArrayType::NONE) {
SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!");
return nullptr;
}
if (type == TypedArrayType::UINT8_CLAMPED) {
SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!");
return nullptr;
}
CC_ASSERT(obj->isArrayBuffer());
napi_status status;
napi_value outputBuffer = obj->_getJSObject();
napi_typedarray_type napiType;
size_t sizeOfEle = 0;
switch (type) {
case TypedArrayType::INT8:
napiType = napi_int8_array;
sizeOfEle = 1;
break;
case TypedArrayType::UINT8:
napiType = napi_uint8_array;
sizeOfEle = 1;
break;
case TypedArrayType::INT16:
napiType = napi_int16_array;
sizeOfEle = 2;
break;
case TypedArrayType::UINT16:
napiType = napi_uint16_array;
sizeOfEle = 2;
break;
case TypedArrayType::INT32:
napiType = napi_int32_array;
sizeOfEle = 4;
break;
case TypedArrayType::UINT32:
napiType = napi_uint32_array;
sizeOfEle = 4;
break;
case TypedArrayType::FLOAT32:
napiType = napi_float32_array;
sizeOfEle = 4;
break;
case TypedArrayType::FLOAT64:
napiType = napi_float64_array;
sizeOfEle = 8;
break;
default:
assert(false); // Should never go here.
break;
}
size_t eleCounts = byteLength / sizeOfEle;
napi_value outputArray;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_typedarray(ScriptEngine::getEnv(), napiType, eleCounts, outputBuffer, offset, &outputArray));
return Object::_createJSObject(ScriptEngine::getEnv(), outputArray, nullptr);
}
Object* Object::createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData) {
napi_status status;
napi_value result;
if (freeFunc) {
struct ExternalArrayBufferCallbackParams* param = new (struct ExternalArrayBufferCallbackParams);
param->func = freeFunc;
param->contents = contents;
param->byteLength = byteLength;
param->userData = freeUserData;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_external_arraybuffer(
ScriptEngine::getEnv(), contents, byteLength, [](napi_env env, void* finalize_data, void* finalize_hint) {
if (finalize_hint) {
struct ExternalArrayBufferCallbackParams* param = reinterpret_cast<struct ExternalArrayBufferCallbackParams *>(finalize_hint);
param->func(param->contents, param->byteLength, param->userData);
delete param;
}
},
reinterpret_cast<void*>(param), &result));
} else {
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_external_arraybuffer(
ScriptEngine::getEnv(), contents, byteLength, nullptr,
freeUserData, &result));
}
Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), result, nullptr);
return obj;
}
bool Object::isFunction() const {
napi_valuetype valuetype0;
napi_status status;
NODE_API_CALL(status, _env, napi_typeof(_env, _objRef.getValue(_env), &valuetype0));
return (valuetype0 == napi_function);
}
bool Object::defineFunction(const char* funcName, napi_callback func) {
napi_value fn;
napi_status status;
NODE_API_CALL(status, _env, napi_create_function(_env, funcName, NAPI_AUTO_LENGTH, func, NULL, &fn));
NODE_API_CALL(status, _env, napi_set_named_property(_env, _objRef.getValue(_env), funcName, fn));
return true;
}
bool Object::defineProperty(const char* name, napi_callback getter, napi_callback setter) {
napi_status status;
napi_property_descriptor properties[] = {{name, nullptr, nullptr, getter, setter, 0, napi_default, 0}};
status = napi_define_properties(_env, _objRef.getValue(_env), sizeof(properties) / sizeof(napi_property_descriptor), properties);
if (status == napi_ok) {
return true;
}
return false;
}
Object* Object::_createJSObject(napi_env env, napi_value js_object, Class* cls) { // NOLINT(readability-identifier-naming)
auto* ret = new Object();
if (!ret->init(env, js_object, cls)) {
delete ret;
ret = nullptr;
}
return ret;
}
Object* Object::createPlainObject() {
napi_value result;
napi_status status;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_object(ScriptEngine::getEnv(), &result));
Object* obj = _createJSObject(ScriptEngine::getEnv(), result, nullptr);
return obj;
}
Object* Object::createArrayObject(size_t length) {
napi_value result;
napi_status status;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_array_with_length(ScriptEngine::getEnv(), length, &result));
Object* obj = _createJSObject(ScriptEngine::getEnv(), result, nullptr);
return obj;
}
Object* Object::createArrayBufferObject(const void* data, size_t byteLength) {
napi_value result;
napi_status status;
void* retData;
Object* obj = nullptr;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_arraybuffer(ScriptEngine::getEnv(), byteLength, &retData, &result));
if (status == napi_ok) {
if (data) {
memcpy(retData, data, byteLength);
}
obj = _createJSObject(ScriptEngine::getEnv(), result, nullptr);
}
return obj;
}
bool Object::getAllKeys(std::vector<std::string>* allKeys) const {
napi_status status;
napi_value names;
NODE_API_CALL(status, _env, napi_get_property_names(_env, _objRef.getValue(_env), &names));
if (status != napi_ok) {
return false;
}
uint32_t name_len = 0;
NODE_API_CALL(status, _env, napi_get_array_length(_env, names, &name_len));
for (uint32_t i = 0; i < name_len; i++) {
napi_value val;
NODE_API_CALL(status, _env, napi_get_element(_env, names, i, &val));
if (status == napi_ok) {
char buffer[MAX_STRING_LEN];
size_t result = 0;
NODE_API_CALL(status, _env, napi_get_value_string_utf8(_env, val, buffer, MAX_STRING_LEN, &result));
if (result > 0) {
allKeys->push_back(buffer);
}
}
}
return true;
}
bool Object::init(napi_env env, napi_value js_object, Class* cls) {
assert(env);
_cls = cls;
_env = env;
_objRef.initWeakref(env, js_object);
if (__objectMap) {
assert(__objectMap->find(this) == __objectMap->end());
__objectMap->emplace(this, nullptr);
}
napi_status status;
return true;
}
bool Object::call(const ValueArray& args, Object* thisObject, Value* rval) {
size_t argc = 0;
std::vector<napi_value> argv;
argv.reserve(10);
argc = args.size();
internal::seToJsArgs(_env, args, &argv);
napi_value return_val;
napi_status status;
assert(isFunction());
napi_value thisObj = thisObject ? thisObject->_getJSObject() : nullptr;
status =
napi_call_function(_env, thisObj, _getJSObject(), argc, argv.data(), &return_val);
if (rval) {
internal::jsToSeValue(return_val, rval);
}
return true;
}
void Object::_setFinalizeCallback(napi_finalize finalizeCb) {
assert(finalizeCb != nullptr);
_finalizeCb = finalizeCb;
}
PrivateObjectBase* Object::getPrivateObject() const {
return _privateObject;
}
void Object::setPrivateObject(PrivateObjectBase* data) {
assert(_privateData == nullptr);
#if CC_DEBUG
if (data != nullptr) {
NativePtrToObjectMap::filter(data->getRaw(), _getClass())
.forEach([&](se::Object *seObj) {
auto *pri = seObj->getPrivateObject();
SE_LOGE("Already exists object %s/[%s], trying to add %s/[%s]\n", pri->getName(), typeid(*pri).name(), data->getName(), typeid(*data).name());
#if JSB_TRACK_OBJECT_CREATION
SE_LOGE(" previous object created at %s\n", it->second->_objectCreationStackFrame.c_str());
#endif
CC_ABORT();
});
}
#endif
napi_status status;
if (data) {
_privateData = data->getRaw();
_privateObject = data;
NativePtrToObjectMap::emplace(_privateData, this);
}
//issue https://github.com/nodejs/node/issues/23999
auto tmpThis = _objRef.getValue(_env);
//_objRef.deleteRef();
napi_ref result = nullptr;
NODE_API_CALL(status, _env,
napi_wrap(_env, tmpThis, this, weakCallback,
(void*)this /* finalize_hint */, &result));
//_objRef.setWeakref(_env, result);
setProperty("__native_ptr__", se::Value(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data))));
return;
}
bool Object::attachObject(Object* obj) {
assert(obj);
Object* global = ScriptEngine::getInstance()->getGlobalObject();
Value jsbVal;
if (!global->getProperty("jsb", &jsbVal)) {
return false;
}
Object* jsbObj = jsbVal.toObject();
Value func;
if (!jsbObj->getProperty("registerNativeRef", &func)) {
return false;
}
ValueArray args;
args.push_back(Value(this));
args.push_back(Value(obj));
func.toObject()->call(args, global);
return true;
}
bool Object::detachObject(Object* obj) {
assert(obj);
Object* global = ScriptEngine::getInstance()->getGlobalObject();
Value jsbVal;
if (!global->getProperty("jsb", &jsbVal)) {
return false;
}
Object* jsbObj = jsbVal.toObject();
Value func;
if (!jsbObj->getProperty("unregisterNativeRef", &func)) {
return false;
}
ValueArray args;
args.push_back(Value(this));
args.push_back(Value(obj));
func.toObject()->call(args, global);
return true;
}
std::string Object::toString() const {
std::string ret;
napi_status status;
if (isFunction() || isArray() || isTypedArray()) {
napi_value result;
NODE_API_CALL(status, _env, napi_coerce_to_string(_env, _objRef.getValue(_env), &result));
char buffer[MAX_STRING_LEN];
size_t result_t = 0;
NODE_API_CALL(status, _env, napi_get_value_string_utf8(_env, result, buffer, MAX_STRING_LEN, &result_t));
ret = buffer;
} else if (isArrayBuffer()) {
ret = "[object ArrayBuffer]";
} else {
ret = "[object Object]";
}
return ret;
}
void Object::root() {
napi_status status;
if (_rootCount == 0) {
uint32_t result = 0;
_objRef.incRef(_env);
//NODE_API_CALL(status, _env, napi_reference_ref(_env, _wrapper, &result));
}
++_rootCount;
}
void Object::unroot() {
napi_status status;
if (_rootCount > 0) {
--_rootCount;
if (_rootCount == 0) {
_objRef.decRef(_env);
}
}
}
bool Object::isRooted() const {
return _rootCount > 0;
}
Class* Object::_getClass() const {
return _cls;
}
Object* Object::getObjectWithPtr(void* ptr) {
Object* obj = nullptr;
auto iter = NativePtrToObjectMap::find(ptr);
if (iter != NativePtrToObjectMap::end()) {
obj = iter->second;
obj->incRef();
}
return obj;
}
napi_value Object::_getJSObject() const {
return _objRef.getValue(_env);
}
void Object::weakCallback(napi_env env, void* nativeObject, void* finalizeHint /*finalize_hint*/) {
if (finalizeHint) {
if (nativeObject == nullptr) {
return;
}
void *rawPtr = reinterpret_cast<Object*>(nativeObject)->_privateData;
Object* seObj = reinterpret_cast<Object*>(nativeObject);
if (seObj->_onCleaingPrivateData) { //called by cleanPrivateData, not release seObj;
return;
}
if (seObj->_clearMappingInFinalizer && rawPtr != nullptr) {
auto iter = NativePtrToObjectMap::find(rawPtr);
if (iter != NativePtrToObjectMap::end()) {
NativePtrToObjectMap::erase(iter);
} else {
CC_LOG_INFO("not find ptr in NativePtrToObjectMap");
}
}
// TODO: remove test code before releasing.
const char* clsName = seObj->_getClass()->getName();
CC_LOG_INFO("weakCallback class name:%s, ptr:%p", clsName, rawPtr);
if (seObj->_finalizeCb != nullptr) {
seObj->_finalizeCb(env, nativeObject, finalizeHint);
} else {
assert(seObj->_getClass() != nullptr);
if (seObj->_getClass()->_getFinalizeFunction() != nullptr) {
seObj->_getClass()->_getFinalizeFunction()(env, nativeObject, finalizeHint);
}
}
seObj->decRef();
}
}
void Object::setup() {
__objectMap = std::make_unique<std::unordered_map<Object*, void*>>();
}
void Object::cleanup() {
void* nativeObj = nullptr;
Object* obj = nullptr;
Class* cls = nullptr;
const auto& nativePtrToObjectMap = NativePtrToObjectMap::instance();
for (const auto& e : nativePtrToObjectMap) {
nativeObj = e.first;
obj = e.second;
if (obj->_finalizeCb != nullptr) {
obj->_finalizeCb(ScriptEngine::getEnv(), nativeObj, nullptr);
} else {
if (obj->_getClass() != nullptr) {
if (obj->_getClass()->_getFinalizeFunction() != nullptr) {
obj->_getClass()->_getFinalizeFunction()(ScriptEngine::getEnv(), nativeObj, nullptr);
}
}
}
obj->decRef();
}
NativePtrToObjectMap::clear();
if (__objectMap) {
for (const auto& e : *__objectMap) {
obj = e.first;
cls = obj->_getClass();
obj->_rootCount = 0;
}
}
__objectMap.reset();
}
Object* Object::createJSONObject(const std::string& jsonStr) {
//not impl
return nullptr;
}
void Object::clearPrivateData(bool clearMapping) {
if (_privateObject != nullptr) {
napi_status status;
void* result = nullptr;
auto tmpThis = _objRef.getValue(_env);
_onCleaingPrivateData = true;
if (clearMapping) {
NativePtrToObjectMap::erase(_privateData);
}
NODE_API_CALL(status, _env, napi_remove_wrap(_env, tmpThis, &result));
delete _privateObject;
_privateObject = nullptr;
_privateData = nullptr;
_onCleaingPrivateData = false;
}
}
} // namespace se

View File

@@ -0,0 +1,493 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include <cassert>
#include "../RefCounter.h"
#include "../Value.h"
#include "../config.h"
#include "CommonHeader.h"
#include "HelperMacros.h"
#include "../PrivateObject.h"
#include "base/std/container/unordered_map.h"
namespace se {
class Class;
class ObjectRef {
private:
napi_ref _ref = nullptr;
int _refCounts = 0;
napi_env _env = nullptr;
napi_value _obj = nullptr;
public:
~ObjectRef() {
deleteRef();
}
napi_value getValue(napi_env env) const {
napi_value result;
napi_status status;
NODE_API_CALL(status, env, napi_get_reference_value(env, _ref, &result));
assert(status == napi_ok);
assert(result != nullptr);
return result;
}
void initWeakref(napi_env env, napi_value obj) {
assert(_ref == nullptr);
_obj = obj;
_env = env;
napi_create_reference(env, obj, 0, &_ref);
}
void setWeakref(napi_env env, napi_ref ref) {
assert(_ref == nullptr);
_ref = ref;
}
void initStrongRef(napi_env env, napi_value obj) {
assert(_ref == nullptr);
_refCounts = 1;
_obj = obj;
napi_create_reference(env, obj, _refCounts, &_ref);
_env = env;
}
void incRef(napi_env env) {
assert(_refCounts == 0);
if (_refCounts == 0) {
uint32_t result = 0;
_refCounts = 1;
napi_reference_ref(env, _ref, &result);
}
}
void decRef(napi_env env) {
assert(_refCounts == 1);
uint32_t result = 0;
if (_refCounts > 0) {
_refCounts--;
if (_refCounts == 0) {
napi_reference_unref(env, _ref, &result);
}
}
}
void deleteRef() {
_refCounts = 0;
if (!_ref) {
return;
}
napi_delete_reference(_env, _ref);
_ref = nullptr;
}
};
class Object;
class Object : public RefCounter {
public:
enum class TypedArrayType {
NONE,
INT8,
INT16,
INT32,
UINT8,
UINT8_CLAMPED,
UINT16,
UINT32,
FLOAT32,
FLOAT64
};
using BufferContentsFreeFunc = void (*)(void *contents, size_t byteLength, void *userData);
struct ExternalArrayBufferCallbackParams {
BufferContentsFreeFunc func{nullptr};
void *contents{nullptr};
size_t byteLength{0};
void *userData{0};
};
Object();
~Object();
/**
* @brief Sets a property to an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] value A value to be used as the property's value.
* @return true if the property is set successfully, otherwise false.
*/
bool setProperty(const char *name, const Value &data);
inline bool setProperty(const std::string &name, const Value &value) {
return setProperty(name.c_str(), value);
}
/**
* @brief Gets a property from an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[out] value The property's value if object has the property, otherwise the undefined value.
* @return true if object has the property, otherwise false.
*/
bool getProperty(const char *name, Value *data);
inline bool getProperty(const char *name, Value *data, bool cachePropertyName) {
return getProperty(name, data);
}
inline bool getProperty(const std::string &name, Value *value) {
return getProperty(name.c_str(), value);
}
void setPrivateObject(PrivateObjectBase *data);
PrivateObjectBase *getPrivateObject() const;
/**
* @brief Gets an object's private data.
* @return A void* that is the object's private data, if the object has private data, otherwise nullptr.
*/
inline void *getPrivateData() const {
return _privateData;
}
/**
* @brief Sets a pointer to private data on an object.
* @param[in] data A void* to set as the object's private data.
* @note This method will associate private data with se::Object by ccstd::unordered_map::emplace.
* It's used for search a se::Object via a void* private data.
*/
template <typename T>
inline void setPrivateData(T *data) {
static_assert(!std::is_void<T>::value, "void * is not allowed for private data");
setPrivateObject(se::make_shared_private_object(data));
}
/**
* @brief Use a InstrusivePtr to hold private data on the se::Object.
*
* @tparam T
* @param data A intrusive pointer object
*/
template <typename T>
inline void setPrivateData(const cc::IntrusivePtr<T> &data) {
setPrivateObject(se::ccintrusive_ptr_private_object(data));
}
/**
* @brief Use a std::shared_ptr to hold private data on the se::Object.
*
* @tparam T
* @param data A shared_ptr object
*/
template <typename T>
inline void setPrivateData(const std::shared_ptr<T> &data) {
setPrivateObject(se::shared_ptr_private_object(data));
}
/**
* @brief Sets a pointer to private data on an object.
* @param[in] data A void* to set as the object's private data.
* @note This method will associate private data with se::Object by ccstd::unordered_map::emplace.
* It's used for search a se::Object via a void* private data.
*/
template <typename T>
inline void setRawPrivateData(T *data, bool tryDestroyInGC = false) {
static_assert(!std::is_void<T>::value, "void * is not allowed for private data");
auto *privateObject = se::rawref_private_object(data);
if (tryDestroyInGC) {
privateObject->tryAllowDestroyInGC();
}
setPrivateObject(privateObject);
}
/**
* @brief Get the underlying private data as std::shared_ptr
*
* @tparam T
* @return std::shared_ptr<T>
*/
template <typename T>
inline std::shared_ptr<T> getPrivateSharedPtr() const {
assert(_privateObject->isSharedPtr());
return static_cast<se::SharedPtrPrivateObject<T> *>(_privateObject)->getData();
}
/**
* @brief Get the underlying private data as InstrusivePtr
*
* @tparam T
* @return cc::IntrusivePtr<T>
*/
template <typename T>
inline cc::IntrusivePtr<T> getPrivateInstrusivePtr() const {
assert(_privateObject->isCCIntrusivePtr());
return static_cast<se::CCIntrusivePtrPrivateObject<T> *>(_privateObject)->getData();
}
template <typename T>
inline T *getTypedPrivateData() const {
return reinterpret_cast<T *>(getPrivateData());
}
/**
* @brief Clears private data of an object.
* @param clearMapping Whether to clear the mapping of native object & se::Object.
*/
void clearPrivateData(bool clearMapping = true);
/**
* @brief Sets whether to clear the mapping of native object & se::Object in finalizer
*/
void setClearMappingInFinalizer(bool v) { _clearMappingInFinalizer = v; }
/**
* @brief Tests whether an object is an array.
* @return true if object is an array, otherwise false.
*/
bool isArray() const;
/**
* @brief Gets array length of an array object.
* @param[out] length The array length to be stored. It's set to 0 if there is an error.
* @return true if succeed, otherwise false.
*/
bool getArrayLength(uint32_t *length) const;
/**
* @brief Gets an element from an array object by numeric index.
* @param[in] index An integer value for index.
* @param[out] data The se::Value to be stored for the element in certain index.
* @return true if succeed, otherwise false.
*/
bool getArrayElement(uint32_t index, Value *data) const;
/**
* @brief Sets an element to an array object by numeric index.
* @param[in] index An integer value for index.
* @param[in] data The se::Value to be set to array with certain index.
* @return true if succeed, otherwise false.
*/
bool setArrayElement(uint32_t index, const Value &data);
/** @brief Tests whether an object is a typed array.
* @return true if object is a typed array, otherwise false.
*/
bool isTypedArray() const;
/** @brief Tests whether an object is a proxy object.
* @return true if object is a proxy object, otherwise false.
*/
bool isProxy() const;
/**
* @brief Gets the type of a typed array object.
* @return The type of a typed array object.
*/
TypedArrayType getTypedArrayType() const;
/**
* @brief Gets backing store of a typed array object.
* @param[out] ptr A temporary pointer to the backing store of a JavaScript Typed Array object.
* @param[out] length The byte length of a JavaScript Typed Array object.
* @return true if succeed, otherwise false.
*/
bool getTypedArrayData(uint8_t **ptr, size_t *length) const;
/**
* @brief Tests whether an object is an array buffer object.
* @return true if object is an array buffer object, otherwise false.
*/
bool isArrayBuffer() const;
/**
* @brief Gets buffer data of an array buffer object.
* @param[out] ptr A pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object.
* @param[out] length The number of bytes in a JavaScript data object.
* @return true if succeed, otherwise false.
*/
bool getArrayBufferData(uint8_t **ptr, size_t *length) const;
/**
* @brief Creates a JavaScript Typed Array Object with specified format from an existing pointer,
if provide a null pointer,then will create a empty JavaScript Typed Array Object.
* @param[in] type The format of typed array.
* @param[in] data A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createTypedArray(TypedArrayType type, const void *data, size_t byteLength);
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj);
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset);
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength);
static Object *createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData = nullptr);
/**
* @brief Tests whether an object can be called as a function.
* @return true if object can be called as a function, otherwise false.
*/
bool isFunction() const;
/**
* @brief Defines a function with a native callback for an object.
* @param[in] funcName A utf-8 string containing the function name.
* @param[in] func The native callback triggered by JavaScript code.
* @return true if succeed, otherwise false.
*/
bool defineFunction(const char *funcName, napi_callback func);
/**
* @brief Defines a property with native accessor callbacks for an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] getter The native callback for getter.
* @param[in] setter The native callback for setter.
* @return true if succeed, otherwise false.
*/
bool defineProperty(const char *name, napi_callback getter, napi_callback setter);
bool attachObject(Object *obj);
/**
* @brief Detaches an object from current object.
* @param[in] obj The object to be detached.
* @return true if succeed, otherwise false.
* @note The attached object will not be released if current object is not garbage collected.
*/
bool detachObject(Object *obj);
/**
* @brief Calls an object as a function.
* @param[in] args A se::Value array of arguments to pass to the function. Pass se::EmptyValueArray if argumentCount is 0.
* @param[in] thisObject The object to use as "this," or NULL to use the global object as "this."
* @param[out] rval The se::Value that results from calling object as a function, passing nullptr if return value is ignored.
* @return true if object is a function and there isn't any errors, otherwise false.
*/
bool call(const ValueArray &args, Object *thisObject, Value *rval = nullptr);
/**
* @brief Creates a JavaScript Native Binding Object from an existing se::Class instance.
* @param[in] cls The se::Class instance which stores native callback informations.
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createObjectWithClass(Class *cls);
static Object *_createJSObject(napi_env env, napi_value js_object, Class *cls);
/**
* @brief Creates a JavaScript Object like `{} or new Object()`.
* @return A JavaScript Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createPlainObject();
/**
* @brief Creates a JavaScript Array Object like `[] or new Array()`.
* @param[in] length The initical length of array.
* @return A JavaScript Array Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createArrayObject(size_t length);
/**
* @brief Creates a JavaScript Array Buffer object from an existing pointer.
* @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A Array Buffer Object whose backing store is the same as the one pointed to data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createArrayBufferObject(const void *data, size_t byteLength);
/**
* Gets the Proxy Target object
* @param proxy The JavaScript Proxy object.
* @return The target JavaScript object of the parameter.
*/
static Object *createProxyTarget(se::Object *proxy);
/**
* @brief Roots an object from garbage collection.
* @note Use this method when you want to store an object in a global or on the heap, where the garbage collector will not be able to discover your reference to it.
* An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void root();
/**
* @brief Unroots an object from garbage collection.
* @note An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void unroot();
/**
* @brief Tests whether an object is rooted.
* @return true if it has been already rooted, otherwise false.
*/
bool isRooted() const;
/**
* @brief Creates a JavaScript Object from a JSON formatted string.
* @param[in] jsonStr The utf-8 string containing the JSON string to be parsed.
* @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid.
* @note The return value (non-null) has to be released manually.
*/
static Object *createJSONObject(const std::string &jsonStr);
/**
* @brief Gets all property names of an object.
* @param[out] allKeys A string vector to store all property names.
* @return true if succeed, otherwise false.
*/
bool getAllKeys(std::vector<std::string> *allKeys) const;
/**
* @brief Gets a se::Object from an existing native object pointer.
* @param[in] ptr The native object pointer associated with the se::Object
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *getObjectWithPtr(void *ptr);
Class * _getClass() const; // NOLINT(readability-identifier-naming)
napi_value _getJSObject() const;
void _setFinalizeCallback(napi_finalize finalizeCb); // NOLINT(readability-identifier-naming)
/**
* @brief Returns the string for describing current object.
* @return The string for describing current object.
*/
std::string toString() const;
bool init(napi_env env, napi_value js_object, Class *cls);
private:
static void weakCallback(napi_env env, void *nativeObject, void * /*finalize_hint*/);
static void setup();
static void cleanup();
private:
ObjectRef _objRef;
napi_finalize _finalizeCb = nullptr;
bool _clearMappingInFinalizer = true;
void *_privateData = nullptr;
PrivateObjectBase *_privateObject = nullptr;
napi_env _env = nullptr;
Class * _cls = nullptr;
uint32_t _rootCount = 0;
bool _onCleaingPrivateData = false;
friend class ScriptEngine;
};
}; // namespace se

View File

@@ -0,0 +1,312 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.h"
#include <sstream>
#include "../MappingUtils.h"
#include "Class.h"
#include "Utils.h"
#include "CommonHeader.h"
#include <napi/native_api.h>
#include "base/std/container/array.h"
namespace se {
AutoHandleScope::AutoHandleScope() {
napi_open_handle_scope(ScriptEngine::getEnv(), &_handleScope);
}
AutoHandleScope::~AutoHandleScope() {
napi_close_handle_scope(ScriptEngine::getEnv(), _handleScope);
}
ScriptEngine *gSriptEngineInstance = nullptr;
ScriptEngine::ScriptEngine() {};
ScriptEngine::~ScriptEngine() = default;
void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate &delegate) {
_fileOperationDelegate = delegate;
}
const ScriptEngine::FileOperationDelegate &ScriptEngine::getFileOperationDelegate() const {
return _fileOperationDelegate;
}
ScriptEngine *ScriptEngine::getInstance() {
if (gSriptEngineInstance == nullptr) {
gSriptEngineInstance = new ScriptEngine();
}
return gSriptEngineInstance;
}
void ScriptEngine::destroyInstance() {
if (gSriptEngineInstance) {
gSriptEngineInstance->cleanup();
delete gSriptEngineInstance;
gSriptEngineInstance = nullptr;
}
}
bool ScriptEngine::runScript(const std::string &path, Value *ret /* = nullptr */) {
assert(!path.empty());
napi_status status;
napi_value result = nullptr;
LOGI("run script : %{public}s", path.c_str());
//NODE_API_CALL(status, ScriptEngine::getEnv(), napi_run_script_path(ScriptEngine::getEnv(), path.c_str(), &result));
if (ret && result) {
internal::jsToSeValue(result, ret);
}
return false;
}
bool ScriptEngine::evalString(const char *scriptStr, ssize_t length, Value *ret, const char *fileName) {
napi_status status;
napi_value script;
napi_value result;
length = length < 0 ? NAPI_AUTO_LENGTH : length;
status = napi_create_string_utf8(ScriptEngine::getEnv(), scriptStr, length, &script);
LOGI("eval :%{public}s", scriptStr);
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_run_script(ScriptEngine::getEnv(), script, &result));
return true;
}
bool ScriptEngine::init() {
napi_status status;
napi_value result;
for (const auto &hook : _beforeInitHookArray) {
hook();
}
Object::setup();
NativePtrToObjectMap::init();
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_global(ScriptEngine::getEnv(), &result));
_globalObj = Object::_createJSObject(ScriptEngine::getEnv(), result, nullptr);
_globalObj->root();
_globalObj->setProperty("window", Value(_globalObj));
_globalObj->setProperty("scriptEngineType", se::Value("napi"));
_isValid = true;
for (const auto &hook : _afterInitHookArray) {
hook();
}
_afterInitHookArray.clear();
return _isValid;
}
Object *ScriptEngine::getGlobalObject() const {
return _globalObj;
}
bool ScriptEngine::start() {
bool ok = true;
if (!init()) {
return false;
}
_startTime = std::chrono::steady_clock::now();
for (auto cb : _permRegisterCallbackArray) {
ok = cb(_globalObj);
assert(ok);
if (!ok) {
break;
}
}
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::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();
Class::cleanup();
garbageCollect();
_globalObj = nullptr;
_isValid = false;
_registerCallbackArray.clear();
for (const auto &hook : _afterCleanupHookArray) {
hook();
}
_afterCleanupHookArray.clear();
_isInCleanup = false;
NativePtrToObjectMap::destroy();
SE_LOGD("ScriptEngine::cleanup end ...\n");
}
void ScriptEngine::addBeforeCleanupHook(const std::function<void()> &hook) {
_beforeCleanupHookArray.push_back(hook);
return;
}
void ScriptEngine::addAfterCleanupHook(const std::function<void()> &hook) {
_afterCleanupHookArray.push_back(hook);
return;
}
void ScriptEngine::addRegisterCallback(RegisterCallback cb) {
assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end());
_registerCallbackArray.push_back(cb);
}
napi_env ScriptEngine::getEnv() {
return getInstance()->_env;
}
void ScriptEngine::setEnv(napi_env env) {
getInstance()->_env = env;
}
void ScriptEngine::addPermanentRegisterCallback(RegisterCallback cb) {
if (std::find(_permRegisterCallbackArray.begin(), _permRegisterCallbackArray.end(), cb) == _permRegisterCallbackArray.end()) {
_permRegisterCallbackArray.push_back(cb);
}
}
void ScriptEngine::setExceptionCallback(const ExceptionCallback &cb) {
//not impl
return;
}
const std::chrono::steady_clock::time_point &ScriptEngine::getStartTime() const {
return _startTime;
}
bool ScriptEngine::isValid() const {
return _isValid;
}
void ScriptEngine::enableDebugger(const std::string &serverAddr, uint32_t port, bool isWait) {
//not impl
return;
}
bool ScriptEngine::saveByteCodeToFile(const std::string &path, const std::string &pathBc) {
//not impl
return true;
}
void ScriptEngine::clearException() {
//not impl
return;
}
void ScriptEngine::garbageCollect() {
//not impl
return;
}
bool ScriptEngine::isGarbageCollecting() const {
return _isGarbageCollecting;
}
void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting) { //NOLINT(readability-identifier-naming)
_isGarbageCollecting = isGarbageCollecting;
}
void ScriptEngine::setJSExceptionCallback(const ExceptionCallback &cb) {
//not impl
return;
}
void ScriptEngine::addAfterInitHook(const std::function<void()> &hook) {
_afterInitHookArray.push_back(hook);
return;
}
std::string ScriptEngine::getCurrentStackTrace() {
//not impl
return "";
}
void ScriptEngine::_setNeedCallConstructor(bool need) {
_isneedCallConstructor = need;
}
bool ScriptEngine::_needCallConstructor() {
return _isneedCallConstructor;
}
bool ScriptEngine::callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval) {
Value objFunc;
if (!targetObj->getProperty(funcName, &objFunc)) {
return false;
}
ValueArray argv;
for (size_t i = 0; i < argc; ++i) {
argv.push_back(args[i]);
}
objFunc.toObject()->call(argv, targetObj, rval);
return true;
}
void ScriptEngine::handlePromiseExceptions() {
//not impl
assert(true);
return;
}
void ScriptEngine::mainLoopUpdate() {
// empty implementation
}
void ScriptEngine::throwException(const std::string &errorMessage) {
napi_status status;
NODE_API_CALL_RETURN_VOID(getEnv(), napi_throw_error(getEnv(), nullptr, errorMessage.c_str()));
}
}; // namespace se

View File

@@ -0,0 +1,305 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include <functional>
#include <thread>
#include "../Value.h"
#include "../config.h"
#include "CommonHeader.h"
using HandleScope = napi_handle_scope;
namespace se {
class AutoHandleScope {
public:
// This interface needs to be implemented in NAPI, similar to V8.
// Ref:https://nodejs.org/docs/latest-v17.x/api/n-api.html#object-lifetime-management
AutoHandleScope();
~AutoHandleScope();
private:
HandleScope _handleScope;
};
using RegisterCallback = bool (*)(Object *);
using ExceptionCallback = std::function<void(const char *, const char *, const char *)>; // location, message, stack
class ScriptEngine {
public:
/**
* Delegate class for file operation
*/
class FileOperationDelegate {
public:
FileOperationDelegate()
: onGetDataFromFile(nullptr),
onGetStringFromFile(nullptr),
onCheckFileExist(nullptr),
onGetFullPath(nullptr) {}
/**
* @brief Tests whether delegate is valid.
*/
bool isValid() const {
return onGetDataFromFile != nullptr && onGetStringFromFile != nullptr && onCheckFileExist != nullptr && onGetFullPath != nullptr;
}
// path, buffer, buffer size
std::function<void(const std::string &, const std::function<void(const uint8_t *, size_t)> &)> onGetDataFromFile;
// path, return file string content.
std::function<std::string(const std::string &)> onGetStringFromFile;
// path
std::function<bool(const std::string &)> onCheckFileExist;
// path, return full path
std::function<std::string(const std::string &)> onGetFullPath;
};
ScriptEngine();
~ScriptEngine();
/**
* @brief Sets the delegate for file operation.
* @param delegate[in] The delegate instance for file operation.
*/
void setFileOperationDelegate(const FileOperationDelegate &delegate);
/**
* @brief Compile script file into v8::ScriptCompiler::CachedData and save to file.
* @param[in] path The path of script file.
* @param[in] pathBc The location where bytecode file should be written to. The path should be ends with ".bc", which indicates a bytecode file.
* @return true if succeed, otherwise false.
*/
bool saveByteCodeToFile(const std::string &path, const std::string &pathBc);
/**
* @brief Gets the delegate for file operation.
* @return The delegate for file operation
*/
const FileOperationDelegate &getFileOperationDelegate() const;
static ScriptEngine *getInstance();
/**
* @brief Destroys the instance of script engine.
*/
static void destroyInstance();
/**
* @brief Clears all exceptions.
*/
void clearException();
/**
* @brief Sets the callback function while an exception is fired in JS.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setJSExceptionCallback(const ExceptionCallback &cb);
/**
* @brief Sets the callback function while an exception is fired.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setExceptionCallback(const ExceptionCallback &cb);
/**
* @brief Grab a snapshot of the current JavaScript execution stack.
* @return current stack trace string
*/
std::string getCurrentStackTrace();
/**
* @brief Executes a utf-8 string buffer which contains JavaScript code.
* @param[in] script A utf-8 string buffer, if it isn't null-terminated, parameter `length` should be assigned and > 0.
* @param[in] length The length of parameter `scriptStr`, it will be set to string length internally if passing < 0 and parameter `scriptStr` is null-terminated.
* @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @param[in] fileName A string containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information.
* @return true if succeed, otherwise false.
*/
bool evalString(const char *script, ssize_t length = -1, Value *ret = nullptr, const char *fileName = nullptr);
/**
* @brief Adds a callback for registering a native binding module, which will not be removed by ScriptEngine::cleanup.
* @param[in] cb A callback for registering a native binding module.
* @note This method just add a callback to a vector, callbacks is invoked in `start` method.
*/
void addPermanentRegisterCallback(RegisterCallback cb);
/**
* @brief Starts the script engine.
* @return true if succeed, otherwise false.
* @note This method will invoke all callbacks of native binding modules by the order of registration.
*/
bool start();
/**
* @brief Initializes script engine.
* @return true if succeed, otherwise false.
* @note This method will create JavaScript context and global object.
*/
bool init();
/**
* @brief Adds a hook function before initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeInitHook(const std::function<void()> &hook);
/**
* @brief Adds a hook function after initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterInitHook(const std::function<void()> &hook);
/**
* @brief Cleanups script engine.
* @note This method will removes all objects in JavaScript VM even whose are rooted, then shutdown JavaScript VMf.
*/
void cleanup();
/**
* @brief Adds a hook function before cleanuping script engine.
* @param[in] hook A hook function to be invoked before cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeCleanupHook(const std::function<void()> &hook);
/**
* @brief Adds a hook function after cleanuping script engine.
* @param[in] hook A hook function to be invoked after cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterCleanupHook(const std::function<void()> &hook);
/**
* @brief Gets the global object of JavaScript VM.
* @return The se::Object stores the global JavaScript object.
*/
Object *getGlobalObject() const;
static napi_env getEnv();
static void setEnv(napi_env env);
/**
* @brief Adds a callback for registering a native binding module.
* @param[in] cb A callback for registering a native binding module.
* @note This method just add a callback to a vector, callbacks is invoked in `start` method.
*/
void addRegisterCallback(RegisterCallback cb);
const std::chrono::steady_clock::time_point &getStartTime() const;
/**
* @brief Tests whether script engine is doing garbage collection.
* @return true if it's in garbage collection, otherwise false.
*/
bool isGarbageCollecting() const;
/**
* @brief Performs a JavaScript garbage collection.
*/
void garbageCollect();
/**
* @brief Tests whether script engine is being cleaned up.
* @return true if it's in cleaning up, otherwise false.
*/
bool isInCleanup() const { return _isInCleanup; }
/**
* @brief Executes a file which contains JavaScript code.
* @param[in] path Script file path.
* @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @return true if succeed, otherwise false.
*/
bool runScript(const std::string &path, Value *ret = nullptr);
/**
* @brief Tests whether script engine is valid.
* @return true if it's valid, otherwise false.
*/
bool isValid() const;
/**
* @brief Enables JavaScript debugger
* @param[in] serverAddr The address of debugger server.
* @param[in] isWait Whether wait debugger attach when loading.
*/
void enableDebugger(const std::string &serverAddr, uint32_t port, bool isWait = false);
/**
* @brief Tests whether JavaScript debugger is enabled
* @return true if JavaScript debugger is enabled, otherwise false.
*/
bool isDebuggerEnabled() const;
/**
* @brief Main loop update trigger, it's need to invoked in main thread every frame.
*/
void mainLoopUpdate();
bool runByteCodeFile(const std::string &pathBc, Value *ret /* = nullptr */);
/**
* @brief Throw JS exception
*/
void throwException(const std::string &errorMessage);
/**
* @brief for napi_new_instance, skip constructor.
*/
void _setNeedCallConstructor(bool need);
/**
* @brief for napi_new_instance, skip constructor.
*/
bool _needCallConstructor();
/**
* @brief Fast version of call script function, faster than Object::call
*/
bool callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval = nullptr);
void _setGarbageCollecting(bool isGarbageCollecting); // NOLINT(readability-identifier-naming)
void handlePromiseExceptions();
private:
FileOperationDelegate _fileOperationDelegate;
std::vector<RegisterCallback> _registerCallbackArray;
std::vector<RegisterCallback> _permRegisterCallbackArray;
std::vector<std::function<void()>> _beforeInitHookArray;
std::vector<std::function<void()>> _afterInitHookArray;
std::vector<std::function<void()>> _beforeCleanupHookArray;
std::vector<std::function<void()>> _afterCleanupHookArray;
Object * _globalObj = nullptr;
napi_env _env = nullptr;
bool _isValid{false};
bool _isGarbageCollecting{false};
bool _isInCleanup{false};
bool _isErrorHandleWorking{false};
bool _isneedCallConstructor{true};
std::chrono::steady_clock::time_point _startTime;
};
}; // namespace se

View File

@@ -0,0 +1,32 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include "Class.h"
#include "HelperMacros.h"
#include "Object.h"
#include "ScriptEngine.h"
#include "Utils.h"

View File

@@ -0,0 +1,195 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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 "Utils.h"
#include "CommonHeader.h"
#include "ScriptEngine.h"
#include "Class.h"
#define MAX_STRING_LENS 1024
namespace se {
namespace internal {
void jsToSeValue(const target_value& value, Value* v) {
assert(v != nullptr);
napi_status status;
napi_valuetype valType;
int64_t iRet = 0;
double dRet = 0.0;
bool bRet = false;
bool lossless = false;
size_t len = 0;
void* privateObjPtr = nullptr;
void* nativePtr = nullptr;
Object* obj = nullptr;
if (!value) {
valType = napi_valuetype::napi_undefined;
}else {
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_typeof(ScriptEngine::getEnv(), value, &valType));
}
switch (valType) {
case napi_valuetype::napi_undefined:
v->setUndefined();
break;
case napi_valuetype::napi_null:
v->setNull();
break;
case napi_valuetype::napi_number:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_double(ScriptEngine::getEnv(), value, &dRet));
if (status == napi_ok) {
v->setDouble(dRet);
} else {
v->setUndefined();
}
break;
case napi_valuetype::napi_bigint:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_bigint_int64(ScriptEngine::getEnv(), value, &iRet, &lossless));
if (lossless) {
v->setInt64(iRet);
} else {
v->setUndefined();
}
break;
case napi_valuetype::napi_string:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_string_utf8(ScriptEngine::getEnv(), value, nullptr, 0, &len));
if (status == napi_ok) {
std::string valueStr;
len += 1;
valueStr.resize(len);
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_string_utf8(ScriptEngine::getEnv(), value, const_cast<char*>(valueStr.data()), valueStr.size(), &len));
if (valueStr.length() != len) {
valueStr.resize(len);
}
v->setString(valueStr);
} else {
v->setUndefined();
}
break;
case napi_valuetype::napi_boolean:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_bool(ScriptEngine::getEnv(), value, &bRet));
if (status == napi_ok) {
v->setBoolean(bRet);
} else {
v->setUndefined();
}
break;
case napi_valuetype::napi_object:
case napi_valuetype::napi_function:
status = napi_unwrap(ScriptEngine::getEnv(), value, &privateObjPtr);
if ((status == napi_ok) && privateObjPtr) {
nativePtr = reinterpret_cast<Object*>(privateObjPtr)->getPrivateData();
}
if (nativePtr) {
obj = Object::getObjectWithPtr(nativePtr);
}
if (obj == nullptr) {
obj = Object::_createJSObject(ScriptEngine::getEnv(), value, nullptr);
}
if (obj) {
v->setObject(obj, true);
obj->decRef();
} else {
v->setUndefined();
}
break;
default:
break;
}
}
void jsToSeArgs(size_t argc, target_value* argv, ValueArray* outArr) {
assert(outArr != nullptr);
for (int i = 0; i < argc; i++) {
Value v;
jsToSeValue(argv[i], &v);
outArr->push_back(v);
}
}
bool seToJsValue(const Value& v, target_value* outJsVal) {
assert(outJsVal != nullptr);
bool ret = false;
napi_status status = napi_ok;
switch (v.getType()) {
case Value::Type::Number:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_double(ScriptEngine::getEnv(), v.toDouble(), outJsVal));
ret = (status == napi_ok);
break;
case Value::Type::String: {
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_string_utf8(ScriptEngine::getEnv(), v.toString().c_str(), v.toString().length(), outJsVal));
ret = (status == napi_ok);
} break;
case Value::Type::Boolean:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_boolean(ScriptEngine::getEnv(), v.toBoolean(), outJsVal));
ret = (status == napi_ok);
break;
case Value::Type::Object:
*outJsVal = v.toObject()->_getJSObject();
ret = (outJsVal != nullptr);
break;
case Value::Type::Null:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_null(ScriptEngine::getEnv(), outJsVal));
ret = (status == napi_ok);
break;
case Value::Type::Undefined:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_undefined(ScriptEngine::getEnv(), outJsVal));
ret = (status == napi_ok);
break;
case Value::Type::BigInt:
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_bigint_int64(ScriptEngine::getEnv(), v.toInt64(), outJsVal));
ret = (status == napi_ok);
break;
default:
assert(false);
break;
}
//LOGI("type :%d", v.getType());
return ret;
}
void seToJsArgs(napi_env env, const ValueArray& args, std::vector<target_value>* outArr) {
assert(outArr != nullptr);
for (const auto& data : args) {
napi_value jsval;
seToJsValue(data, &jsval);
outArr->push_back(jsval);
}
}
bool setReturnValue(const Value& data, target_value& argv) {
if (data.getType() == Value::Type::BigInt) {
// TODO: fix 'TypeError: Cannot mix BigInt and other types, use explicit conversions' for spine & dragonbones
napi_status status;
NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_double(ScriptEngine::getEnv(), data.toDouble(), &argv));
return true;
}
return seToJsValue(data, &argv);
}
} // namespace internal
}; // namespace se

View File

@@ -0,0 +1,46 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include "../config.h"
#include "Object.h"
namespace se {
namespace internal {
using target_value = napi_value;
struct PrivateData {
void * data;
Object *seObj;
};
bool setReturnValue(const Value &data, target_value &argv);
void jsToSeValue(const target_value &value, Value *v);
void jsToSeArgs(size_t argc, target_value *argv, ValueArray *outArr);
bool seToJsValue(const Value &v, target_value *jsval);
void seToJsArgs(napi_env env, const ValueArray &args, std::vector<target_value> *outArr);
} // namespace internal
} // namespace se

View File

@@ -0,0 +1,115 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#ifndef FOUNDATION_ACE_NAPI_INTERFACES_KITS_NAPI_NATIVE_COMMON_H
#define FOUNDATION_ACE_NAPI_INTERFACES_KITS_NAPI_NATIVE_COMMON_H
#define DEPRECATED __attribute__((__deprecated__))
#ifndef NAPI_VERSION
#define NAPI_VERSION 8
#endif
#define NAPI_RETVAL_NOTHING
#define GET_AND_THROW_LAST_ERROR(env) \
do { \
const napi_extended_error_info* errorInfo = nullptr; \
napi_get_last_error_info((env), &errorInfo); \
bool isPending = false; \
napi_is_exception_pending((env), &isPending); \
if (!isPending && errorInfo != nullptr) { \
const char* errorMessage = \
errorInfo->error_message != nullptr ? errorInfo->error_message : "empty error message"; \
napi_throw_error((env), nullptr, errorMessage); \
} \
} while (0)
#define NAPI_ASSERT_BASE(env, assertion, message, retVal) \
do { \
if (!(assertion)) { \
napi_throw_error((env), nullptr, "assertion (" #assertion ") failed: " message); \
return retVal; \
} \
} while (0)
#define NAPI_ASSERT(env, assertion, message) NAPI_ASSERT_BASE(env, assertion, message, nullptr)
#define NAPI_ASSERT_RETURN_VOID(env, assertion, message) NAPI_ASSERT_BASE(env, assertion, message, NAPI_RETVAL_NOTHING)
#define NAPI_CALL_BASE(env, theCall, retVal) \
do { \
if ((theCall) != napi_ok) { \
GET_AND_THROW_LAST_ERROR((env)); \
return retVal; \
} \
} while (0)
#define NAPI_CALL(env, theCall) NAPI_CALL_BASE(env, theCall, nullptr)
#define NAPI_CALL_RETURN_VOID(env, theCall) NAPI_CALL_BASE(env, theCall, NAPI_RETVAL_NOTHING)
#define DECLARE_NAPI_PROPERTY(name, val) \
{ \
(name), nullptr, nullptr, nullptr, nullptr, val, napi_default, nullptr \
}
#define DECLARE_NAPI_STATIC_PROPERTY(name, val) \
{ \
(name), nullptr, nullptr, nullptr, nullptr, val, napi_static, nullptr \
}
#define DECLARE_NAPI_FUNCTION(name, func) \
{ \
(name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, nullptr \
}
#define DECLARE_NAPI_FUNCTION_WITH_DATA(name, func, data) \
{ \
(name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, data \
}
#define DECLARE_NAPI_STATIC_FUNCTION(name, func) \
{ \
(name), nullptr, (func), nullptr, nullptr, nullptr, napi_static, nullptr \
}
#define DECLARE_NAPI_GETTER(name, getter) \
{ \
(name), nullptr, nullptr, (getter), nullptr, nullptr, napi_default, nullptr \
}
#define DECLARE_NAPI_SETTER(name, setter) \
{ \
(name), nullptr, nullptr, nullptr, (setter), nullptr, napi_default, nullptr \
}
#define DECLARE_NAPI_GETTER_SETTER(name, getter, setter) \
{ \
(name), nullptr, nullptr, (getter), (setter), nullptr, napi_default, nullptr \
}
#endif /* FOUNDATION_ACE_NAPI_INTERFACES_KITS_NAPI_NATIVE_COMMON_H */

View File

@@ -0,0 +1,63 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#ifndef UINT64_C
#define UINT64_C(value) __CONCAT(value, ULL)
#endif
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/Array.h"
#include "js/ArrayBuffer.h"
#include "js/BigInt.h"
#include "js/BuildId.h"
#include "js/CompilationAndEvaluation.h"
#include "js/ContextOptions.h"
#include "js/Conversions.h"
#include "js/Equality.h"
#include "js/GCAPI.h"
#include "js/Initialization.h"
#include "js/JSON.h"
#include "js/LocaleSensitive.h"
#include "js/MemoryMetrics.h"
#include "js/SourceText.h"
#include "js/Warnings.h"
#include "js/experimental/TypedData.h"
#include "mozilla/Unused.h"
#include "../PrivateObject.h"
#include "HelperMacros.h"
#include <assert.h>
#include <chrono>
#include <functional>
#include <initializer_list>
#include <unordered_map>
#include <vector>
#include "base/std/container/string.h"

View File

@@ -0,0 +1,242 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "Class.h"
#include "Object.h"
#include "ScriptEngine.h"
#include "Utils.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
namespace se {
// --- Global Lookup for Constructor Functions
namespace {
// std::unordered_map<std::string, Class *> __clsMap;
JSContext *__cx = nullptr;
bool empty_constructor(JSContext *cx, uint32_t argc, JS::Value *vp) {
assert(false);
return true;
}
ccstd::vector<Class *> __allClasses;
} // namespace
Class::Class()
: _parent(nullptr),
_proto(nullptr),
_parentProto(nullptr),
_ctor(nullptr),
_finalizeOp(nullptr) {
memset(&_jsCls, 0, sizeof(_jsCls));
memset(&_classOps, 0, sizeof(_classOps));
__allClasses.push_back(this);
}
Class::~Class() {
}
Class *Class::create(const char *className, Object *obj, Object *parentProto, JSNative ctor) {
Class *cls = ccnew Class();
if (cls != nullptr && !cls->init(className, obj, parentProto, ctor)) {
delete cls;
cls = nullptr;
}
return cls;
}
Class *Class::create(const std::initializer_list<const char *> &classPath, se::Object *parent, Object *parentProto, JSNative ctor) {
se::AutoHandleScope scope;
se::Object *currentParent = parent;
se::Value tmp;
for (auto i = 0; i < classPath.size() - 1; i++) {
bool ok = currentParent->getProperty(*(classPath.begin() + i), &tmp);
CC_ASSERT(ok); // class or namespace in path is not defined
currentParent = tmp.toObject();
}
return create(*(classPath.end() - 1), currentParent, parentProto, ctor);
}
bool Class::init(const char *clsName, Object *parent, Object *parentProto, JSNative ctor) {
_name = clsName;
_parent = parent;
if (_parent != nullptr)
_parent->incRef();
_parentProto = parentProto;
if (_parentProto != nullptr)
_parentProto->incRef();
_ctor = ctor;
if (_ctor == nullptr) {
_ctor = empty_constructor;
}
// SE_LOGD("Class init ( %s ) ...\n", clsName);
return true;
}
void Class::destroy() {
SAFE_DEC_REF(_parent);
SAFE_DEC_REF(_proto);
SAFE_DEC_REF(_parentProto);
}
/* static */
void Class::onTraceCallback(JSTracer *trc, JSObject *obj) {
auto *seObj = reinterpret_cast<Object *>(internal::SE_JS_GetPrivate(obj, 1));
if (seObj != nullptr) {
JS::TraceEdge(trc, &seObj->_heap, "seObj");
}
}
bool Class::install() {
// assert(__clsMap.find(_name) == __clsMap.end());
//
// __clsMap.emplace(_name, this);
_jsCls.name = _name;
_jsCls.flags = JSCLASS_USERBIT1 | JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_FOREGROUND_FINALIZE; //IDEA: Use JSCLASS_BACKGROUND_FINALIZE to improve GC performance
if (_finalizeOp != nullptr) {
_classOps.finalize = _finalizeOp;
} else {
_classOps.finalize = [](JSFreeOp *fop, JSObject *obj) {};
}
_classOps.trace = Class::onTraceCallback;
_jsCls.cOps = &_classOps;
JSObject *parentObj = _parentProto != nullptr ? _parentProto->_getJSObject() : nullptr;
JS::RootedObject parentProto(__cx, parentObj);
JS::RootedObject parent(__cx, _parent->_getJSObject());
_funcs.push_back(JS_FS_END);
_properties.push_back(JS_PS_END);
_staticFuncs.push_back(JS_FS_END);
_staticProperties.push_back(JS_PS_END);
JS::RootedObject jsobj(__cx, JS_InitClass(__cx, parent, parentProto, &_jsCls, _ctor, 0, _properties.data(), _funcs.data(), _staticProperties.data(), _staticFuncs.data()));
if (jsobj != nullptr) {
_proto = Object::_createJSObject(nullptr, jsobj);
// SE_LOGD("_proto: %p, name: %s\n", _proto, _name);
_proto->root();
return true;
}
return false;
}
bool Class::defineFunction(const char *name, JSNative func) {
JSFunctionSpec cb = JS_FN(name, func, 0, JSPROP_ENUMERATE);
_funcs.push_back(cb);
return true;
}
bool Class::defineProperty(const char *name, JSNative getter, JSNative setter) {
JSPropertySpec property = JS_PSGS(name, getter, setter, JSPROP_ENUMERATE);
_properties.push_back(property);
return true;
}
bool Class::defineProperty(const std::initializer_list<const char *> &names, JSNative getter, JSNative setter) {
bool ret = true;
for (const auto *name : names) {
ret &= defineProperty(name, getter, setter);
}
return ret;
}
bool Class::defineStaticFunction(const char *name, JSNative func) {
JSFunctionSpec cb = JS_FN(name, func, 0, JSPROP_ENUMERATE);
_staticFuncs.push_back(cb);
return true;
}
bool Class::defineStaticProperty(const char *name, JSNative getter, JSNative setter) {
JSPropertySpec property = JS_PSGS(name, getter, setter, JSPROP_ENUMERATE);
_staticProperties.push_back(property);
return true;
}
bool Class::defineFinalizeFunction(JSFinalizeOp func) {
_finalizeOp = func;
return true;
}
// JSObject* Class::_createJSObject(const std::string &clsName, Class** outCls)
// {
// auto iter = __clsMap.find(clsName);
// if (iter == __clsMap.end())
// {
// *outCls = nullptr;
// return nullptr;
// }
//
// Class* thiz = iter->second;
// JS::RootedObject obj(__cx, _createJSObjectWithClass(thiz));
// *outCls = thiz;
// return obj;
// }
void Class::_createJSObjectWithClass(Class *cls, JS::MutableHandleObject outObj) {
JSObject *proto = cls->_proto != nullptr ? cls->_proto->_getJSObject() : nullptr;
JS::RootedObject jsProto(__cx, proto);
outObj.set(JS_NewObjectWithGivenProto(__cx, &cls->_jsCls, jsProto));
}
void Class::setContext(JSContext *cx) {
__cx = cx;
}
Object *Class::getProto() {
return _proto;
}
JSFinalizeOp Class::_getFinalizeCb() const {
return _finalizeOp;
}
void Class::cleanup() {
for (auto cls : __allClasses) {
cls->destroy();
}
se::ScriptEngine::getInstance()->addAfterCleanupHook([]() {
for (auto cls : __allClasses) {
delete cls;
}
__allClasses.clear();
});
}
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

View File

@@ -0,0 +1,157 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "Base.h"
namespace se {
class Object;
/**
* se::Class represents a definition of how to create a native binding object.
*/
class Class final {
public:
/**
* @brief Creates a class used for creating relevant native binding objects.
* @param[in] className A null-terminated UTF8 string containing the class's name.
* @param[in] obj The object that current class proto object attaches to. Should not be nullptr.
* @param[in] parentProto The parent proto object that current class inherits from. Passing nullptr means a new class has no parent.
* @param[in] ctor A callback to invoke when your constructor is used in a 'new' expression. Pass nullptr to use the default object constructor.
* @return A class instance used for creating relevant native binding objects.
* @note Don't need to delete the pointer return by this method, it's managed internally.
*/
static Class *create(const char *className, Object *obj, Object *parentProto, JSNative ctor);
static Class *create(const std::initializer_list<const char *> &classPath, se::Object *parent, Object *parentProto, JSNative ctor);
/**
* @brief Defines a member function with a callback. Each objects created by class will have this function property.
* @param[in] name A null-terminated UTF8 string containing the function name.
* @param[in] func A callback to invoke when the property is called as a function.
* @return true if succeed, otherwise false.
*/
bool defineFunction(const char *name, JSNative func);
/**
* @brief Defines a property with accessor callbacks. Each objects created by class will have this property.
* @param[in] name A null-terminated UTF8 string containing the property name.
* @param[in] getter A callback to invoke when the property is read.
* @param[in] setter A callback to invoke when the property is set.
* @return true if succeed, otherwise false.
*/
bool defineProperty(const char *name, JSNative getter, JSNative setter);
bool defineProperty(const std::initializer_list<const char *> &names, JSNative getter, JSNative setter);
/**
* @brief Defines a static function with a callback. Only JavaScript constructor object will have this function.
* @param[in] name A null-terminated UTF8 string containing the function name.
* @param[in] func A callback to invoke when the constructor's property is called as a function.
* @return true if succeed, otherwise false.
*/
bool defineStaticFunction(const char *name, JSNative func);
/**
* @brief Defines a static property with accessor callbacks. Only JavaScript constructor object will have this property.
* @param[in] name A null-terminated UTF8 string containing the property name.
* @param[in] getter A callback to invoke when the constructor's property is read.
* @param[in] setter A callback to invoke when the constructor's property is set.
* @return true if succeed, otherwise false.
*/
bool defineStaticProperty(const char *name, JSNative getter, JSNative setter);
/**
* @brief Defines the finalize function with a callback.
* @param[in] func The callback to invoke when a JavaScript object is garbage collected.
* @return true if succeed, otherwise false.
*/
bool defineFinalizeFunction(JSFinalizeOp func);
/**
* @brief Installs class to JavaScript VM.
* @return true if succeed, otherwise false.
* @note After this method, an object could be created by `var foo = new Foo();`.
*/
bool install();
/**
* @brief Gets the proto object of this class.
* @return The proto object of this class.
* @note Don't need to be released in user code.
*/
Object *getProto();
/**
* @brief Gets the class name.
* @return The class name.
*/
const char *getName() const { return _name; }
// Private API used in wrapper
JSFinalizeOp _getFinalizeCb() const;
//
private:
Class();
~Class();
bool init(const char *clsName, Object *obj, Object *parentProto, JSNative ctor);
void destroy();
// static JSObject* _createJSObject(const std::string &clsName, Class** outCls);
static void _createJSObjectWithClass(Class *cls, JS::MutableHandleObject outObj);
static void setContext(JSContext *cx);
static void cleanup();
static void onTraceCallback(JSTracer *trc, JSObject *obj);
const char *_name;
Object *_parent;
Object *_proto;
Object *_parentProto;
JSNative _ctor;
JSClass _jsCls;
JSClassOps _classOps;
ccstd::vector<JSFunctionSpec> _funcs;
ccstd::vector<JSFunctionSpec> _staticFuncs;
ccstd::vector<JSPropertySpec> _properties;
ccstd::vector<JSPropertySpec> _staticProperties;
JSFinalizeOp _finalizeOp;
friend class ScriptEngine;
friend class Object;
};
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

View File

@@ -0,0 +1,81 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "HelperMacros.h"
#if defined(RECORD_JSB_INVOKING)
namespace {
bool cmp(const std::pair<const char *, std::tuple<int, uint64_t>> &a, const std::pair<const char *, std::tuple<int, uint64_t>> &b) {
return std::get<1>(a.second) > std::get<1>(b.second);
}
unsigned int __jsbInvocationCount; // NOLINT(readability-identifier-naming)
ccstd::unordered_map<char const *, std::tuple<int, uint64_t>> __jsbFunctionInvokedRecords; // NOLINT(readability-identifier-naming)
} // namespace
JsbInvokeScopeT::JsbInvokeScopeT(const char *functionName) : _functionName(functionName) {
_start = std::chrono::high_resolution_clock::now();
__jsbInvocationCount++;
}
JsbInvokeScopeT::~JsbInvokeScopeT() {
auto &ref = __jsbFunctionInvokedRecords[_functionName];
std::get<0>(ref) += 1;
std::get<1>(ref) += std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - _start).count();
}
#endif
void printJSBInvokeAtFrame(int n) {
#if defined(RECORD_JSB_INVOKING)
static int cnt = 0;
cnt += 1;
if (cnt % n == 0) {
printJSBInvoke();
}
#endif
}
void clearRecordJSBInvoke() {
#if defined(RECORD_JSB_INVOKING)
__jsbInvocationCount = 0;
__jsbFunctionInvokedRecords.clear();
#endif
}
void printJSBInvoke() {
#if defined(RECORD_JSB_INVOKING)
static ccstd::vector<std::pair<const char *, std::tuple<int, uint64_t>>> pairs;
for (const auto &it : __jsbFunctionInvokedRecords) {
pairs.emplace_back(it); //NOLINT
}
std::sort(pairs.begin(), pairs.end(), cmp);
cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "Start print JSB function record info....... %d times", __jsbInvocationCount);
for (const auto &pair : pairs) {
cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "\t%s takes %.3lf ms, invoked %u times,", pair.first, std::get<1>(pair.second) / 1000000.0, std::get<0>(pair.second));
}
pairs.clear();
cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "End print JSB function record info.......\n");
#endif
}

View File

@@ -0,0 +1,249 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../ValueArrayPool.h"
#include "../config.h"
//#define RECORD_JSB_INVOKING
#ifndef CC_DEBUG
#undef RECORD_JSB_INVOKING
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#if defined(RECORD_JSB_INVOKING)
class JsbInvokeScopeT {
public:
JsbInvokeScopeT(const char *functionName);
~JsbInvokeScopeT();
private:
const char *_functionName;
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
};
#define JsbInvokeScope(arg) JsbInvokeScopeT invokeScope(arg); // NOLINT(readability-identifier-naming)
#else
// NOLINTNEXTLINE(readability-identifier-naming)
#define JsbInvokeScope(arg) \
do { \
} while (0)
#endif
template <typename T, typename STATE>
constexpr inline T *SE_THIS_OBJECT(STATE &s) { // NOLINT(readability-identifier-naming)
return reinterpret_cast<T *>(s.nativeThisObject());
}
template <typename T>
constexpr typename std::enable_if<std::is_enum<T>::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming)
return typeid(std::underlying_type_t<T>).name();
}
template <typename T>
constexpr typename std::enable_if<!std::is_enum<T>::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming)
return typeid(T).name();
}
void clearRecordJSBInvoke();
void printJSBInvoke();
void printJSBInvokeAtFrame(int n);
#define SAFE_INC_REF(obj) \
if (obj != nullptr) obj->incRef()
#define SAFE_DEC_REF(obj) \
if ((obj) != nullptr) { \
(obj)->decRef(); \
(obj) = nullptr; \
}
#define _SE(name) name##Registry
#define SE_DECLARE_FUNC(funcName) \
bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp)
#define SE_BIND_FUNC(funcName) \
bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \
JsbInvokeScope(#funcName); \
bool ret = false; \
JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \
JS::RootedObject _thizObj(_cx); \
_argv.computeThis(_cx, &_thizObj); \
bool needDeleteValueArray{false}; \
se::ValueArray &args = se::gValueArrayPool.get(argc, needDeleteValueArray); \
se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; \
se::internal::jsToSeArgs(_cx, argc, _argv, args); \
se::PrivateObjectBase *privateObject = static_cast<se::PrivateObjectBase *>(se::internal::getPrivate(_cx, _thizObj, 0)); \
se::Object *thisObject = reinterpret_cast<se::Object *>(se::internal::getPrivate(_cx, _thizObj, 1)); \
se::State state(thisObject, privateObject, args); \
ret = funcName(state); \
if (!ret) { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
se::internal::setReturnValue(_cx, state.rval(), _argv); \
return ret; \
}
#define SE_BIND_FUNC_FAST(funcName) \
bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \
JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \
JS::RootedObject _thizObj(_cx); \
_argv.computeThis(_cx, &_thizObj); \
auto *privateObject = static_cast<se::PrivateObjectBase *>(se::internal::SE_JS_GetPrivate(_thizObj, 0)); \
funcName(privateObject->getRaw()); \
_argv.rval().setUndefined(); \
return true; \
}
#define SE_DECLARE_FINALIZE_FUNC(funcName) \
void funcName##Registry(JSFreeOp *_fop, JSObject *_obj);
#define SE_BIND_FINALIZE_FUNC(funcName) \
void funcName##Registry(JSFreeOp *_fop, JSObject *_obj) { \
JsbInvokeScope(#funcName); \
se::PrivateObjectBase *privateObject = static_cast<se::PrivateObjectBase *>(se::internal::SE_JS_GetPrivate(_obj, 0)); \
se::Object *seObj = static_cast<se::Object *>(se::internal::SE_JS_GetPrivate(_obj, 1)); \
bool ret = false; \
if (privateObject == nullptr) \
return; \
se::State state(privateObject); \
ret = funcName(state); \
if (!ret) { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
if (seObj->isClearMappingInFinalizer() && privateObject != nullptr) { \
void *nativeObj = privateObject->getRaw(); \
auto iter = se::NativePtrToObjectMap::find(nativeObj); \
if (iter != se::NativePtrToObjectMap::end()) { \
se::NativePtrToObjectMap::erase(iter); \
} \
} \
seObj->decRef(); \
}
#define SE_BIND_CTOR(funcName, cls, finalizeCb) \
bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \
JsbInvokeScope(#funcName); \
bool ret = false; \
JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \
bool needDeleteValueArray{false}; \
se::ValueArray &args = se::gValueArrayPool.get(argc, needDeleteValueArray); \
se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; \
se::internal::jsToSeArgs(_cx, argc, _argv, args); \
se::Object *thisObject = se::Object::_createJSObjectForConstructor(cls, _argv); \
thisObject->_setFinalizeCallback(finalizeCb##Registry); \
_argv.rval().setObject(*thisObject->_getJSObject()); \
se::State state(thisObject, args); \
ret = funcName(state); \
if (ret) { \
se::Value _property; \
bool _found = false; \
_found = thisObject->getProperty("_ctor", &_property); \
if (_found) _property.toObject()->call(args, thisObject); \
} else { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
return ret; \
}
#define SE_BIND_PROP_GET_IMPL(funcName, postFix) \
bool funcName##postFix##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \
JsbInvokeScope(#funcName); \
bool ret = false; \
JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \
JS::RootedObject _thizObj(_cx); \
_argv.computeThis(_cx, &_thizObj); \
se::PrivateObjectBase *privateObject = static_cast<se::PrivateObjectBase *>(se::internal::getPrivate(_cx, _thizObj, 0)); \
se::Object *thisObject = reinterpret_cast<se::Object *>(se::internal::getPrivate(_cx, _thizObj, 1)); \
se::State state(thisObject, privateObject); \
ret = funcName(state); \
if (!ret) { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
se::internal::setReturnValue(_cx, state.rval(), _argv); \
return ret; \
}
#define SE_BIND_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, )
#define SE_BIND_FUNC_AS_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, _asGetter)
#define SE_BIND_PROP_SET_IMPL(funcName, postFix) \
bool funcName##postFix##Registry(JSContext *_cx, unsigned _argc, JS::Value *_vp) { \
JsbInvokeScope(#funcName); \
bool ret = false; \
JS::CallArgs _argv = JS::CallArgsFromVp(_argc, _vp); \
JS::RootedObject _thizObj(_cx); \
_argv.computeThis(_cx, &_thizObj); \
se::PrivateObjectBase *privateObject = static_cast<se::PrivateObjectBase *>(se::internal::getPrivate(_cx, _thizObj, 0)); \
se::Object *thisObject = reinterpret_cast<se::Object *>(se::internal::getPrivate(_cx, _thizObj, 1)); \
bool needDeleteValueArray{false}; \
se::ValueArray &args = se::gValueArrayPool.get(1, needDeleteValueArray); \
se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; \
se::Value &data{args[0]}; \
se::internal::jsToSeValue(_cx, _argv[0], &data); \
se::State state(thisObject, privateObject, args); \
ret = funcName(state); \
if (!ret) { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
return ret; \
}
#define SE_BIND_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, )
#define SE_BIND_FUNC_AS_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, _asSetter)
#define SE_TYPE_NAME(t) typeid(t).name()
#define SE_QUOTEME_(x) #x
#define SE_QUOTEME(x) SE_QUOTEME_(x)
#define SE_REPORT_ERROR(fmt, ...) \
SE_LOGD("ERROR (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__); \
JS_ReportErrorUTF8(se::ScriptEngine::getInstance()->_getContext(), fmt, ##__VA_ARGS__)
#if CC_DEBUG > 0
#define SE_ASSERT(cond, fmt, ...) \
do { \
if (!(cond)) { \
SE_LOGE("ASSERT (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__); \
assert(false); \
} \
} while (false)
#else
#define SE_ASSERT(cond, fmt, ...)
#endif // #if CC_DEBUG > 0
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

View File

@@ -0,0 +1,780 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "Object.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "../MappingUtils.h"
#include "Class.h"
#include "ScriptEngine.h"
#include "Utils.h"
namespace se {
std::unordered_map<Object *, void *> __objectMap; // Currently, the value `void*` is always nullptr
namespace {
JSContext *__cx = nullptr;
} // namespace
Object::Object()
: _privateObject(nullptr),
_cls(nullptr),
_finalizeCb(nullptr),
_rootCount(0) {
_currentVMId = ScriptEngine::getInstance()->getVMId();
}
Object::~Object() {
if (_cls == nullptr) {
unroot();
}
if (_rootCount > 0) {
unprotect();
}
auto iter = __objectMap.find(this);
if (iter != __objectMap.end()) {
__objectMap.erase(iter);
}
}
bool Object::init(Class *cls, JSObject *obj) {
_cls = cls;
_heap = obj;
assert(__objectMap.find(this) == __objectMap.end());
__objectMap.emplace(this, nullptr);
if (_cls == nullptr) {
root();
}
return true;
}
Object *Object::_createJSObject(Class *cls, JSObject *obj) {
Object *ret = ccnew Object();
if (!ret->init(cls, obj)) {
delete ret;
ret = nullptr;
}
return ret;
}
Object *Object::_createJSObjectForConstructor(Class *cls, const JS::CallArgs &args) {
Object *ret = ccnew Object();
JS::RootedObject obj(__cx, JS_NewObjectForConstructor(__cx, &cls->_jsCls, args));
if (!ret->init(cls, obj)) {
delete ret;
ret = nullptr;
}
return ret;
}
Object *Object::createPlainObject() {
Object *obj = Object::_createJSObject(nullptr, JS_NewPlainObject(__cx));
return obj;
}
Object *Object::createObjectWithClass(Class *cls) {
JS::RootedObject jsobj(__cx);
Class::_createJSObjectWithClass(cls, &jsobj);
Object *obj = Object::_createJSObject(cls, jsobj);
return obj;
}
Object *Object::getObjectWithPtr(void *ptr) {
Object *obj = nullptr;
auto iter = NativePtrToObjectMap::find(ptr);
if (iter != NativePtrToObjectMap::end()) {
obj = iter->second;
obj->incRef();
}
return obj;
}
Object *Object::createArrayObject(size_t length) {
JS::RootedObject jsobj(__cx, JS::NewArrayObject(__cx, length));
Object *obj = Object::_createJSObject(nullptr, jsobj);
return obj;
}
Object *Object::createArrayBufferObject(const void *data, size_t byteLength) {
Object *obj = nullptr;
if (byteLength > 0 && data != nullptr) {
mozilla::UniquePtr<uint8_t[], JS::FreePolicy> jsBuf(
js_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena, byteLength));
if (!jsBuf)
return nullptr;
memcpy(jsBuf.get(), data, byteLength);
JS::RootedObject jsobj(__cx, JS::NewArrayBufferWithContents(__cx, byteLength, jsBuf.get()));
if (jsobj) {
// If JS::NewArrayBufferWithContents returns non-null, the ownership of
// the data is transfered to obj, so we release the ownership here.
mozilla::Unused << jsBuf.release();
obj = Object::_createJSObject(nullptr, jsobj);
}
} else {
JS::RootedObject jsobj(__cx, JS::NewArrayBuffer(__cx, byteLength));
if (jsobj) {
obj = Object::_createJSObject(nullptr, jsobj);
}
}
return obj;
}
/* static */
Object *Object::createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData /* = nullptr*/) {
struct BackingStoreUserData {
BufferContentsFreeFunc freeFunc;
void *freeUserData;
size_t byteLength;
};
auto *userData = ccnew BackingStoreUserData();
userData->freeFunc = freeFunc;
userData->freeUserData = freeUserData;
userData->byteLength = byteLength;
Object *obj = nullptr;
JS::RootedObject jsobj(__cx, JS::NewExternalArrayBuffer(
__cx, byteLength, contents,
[](void *data, void *deleterData) {
auto *userData = reinterpret_cast<BackingStoreUserData *>(deleterData);
userData->freeFunc(data, userData->byteLength, userData->freeUserData);
delete userData;
},
userData));
if (jsobj) {
obj = Object::_createJSObject(nullptr, jsobj);
}
return obj;
}
Object *Object::createTypedArray(TypedArrayType type, const void *data, size_t byteLength) {
if (type == TypedArrayType::NONE) {
SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!");
return nullptr;
}
if (type == TypedArrayType::UINT8_CLAMPED) {
SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!");
return nullptr;
}
#define CREATE_TYPEDARRAY(_type_, _data, _byteLength, count) \
{ \
void *tmpData = nullptr; \
JS::RootedObject arr(__cx, JS_New##_type_##Array(__cx, (uint32_t)(count))); \
bool isShared = false; \
if (_data != nullptr) { \
JS::AutoCheckCannotGC nogc; \
tmpData = JS_Get##_type_##ArrayData(arr, &isShared, nogc); \
memcpy(tmpData, (const void *)_data, (_byteLength)); \
} \
Object *obj = Object::_createJSObject(nullptr, arr); \
return obj; \
}
switch (type) {
case TypedArrayType::INT8:
CREATE_TYPEDARRAY(Int8, data, byteLength, byteLength);
case TypedArrayType::INT16:
CREATE_TYPEDARRAY(Int16, data, byteLength, byteLength / 2);
case TypedArrayType::INT32:
CREATE_TYPEDARRAY(Int32, data, byteLength, byteLength / 4);
case TypedArrayType::UINT8:
CREATE_TYPEDARRAY(Uint8, data, byteLength, byteLength);
case TypedArrayType::UINT16:
CREATE_TYPEDARRAY(Uint16, data, byteLength, byteLength / 2);
case TypedArrayType::UINT32:
CREATE_TYPEDARRAY(Uint32, data, byteLength, byteLength / 4);
case TypedArrayType::FLOAT32:
CREATE_TYPEDARRAY(Float32, data, byteLength, byteLength / 4);
case TypedArrayType::FLOAT64:
CREATE_TYPEDARRAY(Float64, data, byteLength, byteLength / 8);
default:
assert(false); // Should never go here.
break;
}
return nullptr;
#undef CREATE_TYPEDARRAY
}
/* static */
Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj) {
return Object::createTypedArrayWithBuffer(type, obj, 0);
}
/* static */
Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset) {
size_t byteLength{0};
uint8_t *skip{nullptr};
obj->getTypedArrayData(&skip, &byteLength);
return Object::createTypedArrayWithBuffer(type, obj, offset, byteLength - offset);
}
/* static */
Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength) {
if (type == TypedArrayType::NONE) {
SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!");
return nullptr;
}
if (type == TypedArrayType::UINT8_CLAMPED) {
SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!");
return nullptr;
}
assert(obj->isArrayBuffer());
JS::RootedObject jsobj(__cx, obj->_getJSObject());
switch (type) {
case TypedArrayType::INT8: {
JS::RootedObject typeArray(__cx, JS_NewInt8ArrayWithBuffer(__cx, jsobj, offset, byteLength));
return Object::_createJSObject(nullptr, typeArray);
}
case TypedArrayType::INT16: {
JS::RootedObject typeArray(__cx, JS_NewInt16ArrayWithBuffer(__cx, jsobj, offset, byteLength / 2));
return Object::_createJSObject(nullptr, typeArray);
}
case TypedArrayType::INT32: {
JS::RootedObject typeArray(__cx, JS_NewInt32ArrayWithBuffer(__cx, jsobj, offset, byteLength / 4));
return Object::_createJSObject(nullptr, typeArray);
}
case TypedArrayType::UINT8: {
JS::RootedObject typeArray(__cx, JS_NewUint8ArrayWithBuffer(__cx, jsobj, offset, byteLength));
return Object::_createJSObject(nullptr, typeArray);
}
case TypedArrayType::UINT16: {
JS::RootedObject typeArray(__cx, JS_NewUint16ArrayWithBuffer(__cx, jsobj, offset, byteLength / 2));
return Object::_createJSObject(nullptr, typeArray);
}
case TypedArrayType::UINT32: {
JS::RootedObject typeArray(__cx, JS_NewUint32ArrayWithBuffer(__cx, jsobj, offset, byteLength / 4));
return Object::_createJSObject(nullptr, typeArray);
}
case TypedArrayType::FLOAT32: {
JS::RootedObject typeArray(__cx, JS_NewFloat32ArrayWithBuffer(__cx, jsobj, offset, byteLength / 4));
return Object::_createJSObject(nullptr, typeArray);
}
case TypedArrayType::FLOAT64: {
JS::RootedObject typeArray(__cx, JS_NewFloat64ArrayWithBuffer(__cx, jsobj, offset, byteLength / 8));
return Object::_createJSObject(nullptr, typeArray);
}
default:
assert(false); // Should never go here.
break;
}
return nullptr;
}
Object *Object::createUint8TypedArray(uint8_t *data, size_t dataCount) {
return createTypedArray(TypedArrayType::UINT8, data, dataCount);
}
Object *Object::createJSONObject(const std::string &jsonStr) {
Value strVal(jsonStr);
JS::RootedValue jsStr(__cx);
internal::seToJsValue(__cx, strVal, &jsStr);
JS::RootedValue jsObj(__cx);
JS::RootedString rootedStr(__cx, jsStr.toString());
Object *obj = nullptr;
if (JS_ParseJSON(__cx, rootedStr, &jsObj)) {
obj = Object::_createJSObject(nullptr, jsObj.toObjectOrNull());
}
return obj;
}
void Object::_setFinalizeCallback(JSFinalizeOp finalizeCb) {
_finalizeCb = finalizeCb;
}
bool Object::getProperty(const char *name, Value *data, bool cachePropertyName) {
assert(data != nullptr);
data->setUndefined();
JSObject *jsobj = _getJSObject();
if (jsobj == nullptr)
return false;
JS::RootedObject object(__cx, jsobj);
bool found = false;
bool ok = JS_HasProperty(__cx, object, name, &found);
if (!ok || !found) {
return false;
}
JS::RootedValue rcValue(__cx);
ok = JS_GetProperty(__cx, object, name, &rcValue);
if (ok && data) {
internal::jsToSeValue(__cx, rcValue, data);
}
return ok;
}
bool Object::setProperty(const char *name, const Value &v) {
JS::RootedObject object(__cx, _getJSObject());
JS::RootedValue value(__cx);
internal::seToJsValue(__cx, v, &value);
return JS_SetProperty(__cx, object, name, value);
}
bool Object::defineProperty(const char *name, JSNative getter, JSNative setter) {
JS::RootedObject jsObj(__cx, _getJSObject());
return JS_DefineProperty(__cx, jsObj, name, getter, setter, JSPROP_ENUMERATE);
}
bool Object::defineOwnProperty(const char *name, const se::Value &value, bool writable, bool enumerable, bool configurable) {
JS::RootedObject jsObj(__cx, _getJSObject());
JS::RootedValue jsVal(__cx);
internal::seToJsValue(__cx, value, &jsVal);
unsigned attrs = 0;
if (!writable) {
attrs |= JSPROP_READONLY;
}
if (enumerable) {
attrs |= JSPROP_ENUMERATE;
}
if (!configurable) {
attrs |= JSPROP_PERMANENT;
}
return JS_DefineProperty(__cx, jsObj, name, jsVal, attrs);
}
bool Object::call(const ValueArray &args, Object *thisObject, Value *rval /* = nullptr*/) {
assert(isFunction());
JS::RootedValueVector jsarr(__cx);
jsarr.reserve(args.size());
internal::seToJsArgs(__cx, args, &jsarr);
JS::RootedObject contextObject(__cx);
if (thisObject != nullptr) {
contextObject.set(thisObject->_getJSObject());
}
JSObject *funcObj = _getJSObject();
JS::RootedValue func(__cx, JS::ObjectValue(*funcObj));
JS::RootedValue rcValue(__cx);
JSAutoRealm autoRealm(__cx, func.toObjectOrNull());
bool ok = JS_CallFunctionValue(__cx, contextObject, func, jsarr, &rcValue);
if (ok) {
if (rval != nullptr)
internal::jsToSeValue(__cx, rcValue, rval);
} else {
se::ScriptEngine::getInstance()->clearException();
}
return ok;
}
bool Object::defineFunction(const char *funcName, JSNative func) {
JS::RootedObject object(__cx, _getJSObject());
JSFunction *jsFunc = JS_DefineFunction(__cx, object, funcName, func, 0, JSPROP_ENUMERATE);
return jsFunc != nullptr;
}
bool Object::getArrayLength(uint32_t *length) const {
assert(length != nullptr);
if (!isArray())
return false;
JS::RootedObject object(__cx, _getJSObject());
if (JS::GetArrayLength(__cx, object, length))
return true;
*length = 0;
return false;
}
bool Object::getArrayElement(uint32_t index, Value *data) const {
assert(data != nullptr);
if (!isArray())
return false;
JS::RootedObject object(__cx, _getJSObject());
JS::RootedValue rcValue(__cx);
if (JS_GetElement(__cx, object, index, &rcValue)) {
internal::jsToSeValue(__cx, rcValue, data);
return true;
}
data->setUndefined();
return false;
}
bool Object::setArrayElement(uint32_t index, const Value &data) {
if (!isArray())
return false;
JS::RootedValue jsval(__cx);
internal::seToJsValue(__cx, data, &jsval);
JS::RootedObject thisObj(__cx, _getJSObject());
return JS_SetElement(__cx, thisObj, index, jsval);
}
bool Object::isFunction() const {
return JS_ObjectIsFunction(_getJSObject());
}
bool Object::_isNativeFunction(JSNative func) const {
JSObject *obj = _getJSObject();
return JS_ObjectIsFunction(obj) && JS_IsNativeFunction(obj, func);
}
bool Object::isTypedArray() const {
return JS_IsTypedArrayObject(_getJSObject());
}
Object::TypedArrayType Object::getTypedArrayType() const {
TypedArrayType ret = TypedArrayType::NONE;
JSObject *obj = _getJSObject();
if (JS_IsInt8Array(obj))
ret = TypedArrayType::INT8;
else if (JS_IsInt16Array(obj))
ret = TypedArrayType::INT16;
else if (JS_IsInt32Array(obj))
ret = TypedArrayType::INT32;
else if (JS_IsUint8Array(obj))
ret = TypedArrayType::UINT8;
else if (JS_IsUint8ClampedArray(obj))
ret = TypedArrayType::UINT8_CLAMPED;
else if (JS_IsUint16Array(obj))
ret = TypedArrayType::UINT16;
else if (JS_IsUint32Array(obj))
ret = TypedArrayType::UINT32;
else if (JS_IsFloat32Array(obj))
ret = TypedArrayType::FLOAT32;
else if (JS_IsFloat64Array(obj))
ret = TypedArrayType::FLOAT64;
return ret;
}
bool Object::getTypedArrayData(uint8_t **ptr, size_t *length) const {
assert(JS_IsArrayBufferViewObject(_getJSObject()));
bool isShared = false;
JS::AutoCheckCannotGC nogc;
if (ptr != nullptr) {
*ptr = (uint8_t *)JS_GetArrayBufferViewData(_getJSObject(), &isShared, nogc);
}
if (length != nullptr) {
*length = JS_GetArrayBufferViewByteLength(_getJSObject());
}
return (*ptr != nullptr);
}
bool Object::isArray() const {
JS::RootedValue value(__cx, JS::ObjectValue(*_getJSObject()));
bool isArray = false;
return JS::IsArrayObject(__cx, value, &isArray) && isArray;
}
bool Object::isArrayBuffer() const {
return JS::IsArrayBufferObject(_getJSObject());
}
bool Object::getArrayBufferData(uint8_t **ptr, size_t *length) const {
assert(isArrayBuffer());
bool isShared = false;
JS::AutoCheckCannotGC nogc;
if (ptr != nullptr) {
*ptr = (uint8_t *)JS::GetArrayBufferData(_getJSObject(), &isShared, nogc);
}
if (length != nullptr) {
*length = JS::GetArrayBufferByteLength(_getJSObject());
}
return (*ptr != nullptr);
}
bool Object::getAllKeys(ccstd::vector<std::string> *allKeys) const {
assert(allKeys != nullptr);
JS::RootedObject jsobj(__cx, _getJSObject());
JS::Rooted<JS::IdVector> props(__cx, JS::IdVector(__cx));
if (!JS_Enumerate(__cx, jsobj, &props))
return false;
ccstd::vector<std::string> keys;
for (size_t i = 0, length = props.length(); i < length; ++i) {
JS::RootedId id(__cx, props[i]);
JS::RootedValue keyVal(__cx);
JS_IdToValue(__cx, id, &keyVal);
if (JSID_IS_STRING(id)) {
JS::RootedString rootedKeyVal(__cx, keyVal.toString());
allKeys->push_back(internal::jsToStdString(__cx, rootedKeyVal));
} else if (JSID_IS_INT(id)) {
char buf[50] = {0};
snprintf(buf, sizeof(buf), "%d", keyVal.toInt32());
allKeys->push_back(buf);
} else {
assert(false);
}
}
return true;
}
void Object::setPrivateObject(PrivateObjectBase *data) {
assert(_privateObject == nullptr);
#if CC_DEBUG
//assert(NativePtrToObjectMap::find(data->getRaw()) == NativePtrToObjectMap::end());
auto it = NativePtrToObjectMap::find(data->getRaw());
if (it != NativePtrToObjectMap::end()) {
auto *pri = it->second->getPrivateObject();
SE_LOGE("Already exists object %s/[%s], trying to add %s/[%s]\n", pri->getName(), typeid(*pri).name(), data->getName(), typeid(*data).name());
#if JSB_TRACK_OBJECT_CREATION
SE_LOGE(" previous object created at %s\n", it->second->_objectCreationStackFrame.c_str());
#endif
assert(false);
}
#endif
JS::RootedObject obj(__cx, _getJSObject());
internal::setPrivate(__cx, obj, data, this, &_internalData, _finalizeCb); //TODO(cjh): how to use _internalData?
NativePtrToObjectMap::emplace(data->getRaw(), this);
_privateObject = data;
}
PrivateObjectBase *Object::getPrivateObject() const {
if (_privateObject == nullptr) {
JS::RootedObject obj(__cx, _getJSObject());
const_cast<Object *>(this)->_privateObject = static_cast<PrivateObjectBase *>(internal::getPrivate(__cx, obj, 0));
}
return _privateObject;
}
void Object::clearPrivateData(bool clearMapping) {
if (_privateObject != nullptr) {
if (clearMapping) {
NativePtrToObjectMap::erase(_privateObject->getRaw());
}
JS::RootedObject obj(__cx, _getJSObject());
internal::clearPrivate(__cx, obj);
delete _privateObject;
_privateObject = nullptr;
}
}
void Object::setContext(JSContext *cx) {
__cx = cx;
}
// static
void Object::cleanup() {
for (const auto &e : __objectMap) {
e.first->reset();
}
ScriptEngine::getInstance()->addAfterCleanupHook([]() {
__objectMap.clear();
const auto &instance = NativePtrToObjectMap::instance();
for (const auto &e : instance) {
e.second->decRef();
}
NativePtrToObjectMap::clear();
__cx = nullptr;
});
}
JSObject *Object::_getJSObject() const {
return isRooted() ? _root.get() : _heap.get();
}
void Object::root() {
if (_rootCount == 0) {
protect();
}
++_rootCount;
}
void Object::unroot() {
if (_rootCount > 0) {
--_rootCount;
if (_rootCount == 0) {
unprotect();
}
}
}
void Object::protect() {
assert(_root == nullptr);
assert(_heap != JS::SafelyInitialized<JSObject *>::create());
_root.init(__cx, _heap);
_heap.set(JS::SafelyInitialized<JSObject *>::create());
}
void Object::unprotect() {
if (!_root.initialized())
return;
assert(_currentVMId == ScriptEngine::getInstance()->getVMId());
assert(_heap == JS::SafelyInitialized<JSObject *>::create());
_heap = _root.get();
_root.reset();
}
void Object::reset() {
_root.reset();
_heap = JS::SafelyInitialized<JSObject *>::create();
}
void Object::onTraceCallback(JSTracer *trc, void *data) {
auto *thiz = reinterpret_cast<Object *>(data);
thiz->trace(trc);
}
/* Tracing makes no sense in the rooted case, because JS::PersistentRooted
* already takes care of that. */
void Object::trace(JSTracer *tracer) {
assert(!isRooted());
JS::TraceEdge(tracer, &_heap, "seobj tracing");
}
/* If not tracing, then you must call this method during GC in order to
* update the object's location if it was moved, or null it out if it was
* finalized. If the object was finalized, returns true. */
bool Object::updateAfterGC(JSTracer *trc, void *data) {
assert(!isRooted());
bool isGarbageCollected = false;
internal::PrivateData *internalData = nullptr;
JSObject *oldPtr = _heap.unbarrieredGet();
if (_heap.unbarrieredGet() != nullptr)
JS_UpdateWeakPointerAfterGC(trc, &_heap);
JSObject *newPtr = _heap.unbarrieredGet();
// IDEA: test to see ggc
if (oldPtr != nullptr && newPtr != nullptr) {
assert(oldPtr == newPtr);
}
isGarbageCollected = (newPtr == nullptr);
if (isGarbageCollected && internalData != nullptr) {
free(internalData);
}
return isGarbageCollected;
}
bool Object::isRooted() const {
return _rootCount > 0;
}
bool Object::strictEquals(Object *o) const {
JSObject *thisObj = _getJSObject();
JSObject *oThisObj = o->_getJSObject();
if ((thisObj == nullptr || oThisObj == nullptr) && thisObj != oThisObj)
return false;
assert(thisObj);
assert(oThisObj);
JS::RootedValue v1(__cx, JS::ObjectValue(*_getJSObject()));
JS::RootedValue v2(__cx, JS::ObjectValue(*o->_getJSObject()));
bool same = false;
bool ok = JS::SameValue(__cx, v1, v2, &same);
return ok && same;
}
bool Object::attachObject(Object *obj) {
assert(obj);
Object *global = ScriptEngine::getInstance()->getGlobalObject();
Value jsbVal;
if (!global->getProperty("jsb", &jsbVal))
return false;
Object *jsbObj = jsbVal.toObject();
Value func;
if (!jsbObj->getProperty("registerNativeRef", &func))
return false;
ValueArray args;
args.push_back(Value(this));
args.push_back(Value(obj));
func.toObject()->call(args, global);
return true;
}
bool Object::detachObject(Object *obj) {
assert(obj);
Object *global = ScriptEngine::getInstance()->getGlobalObject();
Value jsbVal;
if (!global->getProperty("jsb", &jsbVal))
return false;
Object *jsbObj = jsbVal.toObject();
Value func;
if (!jsbObj->getProperty("unregisterNativeRef", &func))
return false;
ValueArray args;
args.push_back(Value(this));
args.push_back(Value(obj));
func.toObject()->call(args, global);
return true;
}
std::string Object::toString() const {
std::string ret;
if (isFunction() || isArray() || isTypedArray()) {
JS::RootedValue val(__cx, JS::ObjectOrNullValue(_getJSObject()));
internal::forceConvertJsValueToStdString(__cx, val, &ret);
} else if (isArrayBuffer()) {
ret = "[object ArrayBuffer]";
} else {
ret = "[object Object]";
}
return ret;
}
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

View File

@@ -0,0 +1,458 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "../RefCounter.h"
#include "../Value.h"
#include "Base.h"
namespace se {
class Class;
namespace internal {
struct PrivateData;
}
/**
* se::Object represents JavaScript Object.
*/
class Object final : public RefCounter {
public:
/**
* @brief Creates a JavaScript Object like `{} or new Object()`.
* @return A JavaScript Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createPlainObject();
/**
* @brief Creates a JavaScript Array Object like `[] or new Array()`.
* @param[in] length The initical length of array.
* @return A JavaScript Array Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createArrayObject(size_t length);
/**
* @brief Creates a JavaScript Typed Array Object with uint8 format from an existing pointer.
* @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
* @deprecated This method is deprecated, please use `se::Object::createTypedArray` instead.
*/
SE_DEPRECATED_ATTRIBUTE static Object *createUint8TypedArray(uint8_t *data, size_t byteLength);
enum class TypedArrayType {
NONE,
INT8,
INT16,
INT32,
UINT8,
UINT8_CLAMPED,
UINT16,
UINT32,
FLOAT32,
FLOAT64
};
/**
* @brief Creates a JavaScript Typed Array Object with specified format from an existing pointer,
if provide a null pointer,then will create a empty JavaScript Typed Array Object.
* @param[in] type The format of typed array.
* @param[in] data A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createTypedArray(TypedArrayType type, const void *data, size_t byteLength);
/**
* @brief Creates a JavaScript Typed Array Object with a se::Object, which is a ArrayBuffer,
if provide a null pointer,then will create a empty JavaScript Typed Array Object.
* @param[in] type The format of typed array.
* @param[in] obj A ArrayBuffer to TypedArray.
* @param[in] offset Offset of ArrayBuffer to create with.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object which refers to the ArrayBuffer Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj);
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset);
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength);
/**
* @brief Creates a JavaScript Array Buffer object from an existing pointer.
* @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A Array Buffer Object whose backing store is the same as the one pointed to data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createArrayBufferObject(const void *data, size_t byteLength);
using BufferContentsFreeFunc = void (*)(void *contents, size_t byteLength, void *userData);
static Object *createExternalArrayBufferObject(void *contents, size_t nbytes, BufferContentsFreeFunc freeFunc, void *freeUserData = nullptr);
/**
* @brief Creates a JavaScript Object from a JSON formatted string.
* @param[in] jsonStr The utf-8 string containing the JSON string to be parsed.
* @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid.
* @note The return value (non-null) has to be released manually.
*/
static Object *createJSONObject(const std::string &jsonStr);
/**
* @brief Creates a JavaScript Native Binding Object from an existing se::Class instance.
* @param[in] cls The se::Class instance which stores native callback informations.
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createObjectWithClass(Class *cls);
/**
* @brief Gets a se::Object from an existing native object pointer.
* @param[in] ptr The native object pointer associated with the se::Object
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *getObjectWithPtr(void *ptr);
/**
* @brief Gets a property from an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[out] value The property's value if object has the property, otherwise the undefined value.
* @return true if object has the property, otherwise false.
*/
inline bool getProperty(const char *name, Value *data) {
return getProperty(name, data, false);
}
bool getProperty(const char *name, Value *data, bool cachePropertyName);
inline bool getProperty(const std::string &name, Value *value) {
return getProperty(name.c_str(), value);
}
/**
* @brief Sets a property to an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] value A value to be used as the property's value.
* @return true if the property is set successfully, otherwise false.
*/
bool setProperty(const char *name, const Value &value);
inline bool setProperty(const std::string &name, const Value &value) {
return setProperty(name.c_str(), value);
}
/**
* @brief Defines a property with native accessor callbacks for an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] getter The native callback for getter.
* @param[in] setter The native callback for setter.
* @return true if succeed, otherwise false.
*/
bool defineProperty(const char *name, JSNative getter, JSNative setter);
bool defineOwnProperty(const char *name, const se::Value &value, bool writable = true, bool enumerable = true, bool configurable = true);
/**
* @brief Defines a function with a native callback for an object.
* @param[in] funcName A utf-8 string containing the function name.
* @param[in] func The native callback triggered by JavaScript code.
* @return true if succeed, otherwise false.
*/
bool defineFunction(const char *funcName, JSNative func);
/**
* @brief Tests whether an object can be called as a function.
* @return true if object can be called as a function, otherwise false.
*/
bool isFunction() const;
/**
* @brief Calls an object as a function.
* @param[in] args A se::Value array of arguments to pass to the function. Pass se::EmptyValueArray if argumentCount is 0.
* @param[in] thisObject The object to use as "this," or NULL to use the global object as "this."
* @param[out] rval The se::Value that results from calling object as a function, passing nullptr if return value is ignored.
* @return true if object is a function and there isn't any errors, otherwise false.
*/
bool call(const ValueArray &args, Object *thisObject, Value *rval = nullptr);
/**
* @brief Tests whether an object is an array.
* @return true if object is an array, otherwise false.
*/
bool isArray() const;
/**
* @brief Gets array length of an array object.
* @param[out] length The array length to be stored. It's set to 0 if there is an error.
* @return true if succeed, otherwise false.
*/
bool getArrayLength(uint32_t *length) const;
/**
* @brief Gets an element from an array object by numeric index.
* @param[in] index An integer value for index.
* @param[out] data The se::Value to be stored for the element in certain index.
* @return true if succeed, otherwise false.
*/
bool getArrayElement(uint32_t index, Value *data) const;
/**
* @brief Sets an element to an array object by numeric index.
* @param[in] index An integer value for index.
* @param[in] data The se::Value to be set to array with certain index.
* @return true if succeed, otherwise false.
*/
bool setArrayElement(uint32_t index, const Value &data);
/** @brief Tests whether an object is a typed array.
* @return true if object is a typed array, otherwise false.
*/
bool isTypedArray() const;
/**
* @brief Gets the type of a typed array object.
* @return The type of a typed array object.
*/
TypedArrayType getTypedArrayType() const;
/**
* @brief Gets backing store of a typed array object.
* @param[out] ptr A temporary pointer to the backing store of a JavaScript Typed Array object.
* @param[out] length The byte length of a JavaScript Typed Array object.
* @return true if succeed, otherwise false.
*/
bool getTypedArrayData(uint8_t **ptr, size_t *length) const;
/**
* @brief Tests whether an object is an array buffer object.
* @return true if object is an array buffer object, otherwise false.
*/
bool isArrayBuffer() const;
/**
* @brief Gets buffer data of an array buffer object.
* @param[out] ptr A pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object.
* @param[out] length The number of bytes in a JavaScript data object.
* @return true if succeed, otherwise false.
*/
bool getArrayBufferData(uint8_t **ptr, size_t *length) const;
/**
* @brief Gets all property names of an object.
* @param[out] allKeys A string vector to store all property names.
* @return true if succeed, otherwise false.
*/
bool getAllKeys(ccstd::vector<std::string> *allKeys) const;
void setPrivateObject(PrivateObjectBase *data);
PrivateObjectBase *getPrivateObject() const;
/**
* @brief Gets an object's private data.
* @return A void* that is the object's private data, if the object has private data, otherwise nullptr.
*/
inline void *getPrivateData() const {
return _privateObject ? _privateObject->getRaw() : nullptr;
}
/**
* @brief Sets a pointer to private data on an object.
* @param[in] data A void* to set as the object's private data.
* @note This method will associate private data with se::Object by std::unordered_map::emplace.
* It's used for search a se::Object via a void* private data.
*/
template <typename T>
inline void setPrivateData(T *data) {
static_assert(!std::is_void<T>::value, "void * is not allowed for private data");
setPrivateObject(se::make_shared_private_object(data));
}
template <typename T>
inline T *getTypedPrivateData() const {
return reinterpret_cast<T *>(getPrivateData());
}
/**
* @brief Clears private data of an object.
* @param clearMapping Whether to clear the mapping of native object & se::Object.
*/
void clearPrivateData(bool clearMapping = true);
/**
* @brief Sets whether to clear the mapping of native object & se::Object in finalizer
*/
inline void setClearMappingInFinalizer(bool v) { _clearMappingInFinalizer = v; }
inline bool isClearMappingInFinalizer() const { return _clearMappingInFinalizer; }
/**
* @brief Roots an object from garbage collection.
* @note Use this method when you want to store an object in a global or on the heap, where the garbage collector will not be able to discover your reference to it.
* An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void root();
/**
* @brief Unroots an object from garbage collection.
* @note An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void unroot();
/**
* @brief Tests whether an object is rooted.
* @return true if it has been already rooted, otherwise false.
*/
bool isRooted() const;
/**
* @brief Tests whether two objects are strict equal, as compared by the JS === operator.
* @param[in] o The object to be tested with this object.
* @return true if the two values are strict equal, otherwise false.
*/
bool strictEquals(Object *o) const;
/**
* @brief Attaches an object to current object.
* @param[in] obj The object to be attached.
* @return true if succeed, otherwise false.
* @note This method will set `obj` as a property of current object, therefore the lifecycle of child object will depend on current object,
* which means `obj` may be garbage collected only after current object is garbage collected.
* It's normally used in binding a native callback method. For example:
```javascript
var self = this;
someObject.setCallback(function(){}, self);
```
```c++
static bool SomeObject_setCallback(se::State& s)
{
SomeObject* cobj = (SomeObject*)s.nativeThisObject();
const auto& args = s.args();
size_t argc = args.size();
if (argc == 2) {
std::function<void()> arg0;
do {
if (args[0].isObject() && args[0].toObject()->isFunction())
{
se::Value jsThis(args[1]);
se::Value jsFunc(args[0]);
jsThis.toObject()->attachObject(jsFunc.toObject());
auto lambda = [=]() -> void {
...
// Call jsFunc stuff...
...
};
arg0 = lambda;
}
else
{
arg0 = nullptr;
}
} while(false);
SE_PRECONDITION2(ok, false, "Error processing arguments");
cobj->setCallback(arg0);
return true;
}
return false;
}
```
*/
bool attachObject(Object *obj);
/**
* @brief Detaches an object from current object.
* @param[in] obj The object to be detached.
* @return true if succeed, otherwise false.
* @note The attached object will not be released if current object is not garbage collected.
*/
bool detachObject(Object *obj);
/**
* @brief Returns the string for describing current object.
* @return The string for describing current object.
*/
std::string toString() const;
// Private API used in wrapper
static Object *_createJSObject(Class *cls, JSObject *obj);
static Object *_createJSObjectForConstructor(Class *cls, const JS::CallArgs &args);
void _setFinalizeCallback(JSFinalizeOp finalizeCb);
bool _isNativeFunction(JSNative func) const;
JSObject *_getJSObject() const;
Class *_getClass() const { return _cls; }
//
private:
Object();
bool init(Class *cls, JSObject *obj);
virtual ~Object();
static void setContext(JSContext *cx);
static void cleanup();
static void onTraceCallback(JSTracer *trc, void *data);
void trace(JSTracer *tracer);
bool updateAfterGC(JSTracer *trc, void *data);
void protect();
void unprotect();
void reset();
JS::Heap<JSObject *> _heap; /* should be untouched if in rooted mode */
JS::PersistentRootedObject _root; /* should be null if not in rooted mode */
PrivateObjectBase *_privateObject{nullptr};
internal::PrivateData *_internalData{nullptr};
Class *_cls{nullptr};
JSFinalizeOp _finalizeCb{nullptr};
uint32_t _rootCount{0};
uint32_t _currentVMId{0};
bool _clearMappingInFinalizer{true};
friend class ScriptEngine;
friend class Class;
};
extern std::unordered_map<Object *, void *> __objectMap; // Currently, the value `void*` is always nullptr
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,347 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "Base.h"
namespace se {
class Object;
class Class;
class Value;
/**
* A stack-allocated class that governs a number of local handles.
* It's only implemented for v8 wrapper now.
* Other script engine wrappers have empty implementation for this class.
* It's used at the beginning of executing any wrapper API.
*/
class AutoHandleScope {
public:
AutoHandleScope();
~AutoHandleScope();
};
/**
* ScriptEngine is a sington which represents a context of JavaScript VM.
*/
class ScriptEngine final {
public:
/**
* @brief Gets or creates the instance of script engine.
* @return The script engine instance.
*/
static ScriptEngine *getInstance();
/**
* @brief Destroys the instance of script engine.
*/
static void destroyInstance();
/**
* @brief Gets the global object of JavaScript VM.
* @return The se::Object stores the global JavaScript object.
*/
Object *getGlobalObject();
typedef bool (*RegisterCallback)(Object *);
/**
* @brief Adds a callback for registering a native binding module.
* @param[in] cb A callback for registering a native binding module.
* @note This method just add a callback to a vector, callbacks is invoked in `start` method.
*/
void addRegisterCallback(RegisterCallback cb);
/**
* @brief Adds a callback for registering a native binding module, which will not be removed by ScriptEngine::cleanup.
* @param[in] cb A callback for registering a native binding module.
* @note This method just add a callback to a vector, callbacks is invoked in `start` method.
*/
void addPermanentRegisterCallback(RegisterCallback cb);
/**
* @brief Starts the script engine.
* @return true if succeed, otherwise false.
* @note This method will invoke all callbacks of native binding modules by the order of registration.
*/
bool start();
/**
* @brief Initializes script engine.
* @return true if succeed, otherwise false.
* @note This method will create JavaScript context and global object.
*/
bool init();
/**
* @brief Adds a hook function before initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeInitHook(const std::function<void()> &hook);
/**
* @brief Adds a hook function after initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterInitHook(const std::function<void()> &hook);
/**
* @brief Cleanups script engine.
* @note This method will removes all objects in JavaScript VM even whose are rooted, then shutdown JavaScript VMf.
*/
void cleanup();
/**
* @brief Adds a hook function before cleanuping script engine.
* @param[in] hook A hook function to be invoked before cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeCleanupHook(const std::function<void()> &hook);
/**
* @brief Adds a hook function after cleanuping script engine.
* @param[in] hook A hook function to be invoked after cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterCleanupHook(const std::function<void()> &hook);
/**
* @brief Executes a utf-8 string buffer which contains JavaScript code.
* @param[in] scriptStr A utf-8 string buffer, if it isn't null-terminated, parameter `length` should be assigned and > 0.
* @param[in] length The length of parameter `scriptStr`, it will be set to string length internally if passing < 0 and parameter `scriptStr` is null-terminated.
* @param[in] rval The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @param[in] fileName A string containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information.
* @return true if succeed, otherwise false.
*/
bool evalString(const char *scriptStr, ssize_t length = -1, Value *rval = nullptr, const char *fileName = nullptr);
/**
* @brief Compile script file into v8::ScriptCompiler::CachedData and save to file.
* @param[in] path The path of script file.
* @param[in] pathBc The location where bytecode file should be written to. The path should be ends with ".bc", which indicates a bytecode file.
* @return true if succeed, otherwise false.
*/
bool saveByteCodeToFile(const std::string &path, const std::string &pathBc) {
assert(false);
return false;
} //cjh
/**
* @brief Grab a snapshot of the current JavaScript execution stack.
* @return current stack trace string
*/
std::string getCurrentStackTrace() { return ""; } //cjh
/**
* Delegate class for file operation
*/
class FileOperationDelegate {
public:
FileOperationDelegate()
: onGetDataFromFile(nullptr),
onGetStringFromFile(nullptr),
onCheckFileExist(nullptr),
onGetFullPath(nullptr) {}
bool isValid() const {
return onGetDataFromFile != nullptr && onGetStringFromFile != nullptr && onCheckFileExist != nullptr && onGetFullPath != nullptr;
}
// path, buffer, buffer size
std::function<void(const std::string &, const std::function<void(const uint8_t *, size_t)> &)> onGetDataFromFile;
// path, return file string content.
std::function<std::string(const std::string &)> onGetStringFromFile;
// path
std::function<bool(const std::string &)> onCheckFileExist;
// path, return full path
std::function<std::string(const std::string &)> onGetFullPath;
};
/**
* @brief Sets the delegate for file operation.
* @param delegate[in] The delegate instance for file operation.
*/
void setFileOperationDelegate(const FileOperationDelegate &delegate);
/**
* @brief Gets the delegate for file operation.
* @return The delegate for file operation
*/
const FileOperationDelegate &getFileOperationDelegate() const;
/**
* @brief Executes a file which contains JavaScript code.
* @param[in] path Script file path.
* @param[in] rval The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @return true if succeed, otherwise false.
*/
bool runScript(const std::string &path, Value *rval = nullptr);
/**
* @brief Tests whether script engine is doing garbage collection.
* @return true if it's in garbage collection, otherwise false.
*/
bool isGarbageCollecting();
/**
* @brief Performs a JavaScript garbage collection.
*/
void garbageCollect() { JS_GC(_cx); }
/**
* @brief Tests whether script engine is being cleaned up.
* @return true if it's in cleaning up, otherwise false.
*/
bool isInCleanup() { return _isInCleanup; }
/**
* @brief Tests whether script engine is valid.
* @return true if it's valid, otherwise false.
*/
bool isValid() { return _isValid; }
/**
* @brief Throw JS exception
*/
void throwException(const std::string &errorMessage) { assert(false); } //cjh
/**
* @brief Clears all exceptions.
*/
void clearException();
using ExceptionCallback = std::function<void(const char *, const char *, const char *)>; // location, message, stack
/**
* @brief Sets the callback function while an exception is fired.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setExceptionCallback(const ExceptionCallback &cb);
/**
* @brief Sets the callback function while an exception is fired in JS.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setJSExceptionCallback(const ExceptionCallback &cb);
/**
* @brief Gets the start time of script engine.
* @return The start time of script engine.
*/
const std::chrono::steady_clock::time_point &getStartTime() const { return _startTime; }
/**
* @brief Enables JavaScript debugger
* @param[in] serverAddr The address of debugger server.
* @param[in] port The port of debugger server will use.
* @param[in] isWait Whether wait debugger attach when loading.
*/
void enableDebugger(const std::string &serverAddr, uint32_t port, bool isWait = false);
/**
* @brief Tests whether JavaScript debugger is enabled
* @return true if JavaScript debugger is enabled, otherwise false.
*/
bool isDebuggerEnabled() const;
/**
* @brief Main loop update trigger, it's need to invoked in main thread every frame.
*/
void mainLoopUpdate();
/**
* @brief Gets script virtual machine instance ID. Default value is 1, increase by 1 if `init` is invoked.
*/
uint32_t getVMId() const { return _vmId; }
/**
* @brief Fast version of call script function, faster than Object::call
*/
bool callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval = nullptr);
/**
* @brief Handle all exceptions throwed by promise
*/
void handlePromiseExceptions();
// Private API used in wrapper
JSContext *_getContext() { return _cx; }
void _setGarbageCollecting(bool isGarbageCollecting);
void _debugProcessInput(const std::string &str);
//
private:
ScriptEngine();
~ScriptEngine();
static void onWeakPointerCompartmentCallback(JSTracer *trc, JS::Compartment *comp, void *data);
static void onWeakPointerZoneGroupCallback(JSTracer *trc, void *data);
bool getScript(const std::string &path, JS::MutableHandleScript script);
bool compileScript(const std::string &path, JS::MutableHandleScript script);
JSContext *_cx;
JS::Realm *_oldCompartment;
Object *_globalObj;
Object *_debugGlobalObj;
FileOperationDelegate _fileOperationDelegate;
ccstd::vector<RegisterCallback> _registerCallbackArray;
ccstd::vector<RegisterCallback> _permRegisterCallbackArray;
std::chrono::steady_clock::time_point _startTime;
ccstd::vector<std::function<void()>> _beforeInitHookArray;
ccstd::vector<std::function<void()>> _afterInitHookArray;
ccstd::vector<std::function<void()>> _beforeCleanupHookArray;
ccstd::vector<std::function<void()>> _afterCleanupHookArray;
ExceptionCallback _exceptionCallback;
// name ~> JSScript map
std::unordered_map<std::string, JS::PersistentRootedScript *> _filenameScriptMap;
std::string _debuggerServerAddr;
uint32_t _debuggerServerPort;
uint32_t _vmId;
bool _isGarbageCollecting;
bool _isValid;
bool _isInCleanup;
bool _isErrorHandleWorking;
};
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

View File

@@ -0,0 +1,34 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../State.h"
#include "../Value.h"
#include "Class.h"
#include "HelperMacros.h"
#include "Object.h"
#include "ScriptEngine.h"
#include "Utils.h"

View File

@@ -0,0 +1,207 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "Utils.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "Class.h"
#include "Object.h"
#include "ScriptEngine.h"
namespace se {
namespace internal {
void *SE_JS_GetPrivate(JSObject *obj, uint32_t slot) {
assert(slot >= 0 && slot < 2);
const auto &v = JS::GetReservedSlot(obj, slot);
return v.isNullOrUndefined() ? nullptr : v.toPrivate();
}
void SE_JS_SetPrivate(JSObject *obj, uint32_t slot, void *data) {
assert(slot >= 0 && slot < 2);
JS::SetReservedSlot(obj, slot, JS::PrivateValue(data));
}
bool isJSBClass(JSObject *obj) {
const JSClass *cls = JS::GetClass(obj);
return (cls->flags & (JSCLASS_HAS_RESERVED_SLOTS(2)) && (cls->flags & JSCLASS_USERBIT1));
}
void forceConvertJsValueToStdString(JSContext *cx, JS::HandleValue jsval, std::string *ret) {
assert(ret != nullptr);
JS::RootedString jsStr(cx, JS::ToString(cx, jsval));
*ret = jsToStdString(cx, jsStr);
}
std::string jsToStdString(JSContext *cx, JS::HandleString jsStr) {
JS::UniqueChars str = JS_EncodeStringToUTF8(cx, jsStr);
std::string ret(str.get());
return ret;
}
void jsToSeArgs(JSContext *cx, int argc, const JS::CallArgs &argv, ValueArray &outArr) {
for (int i = 0; i < argc; ++i) {
jsToSeValue(cx, argv[i], &outArr[i]);
}
}
void seToJsArgs(JSContext *cx, const ValueArray &args, JS::RootedValueVector *outArr) {
for (const auto &arg : args) {
JS::RootedValue v(cx);
seToJsValue(cx, arg, &v);
outArr->append(v);
}
}
void seToJsValue(JSContext *cx, const Value &arg, JS::MutableHandleValue outVal) {
switch (arg.getType()) {
case Value::Type::Number: {
JS::RootedValue value(cx);
value.setDouble(arg.toNumber());
outVal.set(value);
} break;
case Value::Type::String: {
JS::UTF8Chars utf8Str(arg.toString().c_str(), arg.toString().length());
JSString *string = JS_NewStringCopyUTF8N(cx, utf8Str);
JS::RootedValue value(cx);
value.setString(string);
outVal.set(value);
} break;
case Value::Type::Boolean: {
JS::RootedValue value(cx);
value.setBoolean(arg.toBoolean());
outVal.set(value);
} break;
case Value::Type::Object: {
JS::RootedValue value(cx, JS::ObjectValue(*arg.toObject()->_getJSObject()));
outVal.set(value);
} break;
case Value::Type::Null: {
JS::RootedValue value(cx);
value.setNull();
outVal.set(value);
} break;
case Value::Type::Undefined: {
JS::RootedValue value(cx);
value.setUndefined();
outVal.set(value);
} break;
case Value::Type::BigInt: {
JS::RootedValue value(cx);
JS::BigInt *bi = JS::NumberToBigInt(cx, arg.toUint64());
outVal.setBigInt(bi);
} break;
default:
assert(false);
break;
}
}
void jsToSeValue(JSContext *cx, JS::HandleValue jsval, Value *v) {
if (jsval.isNumber()) {
v->setNumber(jsval.toNumber());
} else if (jsval.isString()) {
JS::RootedString jsstr(cx, jsval.toString());
v->setString(jsToStdString(cx, jsstr));
} else if (jsval.isBoolean()) {
v->setBoolean(jsval.toBoolean());
} else if (jsval.isObject()) {
Object *object = nullptr;
JS::RootedObject jsobj(cx, jsval.toObjectOrNull());
PrivateObjectBase *privateObject = static_cast<PrivateObjectBase *>(internal::getPrivate(cx, jsobj, 0));
void *nativeObj = privateObject ? privateObject->getRaw() : nullptr;
bool needRoot = false;
if (nativeObj != nullptr) {
object = Object::getObjectWithPtr(nativeObj);
}
if (object == nullptr) {
object = Object::_createJSObject(nullptr, jsobj);
needRoot = true;
}
v->setObject(object, needRoot);
object->decRef();
} else if (jsval.isNull()) {
v->setNull();
} else if (jsval.isUndefined()) {
v->setUndefined();
} else if (jsval.isBigInt()) {
v->setUint64(JS::ToBigUint64(jsval.toBigInt()));
} else {
assert(false);
}
}
void setReturnValue(JSContext *cx, const Value &data, const JS::CallArgs &argv) {
JS::RootedValue rval(cx);
seToJsValue(cx, data, &rval);
argv.rval().set(rval);
}
bool hasPrivate(JSContext *cx, JS::HandleObject obj) {
return isJSBClass(obj);
}
void *getPrivate(JSContext *cx, JS::HandleObject obj, uint32_t slot) {
bool found = isJSBClass(obj);
if (found) {
return SE_JS_GetPrivate(obj, slot);
}
return nullptr;
}
void setPrivate(JSContext *cx, JS::HandleObject obj, PrivateObjectBase *data, Object *seObj, PrivateData **outInternalData, JSFinalizeOp finalizeCb) {
bool found = isJSBClass(obj);
assert(found);
if (found) {
SE_JS_SetPrivate(obj, 0, data);
SE_JS_SetPrivate(obj, 1, seObj);
if (outInternalData != nullptr) {
*outInternalData = nullptr;
}
}
}
void clearPrivate(JSContext *cx, JS::HandleObject obj) {
bool found = isJSBClass(obj);
if (found) {
SE_JS_SetPrivate(obj, 0, nullptr);
SE_JS_SetPrivate(obj, 1, nullptr);
}
}
} // namespace internal
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

View File

@@ -0,0 +1,70 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM
#include "Base.h"
#include "../Value.h"
namespace se {
class Class;
namespace internal {
struct PrivateData {
PrivateObjectBase *data{nullptr};
Object *seObj{nullptr};
JSFinalizeOp finalizeCb{nullptr};
};
void forceConvertJsValueToStdString(JSContext *cx, JS::HandleValue jsval, std::string *ret);
std::string jsToStdString(JSContext *cx, JS::HandleString jsStr);
void jsToSeArgs(JSContext *cx, int argc, const JS::CallArgs &argv, ValueArray &outArr);
void jsToSeValue(JSContext *cx, JS::HandleValue jsval, Value *v);
void seToJsArgs(JSContext *cx, const ValueArray &args, JS::RootedValueVector *outArr);
void seToJsValue(JSContext *cx, const Value &v, JS::MutableHandleValue outVal);
void setReturnValue(JSContext *cx, const Value &data, const JS::CallArgs &argv);
bool hasPrivate(JSContext *cx, JS::HandleObject obj);
void *getPrivate(JSContext *cx, JS::HandleObject obj, uint32_t slot);
void setPrivate(JSContext *cx, JS::HandleObject obj, PrivateObjectBase *data, Object *seObj, PrivateData **outInternalData, JSFinalizeOp finalizeCb);
void clearPrivate(JSContext *cx, JS::HandleObject obj);
void *SE_JS_GetPrivate(JSObject *obj, uint32_t slot);
void SE_JS_SetPrivate(JSObject *obj, uint32_t slot, void *data);
} // namespace internal
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM

View File

@@ -0,0 +1,49 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "libplatform/libplatform.h"
//#define V8_DEPRECATION_WARNINGS 1
//#define V8_IMMINENT_DEPRECATION_WARNINGS 1
//#define V8_HAS_ATTRIBUTE_DEPRECATED_MESSAGE 1
#include "v8.h"
#include <string.h> // Resolves that memset, memcpy aren't found while APP_PLATFORM >= 22 on Android
#include <algorithm> // for std::find
#include <chrono>
#include <functional>
#include "../PrivateObject.h"
#include "base/std/container/string.h"
#include "base/std/container/unordered_map.h"
#include "base/std/container/unordered_set.h"
#include "HelperMacros.h"
namespace se {
using V8FinalizeFunc = void (*)(Object *seObj);
} // namespace se

View File

@@ -0,0 +1,328 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "Class.h"
#include <initializer_list>
#include "Value.h"
#include "base/Macros.h"
#include "v8/HelperMacros.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "Object.h"
#include "ScriptEngine.h"
#include "Utils.h"
namespace {
inline v8::Local<v8::Value> createExternal(v8::Isolate *isolate, void *data) {
if (data) {
return v8::External::New(isolate, data);
}
return {};
}
} // namespace
namespace se {
// ------------------------------------------------------- Object
namespace {
// ccstd::unordered_map<ccstd::string, Class *> __clsMap;
v8::Isolate *__isolate = nullptr; // NOLINT
ccstd::vector<Class *> __allClasses; // NOLINT
void invalidConstructor(const v8::FunctionCallbackInfo<v8::Value> &args) {
v8::Local<v8::Object> thisObj = args.This();
v8::Local<v8::String> constructorName = thisObj->GetConstructorName();
v8::String::Utf8Value strConstructorName{args.GetIsolate(), constructorName};
SE_ASSERT(false, "%s 's constructor is not public!", *strConstructorName); // NOLINT(misc-static-assert)
}
} // namespace
Class::Class() {
__allClasses.push_back(this);
}
Class::~Class() = default;
/* static */
Class *Class::create(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data) {
auto *cls = ccnew Class();
if (cls != nullptr && !cls->init(clsName, parent, parentProto, ctor, data)) {
delete cls;
cls = nullptr;
}
return cls;
}
Class *Class::create(const std::initializer_list<const char *> &classPath, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data) {
se::AutoHandleScope scope;
se::Value currentParent{parent};
for (auto i = 0; i < classPath.size() - 1; i++) {
se::Value tmp;
bool ok = currentParent.toObject()->getProperty(*(classPath.begin() + i), &tmp);
CC_ASSERT(ok); // class or namespace in path is not defined
currentParent = tmp;
}
return create(*(classPath.end() - 1), currentParent.toObject(), parentProto, ctor, data);
}
bool Class::init(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data) {
_name = clsName;
_parent = parent;
if (_parent != nullptr) {
_parent->incRef();
}
_parentProto = parentProto;
if (_parentProto != nullptr) {
_parentProto->incRef();
}
_constructor = ctor;
v8::FunctionCallback ctorToSet = _constructor != nullptr ? _constructor : invalidConstructor;
_constructorTemplate.Reset(__isolate, v8::FunctionTemplate::New(__isolate, ctorToSet, createExternal(__isolate, data)));
v8::MaybeLocal<v8::String> jsNameVal = v8::String::NewFromUtf8(__isolate, _name.c_str(), v8::NewStringType::kNormal);
if (jsNameVal.IsEmpty()) {
return false;
}
_constructorTemplate.Get(__isolate)->SetClassName(jsNameVal.ToLocalChecked());
_constructorTemplate.Get(__isolate)->InstanceTemplate()->SetInternalFieldCount(1);
return true;
}
void Class::_setCtor(Object *obj) {
assert(!_ctor.has_value());
_ctor = obj;
if (obj != nullptr) {
obj->root();
obj->incRef();
}
}
void Class::destroy() {
SAFE_DEC_REF(_parent);
SAFE_DEC_REF(_proto);
SAFE_DEC_REF(_parentProto);
if (_ctor.has_value()) {
if (_ctor.value() != nullptr) {
_ctor.value()->unroot();
_ctor.value()->decRef();
}
_ctor.reset();
}
_constructorTemplate.Reset();
}
void Class::cleanup() {
for (auto *cls : __allClasses) {
cls->destroy();
}
se::ScriptEngine::getInstance()->addAfterCleanupHook([]() {
for (auto *cls : __allClasses) {
delete cls;
}
__allClasses.clear();
});
}
void Class::setCreateProto(bool createProto) {
_createProto = createProto;
}
bool Class::install() {
// assert(__clsMap.find(_name) == __clsMap.end());
//
// __clsMap.emplace(_name, this);
if (_parentProto != nullptr) {
_constructorTemplate.Get(__isolate)->Inherit(_parentProto->_getClass()->_constructorTemplate.Get(__isolate));
}
v8::Local<v8::Context> context = __isolate->GetCurrentContext();
v8::MaybeLocal<v8::Function> ctor = _constructorTemplate.Get(__isolate)->GetFunction(context);
if (ctor.IsEmpty()) {
return false;
}
v8::Local<v8::Function> ctorChecked = ctor.ToLocalChecked();
v8::MaybeLocal<v8::String> name = v8::String::NewFromUtf8(__isolate, _name.c_str(), v8::NewStringType::kNormal);
if (name.IsEmpty()) {
return false;
}
v8::Maybe<bool> result = _parent->_getJSObject()->Set(context, name.ToLocalChecked(), ctorChecked);
if (result.IsNothing()) {
return false;
}
v8::MaybeLocal<v8::String> prototypeName = v8::String::NewFromUtf8(__isolate, "prototype", v8::NewStringType::kNormal);
if (prototypeName.IsEmpty()) {
return false;
}
v8::MaybeLocal<v8::Value> prototypeObj = ctorChecked->Get(context, prototypeName.ToLocalChecked());
if (prototypeObj.IsEmpty()) {
return false;
}
if (_createProto) {
// Proto object is released in Class::destroy.
_proto = Object::_createJSObject(this, v8::Local<v8::Object>::Cast(prototypeObj.ToLocalChecked()));
_proto->root();
}
return true;
}
bool Class::defineFunction(const char *name, v8::FunctionCallback func, void *data) {
v8::MaybeLocal<v8::String> jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal);
if (jsName.IsEmpty()) {
return false;
}
_constructorTemplate.Get(__isolate)->PrototypeTemplate()->Set(jsName.ToLocalChecked(), v8::FunctionTemplate::New(__isolate, func, createExternal(__isolate, data)));
return true;
}
bool Class::defineProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data) {
v8::MaybeLocal<v8::String> jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal);
if (jsName.IsEmpty()) {
return false;
}
auto prototypeTemplate = _constructorTemplate.Get(__isolate)->PrototypeTemplate();
auto externalData = createExternal(__isolate, data);
v8::Local<v8::FunctionTemplate> getterTemplate = v8::Local<v8::FunctionTemplate>();
v8::Local<v8::FunctionTemplate> setterTemplate = v8::Local<v8::FunctionTemplate>();
if (getter != nullptr) {
getterTemplate = v8::FunctionTemplate::New(__isolate, getter, externalData);
}
if (setter != nullptr) {
setterTemplate = v8::FunctionTemplate::New(__isolate, setter, externalData);
}
prototypeTemplate->SetAccessorProperty(jsName.ToLocalChecked(), getterTemplate, setterTemplate);
return true;
}
bool Class::defineProperty(const std::initializer_list<const char *> &names, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data) {
bool ret = true;
for (const auto *name : names) {
ret &= defineProperty(name, getter, setter, data);
}
return ret;
}
bool Class::defineStaticFunction(const char *name, v8::FunctionCallback func, void *data) {
v8::MaybeLocal<v8::String> jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal);
if (jsName.IsEmpty()) {
return false;
}
_constructorTemplate.Get(__isolate)->Set(jsName.ToLocalChecked(), v8::FunctionTemplate::New(__isolate, func, createExternal(__isolate, data)));
return true;
}
bool Class::defineStaticProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data) {
v8::MaybeLocal<v8::String> jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal);
if (jsName.IsEmpty()) {
return false;
}
auto externalData = createExternal(__isolate, data);
v8::Local<v8::FunctionTemplate> getterTemplate = v8::Local<v8::FunctionTemplate>();
v8::Local<v8::FunctionTemplate> setterTemplate = v8::Local<v8::FunctionTemplate>();
if (getter != nullptr) {
getterTemplate = v8::FunctionTemplate::New(__isolate, getter, externalData);
}
if (setter != nullptr) {
setterTemplate = v8::FunctionTemplate::New(__isolate, setter, externalData);
}
_constructorTemplate.Get(__isolate)->SetAccessorProperty(jsName.ToLocalChecked(), getterTemplate, setterTemplate);
return true;
}
bool Class::defineStaticProperty(const char *name, const Value &value, PropertyAttribute attribute /* = PropertyAttribute::NONE */) {
v8::MaybeLocal<v8::String> jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal);
if (jsName.IsEmpty()) {
return false;
}
v8::Local<v8::Value> v8Val;
internal::seToJsValue(__isolate, value, &v8Val);
_constructorTemplate.Get(__isolate)->Set(jsName.ToLocalChecked(), v8Val, static_cast<v8::PropertyAttribute>(attribute));
return true;
}
bool Class::defineFinalizeFunction(V8FinalizeFunc finalizeFunc) {
CC_ASSERT_NOT_NULL(finalizeFunc);
_finalizeFunc = finalizeFunc;
return true;
}
// v8::Local<v8::Object> Class::_createJSObject(const ccstd::string &clsName, Class** outCls)
// {
// auto iter = __clsMap.find(clsName);
// if (iter == __clsMap.end())
// {
// *outCls = nullptr;
// return v8::Local<v8::Object>::Cast(v8::Undefined(__isolate));
// }
//
// *outCls = iter->second;
// return _createJSObjectWithClass(iter->second);
// }
v8::Local<v8::Object> Class::_createJSObjectWithClass(Class *cls) { // NOLINT
v8::MaybeLocal<v8::Object> ret = cls->_constructorTemplate.Get(__isolate)->InstanceTemplate()->NewInstance(__isolate->GetCurrentContext());
CC_ASSERT(!ret.IsEmpty());
return ret.ToLocalChecked();
}
Object *Class::getProto() const {
return _proto;
}
V8FinalizeFunc Class::_getFinalizeFunction() const { // NOLINT
return _finalizeFunc;
}
/* static */
void Class::setIsolate(v8::Isolate *isolate) {
__isolate = isolate;
}
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,173 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "../Define.h"
#include "../Value.h"
#include "Base.h"
#include "base/std/optional.h"
namespace se {
class Object;
/**
* se::Class represents a definition of how to create a native binding object.
*/
class Class final {
public:
/**
* @brief Creates a class used for creating relevant native binding objects.
* @param[in] className A null-terminated UTF8 string containing the class's name.
* @param[in] obj The object that current class proto object attaches to. Should not be nullptr.
* @param[in] parentProto The parent proto object that current class inherits from. Passing nullptr means a new class has no parent.
* @param[in] ctor A callback to invoke when your constructor is used in a 'new' expression. Pass nullptr to use the default object constructor.
* @param[in] data A data pointer attach to the function callback.
* @return A class instance used for creating relevant native binding objects.
* @note Don't need to delete the pointer return by this method, it's managed internally.
*/
static Class *create(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data = nullptr);
static Class *create(const std::initializer_list<const char *> &classPath, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data = nullptr);
/**
* @brief Defines a member function with a callback. Each objects created by class will have this function property.
* @param[in] name A null-terminated UTF8 string containing the function name.
* @param[in] func A callback to invoke when the property is called as a function.
* @param[in] data A data pointer attach to the function callback.
* @return true if succeed, otherwise false.
*/
bool defineFunction(const char *name, v8::FunctionCallback func, void *data = nullptr);
/**
* @brief Defines a property with accessor callbacks. Each objects created by class will have this property.
* @param[in] name A null-terminated UTF8 string containing the property name.
* @param[in] getter A callback to invoke when the property is read.
* @param[in] setter A callback to invoke when the property is set.
* @param[in] data A data pointer attach to the property's callback
* @return true if succeed, otherwise false.
*/
bool defineProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data = nullptr);
bool defineProperty(const std::initializer_list<const char *> &names, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data = nullptr);
/**
* @brief Defines a static function with a callback. Only JavaScript constructor object will have this function.
* @param[in] name A null-terminated UTF8 string containing the function name.
* @param[in] func A callback to invoke when the constructor's property is called as a function.
* @param[in] data A data pointer attach to static function callback
* @return true if succeed, otherwise false.
*/
bool defineStaticFunction(const char *name, v8::FunctionCallback func, void *data = nullptr);
/**
* @brief Defines a static property with accessor callbacks. Only JavaScript constructor object will have this property.
* @param[in] name A null-terminated UTF8 string containing the property name.
* @param[in] getter A callback to invoke when the constructor's property is read.
* @param[in] setter A callback to invoke when the constructor's property is set.
* @param[in] data A data pointer attach to static property callback
* @return true if succeed, otherwise false.
*/
bool defineStaticProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data = nullptr);
/**
* @brief Defines a static property with a value. Only JavaScript constructor object will have this property.
* @param[in] name A null-terminated UTF8 string containing the property name.
* @param[in] value A value to be set on the constructor.
* @param[in] attribute An attribute to describe the property.
* @return true if succeed, otherwise false.
*/
bool defineStaticProperty(const char *name, const Value &value, PropertyAttribute attribute = PropertyAttribute::NONE);
/**
* @brief Defines the finalize function with a callback.
* @param[in] func The callback to invoke when a JavaScript object is garbage collected.
* @return true if succeed, otherwise false.
*/
bool defineFinalizeFunction(V8FinalizeFunc func);
/**
* @brief Installs class to JavaScript VM.
* @return true if succeed, otherwise false.
* @note After this method, an object could be created by `var foo = new Foo();`.
*/
bool install();
/**
* @brief Gets the proto object of this class.
* @return The proto object of this class.
* @note Don't need to be released in user code.
*/
Object *getProto() const;
/**
* @brief Gets the class name.
* @return The class name.
*/
const char *getName() const { return _name.c_str(); }
// Private API used in wrapper
V8FinalizeFunc _getFinalizeFunction() const; // NOLINT(readability-identifier-naming)
void _setCtor(Object *obj); // NOLINT(readability-identifier-naming)
inline const ccstd::optional<Object *> &_getCtor() const { return _ctor; } // NOLINT(readability-identifier-naming)
private:
Class();
~Class();
void setCreateProto(bool createProto);
bool init(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data = nullptr);
void destroy();
static void cleanup();
// static v8::Local<v8::Object> _createJSObject(const ccstd::string &clsName, Class** outCls);
static v8::Local<v8::Object> _createJSObjectWithClass(Class *cls); // NOLINT(readability-identifier-naming)
static void setIsolate(v8::Isolate *isolate);
ccstd::string _name;
Object *_parent{nullptr};
Object *_parentProto{nullptr};
Object *_proto{nullptr};
ccstd::optional<Object *> _ctor;
v8::FunctionCallback _constructor{nullptr};
v8::UniquePersistent<v8::FunctionTemplate> _constructorTemplate;
V8FinalizeFunc _finalizeFunc{nullptr};
bool _createProto{true};
friend class ScriptEngine;
friend class Object;
friend class JSBPersistentHandleVisitor;
};
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,181 @@
/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "HelperMacros.h"
#include "../State.h"
#include "../ValueArrayPool.h"
#include "Class.h"
#include "Object.h"
#include "ScriptEngine.h"
#include "Utils.h"
#if defined(RECORD_JSB_INVOKING)
namespace {
bool cmp(const std::pair<const char *, std::tuple<int, uint64_t>> &a, const std::pair<const char *, std::tuple<int, uint64_t>> &b) {
return std::get<1>(a.second) > std::get<1>(b.second);
}
unsigned int __jsbInvocationCount; // NOLINT(readability-identifier-naming)
ccstd::unordered_map<char const *, std::tuple<int, uint64_t>> __jsbFunctionInvokedRecords; // NOLINT(readability-identifier-naming)
} // namespace
JsbInvokeScopeT::JsbInvokeScopeT(const char *functionName) : _functionName(functionName) {
_start = std::chrono::high_resolution_clock::now();
__jsbInvocationCount++;
}
JsbInvokeScopeT::~JsbInvokeScopeT() {
auto &ref = __jsbFunctionInvokedRecords[_functionName];
std::get<0>(ref) += 1;
std::get<1>(ref) += std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - _start).count();
}
#endif
void printJSBInvokeAtFrame(int n) {
#if defined(RECORD_JSB_INVOKING)
static int cnt = 0;
cnt += 1;
if (cnt % n == 0) {
printJSBInvoke();
}
#endif
}
void clearRecordJSBInvoke() {
#if defined(RECORD_JSB_INVOKING)
__jsbInvocationCount = 0;
__jsbFunctionInvokedRecords.clear();
#endif
}
void printJSBInvoke() {
#if defined(RECORD_JSB_INVOKING)
static ccstd::vector<std::pair<const char *, std::tuple<int, uint64_t>>> pairs;
for (const auto &it : __jsbFunctionInvokedRecords) {
pairs.emplace_back(it); // NOLINT
}
std::sort(pairs.begin(), pairs.end(), cmp);
cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "Start print JSB function record info....... %d times", __jsbInvocationCount);
for (const auto &pair : pairs) {
cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "\t%s takes %.3lf ms, invoked %u times,", pair.first, std::get<1>(pair.second) / 1000000.0, std::get<0>(pair.second));
}
pairs.clear();
cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "End print JSB function record info.......\n");
#endif
}
SE_HOT void jsbFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value> &v8args, se_function_ptr func, const char *funcName) {
bool ret = false;
v8::Isolate *isolate = v8args.GetIsolate();
v8::HandleScope scope(isolate);
bool needDeleteValueArray{false};
se::ValueArray &args = se::gValueArrayPool.get(v8args.Length(), needDeleteValueArray);
se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray};
se::internal::jsToSeArgs(v8args, args);
se::Object *thisObject = se::internal::getPrivate(isolate, v8args.This());
se::State state(thisObject, args);
ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s\n", funcName);
}
se::internal::setReturnValue(state.rval(), v8args);
}
SE_HOT void jsbFinalizeWrapper(se::Object *thisObject, se_function_ptr func, const char *funcName) {
auto *engine = se::ScriptEngine::getInstance();
engine->_setGarbageCollecting(true);
se::State state(thisObject);
bool ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s\n", funcName);
}
engine->_setGarbageCollecting(false);
}
SE_HOT void jsbConstructorWrapper(const v8::FunctionCallbackInfo<v8::Value> &v8args, se_function_ptr func, se_finalize_ptr finalizeCb, se::Class *cls, const char *funcName) {
v8::Isolate *isolate = v8args.GetIsolate();
v8::HandleScope scope(isolate);
bool ret = true;
bool needDeleteValueArray{false};
se::ValueArray &args = se::gValueArrayPool.get(v8args.Length(), needDeleteValueArray);
se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray};
se::internal::jsToSeArgs(v8args, args);
se::Object *thisObject = se::Object::_createJSObject(cls, v8args.This());
thisObject->_setFinalizeCallback(finalizeCb);
se::State state(thisObject, args);
ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s\n", funcName);
}
se::Value property;
bool foundCtor = false;
if (!cls->_getCtor().has_value()) {
foundCtor = thisObject->getProperty("_ctor", &property, true);
if (foundCtor) {
cls->_setCtor(property.toObject());
} else {
cls->_setCtor(nullptr);
}
} else {
auto *ctorObj = cls->_getCtor().value();
if (ctorObj != nullptr) {
property.setObject(ctorObj);
foundCtor = true;
}
}
if (foundCtor) {
property.toObject()->call(args, thisObject);
}
}
SE_HOT void jsbGetterWrapper(const v8::FunctionCallbackInfo<v8::Value> &v8args, se_function_ptr func, const char *funcName) {
v8::Isolate *isolate = v8args.GetIsolate();
v8::HandleScope scope(isolate);
bool ret = true;
se::Object *thisObject = se::internal::getPrivate(isolate, v8args.This());
se::State state(thisObject);
ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s\n", funcName);
}
se::internal::setReturnValue(state.rval(), v8args);
}
SE_HOT void jsbSetterWrapper(const v8::FunctionCallbackInfo<v8::Value> &v8args, se_function_ptr func, const char *funcName) {
v8::Isolate *isolate = v8args.GetIsolate();
v8::HandleScope scope(isolate);
bool ret = true;
se::Object *thisObject = se::internal::getPrivate(isolate, v8args.This());
bool needDeleteValueArray{false};
se::ValueArray &args = se::gValueArrayPool.get(1, needDeleteValueArray);
se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray};
se::Value &data{args[0]};
se::internal::jsToSeValue(isolate, v8args[0], &data);
se::State state(thisObject, args);
ret = func(state);
if (!ret) {
SE_LOGE("[ERROR] Failed to invoke %s\n", funcName);
}
}

View File

@@ -0,0 +1,211 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "v8.h"
#include <algorithm>
#include <chrono>
#include <map>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include "../config.h"
#include "base/Log.h"
#include "base/Macros.h"
#include "base/std/container/string.h"
// #define RECORD_JSB_INVOKING
#ifndef CC_DEBUG
#undef RECORD_JSB_INVOKING
#endif
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#if defined(RECORD_JSB_INVOKING)
class JsbInvokeScopeT {
public:
JsbInvokeScopeT(const char *functionName);
~JsbInvokeScopeT();
private:
const char *_functionName;
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
};
#define JsbInvokeScope(arg) JsbInvokeScopeT invokeScope(arg); // NOLINT(readability-identifier-naming)
#else
// NOLINTNEXTLINE(readability-identifier-naming)
#define JsbInvokeScope(arg) \
do { \
} while (0)
#endif
template <typename T, typename STATE>
constexpr inline T *SE_THIS_OBJECT(STATE &s) { // NOLINT(readability-identifier-naming)
return reinterpret_cast<T *>(s.nativeThisObject());
}
template <typename T>
constexpr typename std::enable_if<std::is_enum<T>::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming)
return typeid(std::underlying_type_t<T>).name();
}
template <typename T>
constexpr typename std::enable_if<!std::is_enum<T>::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming)
return typeid(T).name();
}
void clearRecordJSBInvoke();
void printJSBInvoke();
void printJSBInvokeAtFrame(int n);
namespace se {
class Class;
class Object;
class State;
} // namespace se
using se_function_ptr = bool (*)(se::State &state);
using se_finalize_ptr = void (*)(se::Object *seObj);
void jsbFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value> &,
se_function_ptr,
const char *);
void jsbFinalizeWrapper(se::Object *thisObject,
se_function_ptr,
const char *);
void jsbConstructorWrapper(const v8::FunctionCallbackInfo<v8::Value> &,
se_function_ptr,
se_finalize_ptr finalizeCb,
se::Class *,
const char *);
void jsbGetterWrapper(const v8::FunctionCallbackInfo<v8::Value> &,
se_function_ptr,
const char *);
void jsbSetterWrapper(const v8::FunctionCallbackInfo<v8::Value> &,
se_function_ptr,
const char *);
#ifdef __GNUC__
#define SE_UNUSED __attribute__((unused))
#define SE_HOT __attribute__((hot))
#else
#define SE_UNUSED
#define SE_HOT
#endif
#define SAFE_INC_REF(obj) \
if (obj != nullptr) obj->incRef()
#define SAFE_DEC_REF(obj) \
if ((obj) != nullptr) { \
(obj)->decRef(); \
(obj) = nullptr; \
}
#define _SE(name) name##Registry // NOLINT(readability-identifier-naming, bugprone-reserved-identifier)
#define SE_DECLARE_FUNC(funcName) \
void funcName##Registry(const v8::FunctionCallbackInfo<v8::Value> &v8args)
#define SE_BIND_FUNC(funcName) \
void funcName##Registry(const v8::FunctionCallbackInfo<v8::Value> &_v8args) { \
JsbInvokeScope(#funcName); \
jsbFunctionWrapper(_v8args, funcName, #funcName); \
}
#define SE_BIND_FUNC_FAST(funcName) \
void funcName##Registry(const v8::FunctionCallbackInfo<v8::Value> &_v8args) { \
JsbInvokeScope(#funcName); \
auto *thisObject = static_cast<se::Object *>(_v8args.This()->GetAlignedPointerFromInternalField(0)); \
auto *nativeObject = thisObject != nullptr ? thisObject->getPrivateData() : nullptr; \
funcName(nativeObject); \
}
#define SE_BIND_FINALIZE_FUNC(funcName) \
void funcName##Registry(se::Object *thisObject) { \
JsbInvokeScope(#funcName); \
if (thisObject == nullptr) \
return; \
jsbFinalizeWrapper(thisObject, funcName, #funcName); \
}
#define SE_DECLARE_FINALIZE_FUNC(funcName) \
void funcName##Registry(se::Object *thisObject);
// v8 doesn't need to create a new JSObject in SE_BIND_CTOR while SpiderMonkey needs.
#define SE_BIND_CTOR(funcName, cls, finalizeCb) \
void funcName##Registry(const v8::FunctionCallbackInfo<v8::Value> &_v8args) { \
JsbInvokeScope(#funcName); \
jsbConstructorWrapper(_v8args, funcName, _SE(finalizeCb), cls, #funcName); \
}
#define SE_BIND_PROP_GET_IMPL(funcName, postFix) \
void funcName##postFix##Registry(const v8::FunctionCallbackInfo<v8::Value> &_v8args) { \
JsbInvokeScope(#funcName); \
jsbGetterWrapper(_v8args, funcName, #funcName); \
}
#define SE_BIND_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, )
#define SE_BIND_FUNC_AS_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, _asGetter)
#define SE_BIND_PROP_SET_IMPL(funcName, postFix) \
void funcName##postFix##Registry(const v8::FunctionCallbackInfo<v8::Value> &_v8args) { \
JsbInvokeScope(#funcName); \
jsbSetterWrapper(_v8args, funcName, #funcName); \
}
#define SE_BIND_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, )
#define SE_BIND_FUNC_AS_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, _asSetter)
#define SE_TYPE_NAME(t) typeid(t).name()
#define SE_QUOTEME_(x) #x // NOLINT(readability-identifier-naming)
#define SE_QUOTEME(x) SE_QUOTEME_(x)
// IDEA: implement this macro
// #define SE_REPORT_ERROR(fmt, ...) SE_LOGE(SE_STR_CONCAT3("[ERROR] ( %s, %d): ", fmt, "\n"), __FILE__, __LINE__, ##__VA_ARGS__)
#define SE_REPORT_ERROR(fmt, ...) selogMessage(cc::LogLevel::ERR, "[SE_ERROR]", (" (%s, %d): " fmt), __FILE__, __LINE__, ##__VA_ARGS__)
#if CC_DEBUG > 0
#define SE_ASSERT(cond, fmt, ...) \
do { \
if (!(cond)) { \
selogMessage(cc::LogLevel::ERR, "[SE_ASSERT]", (" (%s, %d): " fmt), __FILE__, __LINE__, ##__VA_ARGS__); \
CC_ABORT(); \
} \
} while (false)
#else
#define SE_ASSERT(cond, fmt, ...)
#endif // #if CC_DEBUG > 0
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,40 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "MissingSymbols.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
//TODO(PatriceJiang): modify this when OHOS llvm upgrade
#if CC_PLATFORM == CC_PLATFORM_OHOS
#include <string.h>
extern "C" {
int local_bcmp(const void *cs, const void *ct, size_t count) {
return memcmp(cs, ct, count);
}
int bcmp(const void *cs, const void *ct, size_t count) __attribute__((weak, alias("local_bcmp")));
} // extern "C"
#endif
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,31 @@
/****************************************************************************
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include <cerrno>
#include <cstddef>
#include <cstring>
#include "../config.h"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,706 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#include "bindings/jswrapper/PrivateObject.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "../RefCounter.h"
#include "../Value.h"
#include "Base.h"
#include "ObjectWrap.h"
#include "base/HasMemberFunction.h"
#include <memory>
// DEBUG ONLY:
// Set `__object_id__` && `__native_class_name__` for js object
#ifndef CC_DEBUG_JS_OBJECT_ID
#define CC_DEBUG_JS_OBJECT_ID 0
#endif
#define JSB_TRACK_OBJECT_CREATION 0
namespace se {
class Class;
class ScriptEngine;
/**
* se::Object represents JavaScript Object.
*/
class Object final : public RefCounter {
public:
/**
* @brief Creates a JavaScript Object like `{} or new Object()`.
* @return A JavaScript Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createPlainObject();
/**
* @brief Creates a ES6 Map Object like `new Map()` in JS.
* @return A JavaScript Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createMapObject();
/**
* @brief Creates a ES6 Set Object like `new Set()` in JS.
* @return A JavaScript Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createSetObject();
/**
* @brief Creates a JavaScript Array Object like `[] or new Array()`.
* @param[in] length The initical length of array.
* @return A JavaScript Array Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createArrayObject(size_t length);
/**
* @brief Creates a JavaScript Typed Array Object with uint8 format from an existing pointer.
* @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
* @deprecated This method is deprecated, please use `se::Object::createTypedArray` instead.
*/
SE_DEPRECATED_ATTRIBUTE static Object *createUint8TypedArray(uint8_t *bytes, size_t byteLength);
enum class TypedArrayType {
NONE,
INT8,
INT16,
INT32,
UINT8,
UINT8_CLAMPED,
UINT16,
UINT32,
FLOAT32,
FLOAT64
};
/**
* @brief Creates a JavaScript Typed Array Object with specified format from an existing pointer,
if provide a null pointer,then will create a empty JavaScript Typed Array Object.
* @param[in] type The format of typed array.
* @param[in] data A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createTypedArray(TypedArrayType type, const void *data, size_t byteLength);
/**
* @brief Creates a JavaScript Typed Array Object with a se::Object, which is a ArrayBuffer,
if provide a null pointer,then will create a empty JavaScript Typed Array Object.
* @param[in] type The format of typed array.
* @param[in] obj A ArrayBuffer to TypedArray.
* @param[in] offset Offset of ArrayBuffer to create with.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object which refers to the ArrayBuffer Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj);
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset);
static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength);
/**
* @brief Creates a JavaScript Array Buffer object from an existing pointer.
* @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A Array Buffer Object whose backing store is the same as the one pointed to data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createArrayBufferObject(const void *data, size_t byteLength);
using BufferContentsFreeFunc = void (*)(void *contents, size_t byteLength, void *userData);
static Object *createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData = nullptr);
/**
* @brief Creates a JavaScript Object from a JSON formatted string.
* @param[in] jsonStr The utf-8 string containing the JSON string to be parsed.
* @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid.
* @note The return value (non-null) has to be released manually.
*/
static Object *createJSONObject(const ccstd::string &jsonStr);
/**
* @brief Creates a JavaScript Native Binding Object from an existing se::Class instance.
* @param[in] cls The se::Class instance which stores native callback informations.
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createObjectWithClass(Class *cls);
/**
* @brief Creates a JavaScript Native Binding Object from an JS constructor with no arguments, which behaves as `new MyClass();` in JS.
* @param[in] constructor The JS constructor
* @return A JavaScript object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createObjectWithConstructor(se::Object *constructor);
/**
* @brief Creates a JavaScript Native Binding Object from an JS constructor with arguments, which behaves as `new MyClass(arg0, arg1, arg2, ...);` in JS.
* @param[in] constructor The JS constructor
* @param[in] args The arguments passed to the JS constructor
* @return A JavaScript object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object *createObjectWithConstructor(se::Object *constructor, const ValueArray &args);
/**
* Gets the Proxy Target object
* @param proxy The JavaScript Proxy object.
* @return The target JavaScript object of the parameter.
*/
static Object *createProxyTarget(se::Object *proxy);
/**
* @brief Gets a se::Object from an existing native object pointer.
* @param[in] ptr The native object pointer associated with the se::Object
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
* @deprecated Use NativePtrToObjectMap to query the native object.
*/
CC_DEPRECATED(3.7)
static Object *getObjectWithPtr(void *ptr);
/**
* @brief Gets a property from an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[out] value The property's value if object has the property, otherwise the undefined value.
* @return true if object has the property, otherwise false.
*/
inline bool getProperty(const char *name, Value *data) {
return getProperty(name, data, false);
}
bool getProperty(const char *name, Value *data, bool cachePropertyName);
inline bool getProperty(const ccstd::string &name, Value *value) {
return getProperty(name.c_str(), value);
}
/**
* @brief Sets a property to an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] value A value to be used as the property's value.
* @return true if the property is set successfully, otherwise false.
*/
bool setProperty(const char *name, const Value &data);
inline bool setProperty(const ccstd::string &name, const Value &value) {
return setProperty(name.c_str(), value);
}
/**
* @brief Delete a property of an object.
* @param[in] name A utf-8 string containing the property's name.
* @return true if the property is deleted successfully, otherwise false.
*/
bool deleteProperty(const char *name);
/**
* @brief Defines a property with native accessor callbacks for an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] getter The native callback for getter.
* @param[in] setter The native callback for setter.
* @return true if succeed, otherwise false.
*/
bool defineProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter);
bool defineOwnProperty(const char *name, const se::Value &value, bool writable = true, bool enumerable = true, bool configurable = true);
/**
* @brief Defines a function with a native callback for an object.
* @param[in] funcName A utf-8 string containing the function name.
* @param[in] func The native callback triggered by JavaScript code.
* @return true if succeed, otherwise false.
*/
bool defineFunction(const char *funcName, v8::FunctionCallback func);
/**
* @brief Tests whether an object can be called as a function.
* @return true if object can be called as a function, otherwise false.
*/
bool isFunction() const;
/**
* @brief Calls an object as a function.
* @param[in] args A se::Value array of arguments to pass to the function. Pass se::EmptyValueArray if argumentCount is 0.
* @param[in] thisObject The object to use as "this," or NULL to use the global object as "this."
* @param[out] rval The se::Value that results from calling object as a function, passing nullptr if return value is ignored.
* @return true if object is a function and there isn't any errors, otherwise false.
*/
bool call(const ValueArray &args, Object *thisObject, Value *rval = nullptr);
/**
* @brief Tests whether an object is a ES6 Map.
* @return true if object is a Map, otherwise false.
*/
bool isMap() const;
/**
* @brief Tests whether an object is a ES6 WeakMap.
* @return true if object is a WeakMap, otherwise false.
*/
bool isWeakMap() const;
/**
* @brief Tests whether an object is a ES6 Set.
* @return true if object is a Set, otherwise false.
*/
bool isSet() const;
/**
* @brief Tests whether an object is a ES6 WeakSet.
* @return true if object is a WeakSet, otherwise false.
*/
bool isWeakSet() const;
/**
* @brief Tests whether an object is an array.
* @return true if object is an array, otherwise false.
*/
bool isArray() const;
/**
* @brief Gets array length of an array object.
* @param[out] length The array length to be stored. It's set to 0 if there is an error.
* @return true if succeed, otherwise false.
*/
bool getArrayLength(uint32_t *length) const;
/**
* @brief Gets an element from an array object by numeric index.
* @param[in] index An integer value for index.
* @param[out] data The se::Value to be stored for the element in certain index.
* @return true if succeed, otherwise false.
*/
bool getArrayElement(uint32_t index, Value *data) const;
/**
* @brief Sets an element to an array object by numeric index.
* @param[in] index An integer value for index.
* @param[in] data The se::Value to be set to array with certain index.
* @return true if succeed, otherwise false.
*/
bool setArrayElement(uint32_t index, const Value &data);
/** @brief Tests whether an object is a typed array.
* @return true if object is a typed array, otherwise false.
*/
bool isTypedArray() const;
/** @brief Tests whether an object is a proxy object.
* @return true if object is a proxy object, otherwise false.
*/
bool isProxy() const;
/**
* @brief Gets the type of a typed array object.
* @return The type of a typed array object.
*/
TypedArrayType getTypedArrayType() const;
/**
* @brief Gets backing store of a typed array object.
* @param[out] ptr A temporary pointer to the backing store of a JavaScript Typed Array object.
* @param[out] length The byte length of a JavaScript Typed Array object.
* @return true if succeed, otherwise false.
*/
bool getTypedArrayData(uint8_t **ptr, size_t *length) const;
/**
* @brief Tests whether an object is an array buffer object.
* @return true if object is an array buffer object, otherwise false.
*/
bool isArrayBuffer() const;
/**
* @brief Gets buffer data of an array buffer object.
* @param[out] ptr A pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object.
* @param[out] length The number of bytes in a JavaScript data object.
* @return true if succeed, otherwise false.
*/
bool getArrayBufferData(uint8_t **ptr, size_t *length) const;
/**
* @brief Gets all property names of an object.
* @param[out] allKeys A string vector to store all property names.
* @return true if succeed, otherwise false.
*/
bool getAllKeys(ccstd::vector<ccstd::string> *allKeys) const;
// ES6 Map operations
/**
* @brief Clear all elements in a ES6 map object.
*/
void clearMap();
/**
* @brief Remove an element in a ES6 map by a key.
* @param[in] key The key of the element to remove, it could be any type that se::Value supports.
* @return true if succeed, otherwise false.
*/
bool removeMapElement(const Value &key);
/**
* @brief Get an element in a ES6 map by a key.
* @param[in] key Key of the element to get, it could be any type that se::Value supports.
* @param[out] outValue Out parameter, On success, *outValue receives the current value of the map element, or nullptr if no such element is found
* @return true if succeed, otherwise false.
*/
bool getMapElement(const Value &key, Value *outValue) const;
/**
* @brief Set an element in a ES6 map by a key.
* @param[in] key Key of the element to get, it could be any type that se::Value supports.
* @param[in] value The value to set the map.
* @return true if succeed, otherwise false.
*/
bool setMapElement(const Value &key, const Value &value);
/**
* @brief Get the size of a ES6 map
* @return The size of a ES6 map
*/
uint32_t getMapSize() const;
/**
* @brief Get all elements in a ES6 map
* @return All elements in a ES6 map, they're stored in a std::vector instead of `std::map/unordered_map` since we don't know how to compare or make a hash for `se::Value`s.
*/
ccstd::vector<std::pair<Value, Value>> getAllElementsInMap() const;
// ES6 Set operations
/**
* @brief Clear all elements in a ES6 set object.
*/
void clearSet();
/**
* @brief Remove an element in a ES6 set.
* @param[in] value The value to remove.
* @return true if succeed, otherwise false.
*/
bool removeSetElement(const Value &value);
/**
* @brief Add an element to a ES6 set.
* @param[in] value The value to set the set.
* @return true if succeed, otherwise false.
*/
bool addSetElement(const Value &value);
/**
* @brief Check whether the value is in a ES6 set.
* @return true if the value is in the ES6 set, otherwise false.
*/
bool isElementInSet(const Value &value) const;
/**
* @brief Get the size of a ES6 set.
* @return The size of a ES6 set.
*/
uint32_t getSetSize() const;
/**
* @brief Get all elements in a ES6 set.
* @return All elements in a ES6 set.
*/
ValueArray getAllElementsInSet() const;
void setPrivateObject(PrivateObjectBase *data);
template <typename T>
inline void setPrivateObject(TypedPrivateObject<T> *data) {
setPrivateObject(static_cast<PrivateObjectBase *>(data));
if constexpr (cc::has_setScriptObject<T, void(Object *)>::value) {
data->template get<T>()->setScriptObject(this);
}
}
PrivateObjectBase *getPrivateObject() const;
/*
* @brief Gets an object's private data.
* @return A void* that is the object's private data, if the object has private data, otherwise nullptr.
*/
inline void *getPrivateData() const {
return _privateData;
}
/**
* @brief Sets a pointer to private data on an object and use smart pointer to hold it.
*
* If the pointer is an instance of `cc::RefCounted`, an `cc::IntrusivePtr` will be created to hold
* the reference to the object, otherwise a `std::shared_ptr` object will be used.
* When the JS object is freed by GC, the corresponding smart pointer `IntrusivePtr/shared_ptr` will also be destroyed.
*
* If you do not want the pointer to be released by GC, you can call `setRawPrivateData`.
*
* @param[in] data A void* to set as the object's private data.
* @note This method will associate private data with se::Object by ccstd::unordered_map::emplace.
* It's used for search a se::Object via a void* private data.
*/
template <typename T>
inline void setPrivateData(T *data) {
static_assert(!std::is_void<T>::value, "void * is not allowed for private data");
setPrivateObject(se::make_shared_private_object(data));
}
/**
* @brief Use a InstrusivePtr to hold private data on the se::Object.
*
* @tparam T
* @param data A intrusive pointer object
*/
template <typename T>
inline void setPrivateData(const cc::IntrusivePtr<T> &data) {
setPrivateObject(se::ccintrusive_ptr_private_object(data));
}
/**
* @brief Use a std::shared_ptr to hold private data on the se::Object.
*
* @tparam T
* @param data A shared_ptr object
*/
template <typename T>
inline void setPrivateData(const std::shared_ptr<T> &data) {
setPrivateObject(se::shared_ptr_private_object(data));
}
/**
* @brief Set pointer to the private data on an object and will not use smart pointer to hold it.
*
* @tparam T
* @param data
* @param tryDestroyInGC When GCing the JS object, whether to `delete` the `data` pointer.
*/
template <typename T>
inline void setRawPrivateData(T *data, bool tryDestroyInGC = false) {
static_assert(!std::is_void<T>::value, "void * is not allowed for private data");
auto *privateObject = se::rawref_private_object(data);
if (tryDestroyInGC) {
privateObject->tryAllowDestroyInGC();
}
setPrivateObject(privateObject);
}
/**
* @brief Get the underlying private data as std::shared_ptr
*
* @tparam T
* @return std::shared_ptr<T>
*/
template <typename T>
inline std::shared_ptr<T> getPrivateSharedPtr() const {
assert(_privateObject->isSharedPtr());
return static_cast<se::SharedPtrPrivateObject<T> *>(_privateObject)->getData();
}
/**
* @brief Get the underlying private data as InstrusivePtr
*
* @tparam T
* @return cc::IntrusivePtr<T>
*/
template <typename T>
inline cc::IntrusivePtr<T> getPrivateInstrusivePtr() const {
assert(_privateObject->isCCIntrusivePtr());
return static_cast<se::CCIntrusivePtrPrivateObject<T> *>(_privateObject)->getData();
}
template <typename T>
inline T *getTypedPrivateData() const {
return reinterpret_cast<T *>(getPrivateData());
}
/**
* @brief Clears private data of an object.
* @param clearMapping Whether to clear the mapping of native object & se::Object.
*/
void clearPrivateData(bool clearMapping = true);
/**
* @brief Sets whether to clear the mapping of native object & se::Object in finalizer
*/
void setClearMappingInFinalizer(bool v) { _clearMappingInFinalizer = v; }
/**
* @brief Roots an object from garbage collection.
* @note Use this method when you want to store an object in a global or on the heap, where the garbage collector will not be able to discover your reference to it.
* An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void root();
/**
* @brief Unroots an object from garbage collection.
* @note An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void unroot();
/**
* @brief Tests whether an object is rooted.
* @return true if it has been already rooted, otherwise false.
*/
bool isRooted() const;
/**
* @brief Tests whether two objects are strict equal, as compared by the JS === operator.
* @param[in] o The object to be tested with this object.
* @return true if the two values are strict equal, otherwise false.
*/
bool strictEquals(Object *o) const;
/**
* @brief Attaches an object to current object.
* @param[in] obj The object to be attached.
* @return true if succeed, otherwise false.
* @note This method will set `obj` as a property of current object, therefore the lifecycle of child object will depend on current object,
* which means `obj` may be garbage collected only after current object is garbage collected.
* It's normally used in binding a native callback method. For example:
```javascript
var self = this;
someObject.setCallback(function(){}, self);
```
```c++
static bool SomeObject_setCallback(se::State& s)
{
SomeObject* cobj = (SomeObject*)s.nativeThisObject();
const auto& args = s.args();
size_t argc = args.size();
if (argc == 2) {
std::function<void()> arg0;
do {
if (args[0].isObject() && args[0].toObject()->isFunction())
{
se::Value jsThis(args[1]);
se::Value jsFunc(args[0]);
jsThis.toObject()->attachObject(jsFunc.toObject());
auto lambda = [=]() -> void {
...
// Call jsFunc stuff...
...
};
arg0 = lambda;
}
else
{
arg0 = nullptr;
}
} while(false);
SE_PRECONDITION2(ok, false, "Error processing arguments");
cobj->setCallback(arg0);
return true;
}
return false;
}
```
*/
bool attachObject(Object *obj);
/**
* @brief Detaches an object from current object.
* @param[in] obj The object to be detached.
* @return true if succeed, otherwise false.
* @note The attached object will not be released if current object is not garbage collected.
*/
bool detachObject(Object *obj);
/**
* @brief Returns the string for describing current object.
* @return The string for describing current object.
*/
ccstd::string toString() const;
ccstd::string toStringExt() const;
// Private API used in wrapper
static Object *_createJSObject(Class *cls, v8::Local<v8::Object> obj); // NOLINT(readability-identifier-naming)
v8::Local<v8::Object> _getJSObject() const; // NOLINT(readability-identifier-naming)
ObjectWrap &_getWrap(); // NOLINT(readability-identifier-naming)
Class *_getClass() const; // NOLINT(readability-identifier-naming)
void _setFinalizeCallback(V8FinalizeFunc finalizeCb); // NOLINT(readability-identifier-naming)
bool _isNativeFunction() const; // NOLINT(readability-identifier-naming)
//
#if CC_DEBUG && CC_DEBUG_JS_OBJECT_ID
uint32_t getObjectId() const { return _objectId; }
#endif
private:
static void nativeObjectFinalizeHook(Object *seObj);
static void setIsolate(v8::Isolate *isolate);
static void cleanup();
Object();
~Object() override;
bool init(Class *cls, v8::Local<v8::Object> obj);
v8::Local<v8::Value> getProxyTarget() const;
Class *_cls{nullptr};
ObjectWrap _obj;
uint32_t _rootCount{0};
PrivateObjectBase *_privateObject{nullptr};
void *_privateData{nullptr};
V8FinalizeFunc _finalizeCb{nullptr};
bool _clearMappingInFinalizer{true};
#if CC_DEBUG && CC_DEBUG_JS_OBJECT_ID
uint32_t _objectId = 0;
#endif
#if JSB_TRACK_OBJECT_CREATION
ccstd::string _objectCreationStackFrame;
#endif
friend class ScriptEngine;
friend class JSBPersistentHandleVisitor;
};
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,151 @@
/****************************************************************************
Copyright Joyent, Inc. and other Node contributors.
Copyright (c) 2021 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "ObjectWrap.h"
#include "Object.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
namespace {
bool gIsIsolateValid = false;
}
namespace se {
/* static */
void ObjectWrap::setIsolateValid(bool valid) {
gIsIsolateValid = valid;
}
ObjectWrap::ObjectWrap() = default;
ObjectWrap::~ObjectWrap() {
if (!gIsIsolateValid || persistent().IsEmpty()) {
return;
}
//cjh CC_ASSERT(persistent().IsNearDeath());
persistent().ClearWeak();
persistent().Reset();
}
bool ObjectWrap::init(v8::Local<v8::Object> handle, Object *parent, bool registerWeak) {
CC_ASSERT(persistent().IsEmpty());
_parent = parent;
_registerWeakCallback = registerWeak;
persistent().Reset(v8::Isolate::GetCurrent(), handle);
makeWeak();
return true;
}
void ObjectWrap::setFinalizeCallback(FinalizeFunc finalizeCb) {
_finalizeCb = finalizeCb;
}
/*static*/
void *ObjectWrap::unwrap(v8::Local<v8::Object> handle, uint32_t fieldIndex) {
CC_ASSERT(!handle.IsEmpty());
CC_ASSERT(handle->InternalFieldCount() > 0);
CC_ASSERT(fieldIndex >= 0 && fieldIndex < 1);
return handle->GetAlignedPointerFromInternalField(static_cast<int>(fieldIndex));
}
void ObjectWrap::wrap(void *nativeObj, uint32_t fieldIndex) {
CC_ASSERT(handle()->InternalFieldCount() > 0);
CC_ASSERT(fieldIndex >= 0 && fieldIndex < 1);
handle()->SetAlignedPointerInInternalField(static_cast<int>(fieldIndex), nativeObj);
if (nativeObj) {
persistent().SetWrapperClassId(MAGIC_CLASS_ID_JSB);
} else {
persistent().SetWrapperClassId(0);
}
}
v8::Local<v8::Object> ObjectWrap::handle() {
return handle(v8::Isolate::GetCurrent());
}
v8::Local<v8::Object> ObjectWrap::handle(v8::Isolate *isolate) {
return v8::Local<v8::Object>::New(isolate, persistent());
}
v8::Persistent<v8::Object> &ObjectWrap::persistent() {
return _handle;
}
void ObjectWrap::makeWeak() {
// V8 offical documentation said that:
// kParameter will pass a void* parameter back to the callback, kInternalFields
// will pass the first two internal fields back to the callback,
// kFinalizer will pass a void* parameter back, but is invoked before the object is
// actually collected, so it can be resurrected. In the last case, it is not
// possible to request a second pass callback.
// enum class WeakCallbackType { kParameter, kInternalFields, kFinalizer };
//
// NOTE: We get random crashes while previewing material in editor's inspector window,
// the reason is that kFinalizer will trigger weak callback when some assets are
// still being used, jsbinding code will get a dead se::Object pointer that was
// freed by weak callback. According V8 documentation, kParameter is a better option.
if (_registerWeakCallback) {
persistent().SetWeak(_parent, weakCallback, v8::WeakCallbackType::kParameter);
} else {
persistent().SetWeak();
}
// persistent().MarkIndependent();
}
void ObjectWrap::ref() {
CC_ASSERT(!persistent().IsEmpty());
persistent().ClearWeak();
_refs++;
}
void ObjectWrap::unref() {
if (!gIsIsolateValid) {
return;
}
CC_ASSERT(!persistent().IsEmpty());
CC_ASSERT(!persistent().IsWeak());
CC_ASSERT_GT(_refs, 0);
if (--_refs == 0) {
makeWeak();
}
}
/*static*/
void ObjectWrap::weakCallback(const v8::WeakCallbackInfo<Object> &data) {
Object *seObj = data.GetParameter();
ObjectWrap *wrap = &seObj->_getWrap();
CC_ASSERT(wrap->_refs == 0);
wrap->_handle.Reset();
if (wrap->_finalizeCb != nullptr) {
wrap->_finalizeCb(seObj);
} else {
CC_ABORT();
}
}
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,88 @@
/****************************************************************************
Copyright Joyent, Inc. and other Node contributors.
Copyright (c) 2021 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "../PrivateObject.h"
#include "Base.h"
namespace se {
class ObjectWrap {
public:
static constexpr uint16_t MAGIC_CLASS_ID_JSB = 0x1234;
ObjectWrap();
~ObjectWrap();
bool init(v8::Local<v8::Object> handle, Object *parent, bool registerWeak);
using FinalizeFunc = void (*)(Object *seObj);
void setFinalizeCallback(FinalizeFunc finalizeCb);
v8::Local<v8::Object> handle();
v8::Local<v8::Object> handle(v8::Isolate *isolate);
v8::Persistent<v8::Object> &persistent();
void wrap(void *nativeObj, uint32_t fieldIndex);
static void *unwrap(v8::Local<v8::Object> handle, uint32_t fieldIndex);
/* Ref() marks the object as being attached to an event loop.
* Refed objects will not be garbage collected, even if
* all references are lost.
*/
void ref();
/* Unref() marks an object as detached from the event loop. This is its
* default state. When an object with a "weak" reference changes from
* attached to detached state it will be freed. Be careful not to access
* the object after making this call as it might be gone!
* (A "weak reference" means an object that only has a
* persistent handle.)
*
* DO NOT CALL THIS FROM DESTRUCTOR
*/
void unref();
static void setIsolateValid(bool valid);
private:
static void weakCallback(const v8::WeakCallbackInfo<Object> &data);
void makeWeak();
int _refs{0}; // ro
v8::Persistent<v8::Object> _handle;
FinalizeFunc _finalizeCb{nullptr};
Object *_parent{nullptr};
bool _registerWeakCallback{false};
};
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,2 @@
V8 inspector from node commit (4e8bc7181c1f2491e187477798d433a4488f43d4)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,441 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "../Value.h"
#include "Base.h"
#include <thread>
#if SE_ENABLE_INSPECTOR
namespace node {
namespace inspector {
class Agent;
}
class Environment;
class IsolateData;
} // namespace node
#endif
namespace se {
class Object;
class Class;
class Value;
/**
* A stack-allocated class that governs a number of local handles.
* It's only implemented for v8 wrapper now.
* Other script engine wrappers have empty implementation for this class.
* It's used at the beginning of executing any wrapper API.
*/
class AutoHandleScope {
public:
AutoHandleScope();
~AutoHandleScope();
private:
v8::HandleScope _handleScope;
};
/**
* ScriptEngine is a sington which represents a context of JavaScript VM.
*/
class ScriptEngine final {
public:
/**
* @brief Gets or creates the instance of script engine.
* @return The script engine instance.
*/
static ScriptEngine *getInstance();
/**
* @brief Destroys the instance of script engine.
*/
CC_DEPRECATED(3.6.0)
static void destroyInstance();
ScriptEngine();
~ScriptEngine();
/**
* @brief Gets the global object of JavaScript VM.
* @return The se::Object stores the global JavaScript object.
*/
Object *getGlobalObject() const;
using RegisterCallback = bool (*)(Object *);
/**
* @brief Adds a callback for registering a native binding module.
* @param[in] cb A callback for registering a native binding module.
* @note This method just add a callback to a vector, callbacks is invoked in `start` method.
*/
void addRegisterCallback(RegisterCallback cb);
/**
* @brief Adds a callback for registering a native binding module, which will not be removed by ScriptEngine::cleanup.
* @param[in] cb A callback for registering a native binding module.
* @note This method just add a callback to a vector, callbacks is invoked in `start` method.
*/
void addPermanentRegisterCallback(RegisterCallback cb);
/**
* @brief Starts the script engine.
* @return true if succeed, otherwise false.
* @note This method will invoke all callbacks of native binding modules by the order of registration.
*/
bool start();
/**
* @brief Starts the script engine with isolate.
* @return true if succeed, otherwise false.
* @note This method will invoke all callbacks of native binding modules by the order of registration.
*/
bool start(v8::Isolate *isolate);
/**
* @brief Initializes script engine.
* @return true if succeed, otherwise false.
* @note This method will create JavaScript context and global object.
*/
bool init();
/**
* @brief Initializes script engine with isolate.
* @return true if succeed, otherwise false.
* @note This method will create JavaScript context and global object.
*/
bool init(v8::Isolate *isolate);
/**
* @brief Adds a hook function before initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeInitHook(const std::function<void()> &hook);
/**
* @brief Adds a hook function after initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterInitHook(const std::function<void()> &hook);
/**
* @brief Cleanups script engine.
* @note This method will removes all objects in JavaScript VM even whose are rooted, then shutdown JavaScript VMf.
*/
void cleanup();
/**
* @brief Adds a hook function before cleanuping script engine.
* @param[in] hook A hook function to be invoked before cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeCleanupHook(const std::function<void()> &hook);
/**
* @brief Adds a hook function after cleanuping script engine.
* @param[in] hook A hook function to be invoked after cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterCleanupHook(const std::function<void()> &hook);
/**
* @brief Executes a utf-8 string buffer which contains JavaScript code.
* @param[in] script A utf-8 string buffer, if it isn't null-terminated, parameter `length` should be assigned and > 0.
* @param[in] length The length of parameter `scriptStr`, it will be set to string length internally if passing 0 and parameter `scriptStr` is null-terminated.
* @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @param[in] fileName A string containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information.
* @return true if succeed, otherwise false.
*/
bool evalString(const char *script, uint32_t length = 0, Value *ret = nullptr, const char *fileName = nullptr);
/**
* @brief Compile script file into v8::ScriptCompiler::CachedData and save to file.
* @param[in] path The path of script file.
* @param[in] pathBc The location where bytecode file should be written to. The path should be ends with ".bc", which indicates a bytecode file.
* @return true if succeed, otherwise false.
*/
bool saveByteCodeToFile(const ccstd::string &path, const ccstd::string &pathBc);
/**
* @brief Grab a snapshot of the current JavaScript execution stack.
* @return current stack trace string
*/
ccstd::string getCurrentStackTrace();
/**
* Delegate class for file operation
*/
class FileOperationDelegate {
public:
FileOperationDelegate()
: onGetDataFromFile(nullptr),
onGetStringFromFile(nullptr),
onCheckFileExist(nullptr),
onGetFullPath(nullptr) {}
/**
* @brief Tests whether delegate is valid.
*/
bool isValid() const {
return onGetDataFromFile != nullptr && onGetStringFromFile != nullptr && onCheckFileExist != nullptr && onGetFullPath != nullptr;
}
// path, buffer, buffer size
std::function<void(const ccstd::string &, const std::function<void(const uint8_t *, size_t)> &)> onGetDataFromFile;
// path, return file string content.
std::function<ccstd::string(const ccstd::string &)> onGetStringFromFile;
// path
std::function<bool(const ccstd::string &)> onCheckFileExist;
// path, return full path
std::function<ccstd::string(const ccstd::string &)> onGetFullPath;
};
/**
* @brief Sets the delegate for file operation.
* @param delegate[in] The delegate instance for file operation.
*/
void setFileOperationDelegate(const FileOperationDelegate &delegate);
/**
* @brief Gets the delegate for file operation.
* @return The delegate for file operation
*/
const FileOperationDelegate &getFileOperationDelegate() const;
/**
* @brief Executes a file which contains JavaScript code.
* @param[in] path Script file path.
* @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @return true if succeed, otherwise false.
*/
bool runScript(const ccstd::string &path, Value *ret = nullptr);
/**
* @brief Tests whether script engine is doing garbage collection.
* @return true if it's in garbage collection, otherwise false.
*/
bool isGarbageCollecting() const;
/**
* @brief Performs a JavaScript garbage collection.
*/
void garbageCollect();
/**
* @brief Tests whether script engine is being cleaned up.
* @return true if it's in cleaning up, otherwise false.
*/
bool isInCleanup() const { return _isInCleanup; }
/**
* @brief Tests whether script engine is valid.
* @return true if it's valid, otherwise false.
*/
bool isValid() const;
/**
* @brief Throw JS exception
*/
void throwException(const ccstd::string &errorMessage);
/**
* @brief Clears all exceptions.
*/
void clearException();
using ExceptionCallback = std::function<void(const char *, const char *, const char *)>; // location, message, stack
/**
* @brief Sets the callback function while an exception is fired.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setExceptionCallback(const ExceptionCallback &cb);
/**
* @brief Sets the callback function while an exception is fired in JS.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setJSExceptionCallback(const ExceptionCallback &cb);
/**
* @brief Gets the start time of script engine.
* @return The start time of script engine.
*/
const std::chrono::steady_clock::time_point &getStartTime() const { return _startTime; }
/**
* @brief Enables JavaScript debugger
* @param[in] serverAddr The address of debugger server.
* @param[in] isWait Whether wait debugger attach when loading.
*/
void enableDebugger(const ccstd::string &serverAddr, uint32_t port, bool isWait = false);
/**
* @brief Tests whether JavaScript debugger is enabled
* @return true if JavaScript debugger is enabled, otherwise false.
*/
bool isDebuggerEnabled() const;
/**
* @brief Main loop update trigger, it's need to invoked in main thread every frame.
*/
void mainLoopUpdate();
/**
* @brief Gets script virtual machine instance ID. Default value is 1, increase by 1 if `init` is invoked.
*/
uint32_t getVMId() const { return _vmId; }
/**
* @brief Fast version of call script function, faster than Object::call
*/
bool callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval = nullptr);
/**
* @brief Handle all exceptions throwed by promise
*/
void handlePromiseExceptions();
// Private API used in wrapper
class VMStringPool final {
public:
VMStringPool();
~VMStringPool();
v8::MaybeLocal<v8::String> get(v8::Isolate *isolate, const char *name);
void clear();
private:
ccstd::unordered_map<ccstd::string, v8::Persistent<v8::String> *> _vmStringPoolMap;
};
inline VMStringPool &_getStringPool() { return _stringPool; } // NOLINT(readability-identifier-naming)
void _retainScriptObject(void *owner, void *target); // NOLINT(readability-identifier-naming)
void _releaseScriptObject(void *owner, void *target); // NOLINT(readability-identifier-naming)
v8::Local<v8::Context> _getContext() const; // NOLINT(readability-identifier-naming)
void _setGarbageCollecting(bool isGarbageCollecting); // NOLINT(readability-identifier-naming)
struct DebuggerInfo {
ccstd::string serverAddr;
uint32_t port{0};
bool isWait{false};
inline bool isValid() const { return !serverAddr.empty() && port != 0; }
inline void reset() {
serverAddr.clear();
port = 0;
isWait = false;
}
};
static void _setDebuggerInfo(const DebuggerInfo &info); // NOLINT(readability-identifier-naming)
//
private:
static void privateDataFinalize(PrivateObjectBase *privateObj);
static void onFatalErrorCallback(const char *location, const char *message);
static void onOOMErrorCallback(const char *location,
#if V8_MAJOR_VERSION > 10 || (V8_MAJOR_VERSION == 10 && V8_MINOR_VERSION > 4)
const v8::OOMDetails& details
#else
bool isHeapOom
#endif
);
static void onMessageCallback(v8::Local<v8::Message> message, v8::Local<v8::Value> data);
static void onPromiseRejectCallback(v8::PromiseRejectMessage msg);
/**
* @brief Load the bytecode file and set the return value
* @param[in] path_bc The path of bytecode file.
* @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @return true if succeed, otherwise false.
*/
bool runByteCodeFile(const ccstd::string &pathBc, Value *ret /* = nullptr */);
void callExceptionCallback(const char *, const char *, const char *);
bool callRegisteredCallback();
bool postInit();
// Struct to save exception info
struct PromiseExceptionMsg {
ccstd::string event;
ccstd::string stackTrace;
};
// Push promise and exception msg to _promiseArray
void pushPromiseExeception(const v8::Local<v8::Promise> &promise, const char *event, const char *stackTrace);
static ScriptEngine *instance;
static DebuggerInfo debuggerInfo;
ccstd::vector<std::tuple<std::unique_ptr<v8::Persistent<v8::Promise>>, ccstd::vector<PromiseExceptionMsg>>> _promiseArray;
std::chrono::steady_clock::time_point _startTime;
ccstd::vector<RegisterCallback> _registerCallbackArray;
ccstd::vector<RegisterCallback> _permRegisterCallbackArray;
ccstd::vector<std::function<void()>> _beforeInitHookArray;
ccstd::vector<std::function<void()>> _afterInitHookArray;
ccstd::vector<std::function<void()>> _beforeCleanupHookArray;
ccstd::vector<std::function<void()>> _afterCleanupHookArray;
v8::Persistent<v8::Context> _context;
v8::Isolate *_isolate;
v8::HandleScope *_handleScope;
Object *_globalObj;
Value _gcFuncValue;
Object *_gcFunc = nullptr;
FileOperationDelegate _fileOperationDelegate;
ExceptionCallback _nativeExceptionCallback = nullptr;
ExceptionCallback _jsExceptionCallback = nullptr;
#if SE_ENABLE_INSPECTOR
node::Environment *_env;
node::IsolateData *_isolateData;
#endif
VMStringPool _stringPool;
std::thread::id _engineThreadId;
ccstd::string _lastStackTrace;
ccstd::string _debuggerServerAddr;
uint32_t _debuggerServerPort;
bool _isWaitForConnect;
uint32_t _vmId;
bool _isValid;
bool _isGarbageCollecting;
bool _isInCleanup;
bool _isErrorHandleWorking;
};
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,32 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "Class.h"
#include "HelperMacros.h"
#include "Object.h"
#include "ScriptEngine.h"
#include "Utils.h"

View File

@@ -0,0 +1,249 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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 "Utils.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include <cfloat>
#include "Class.h"
#include "Object.h"
#include "ScriptEngine.h"
#include "base/Log.h"
#include "base/Macros.h"
namespace se {
namespace internal {
void jsToSeArgs(const v8::FunctionCallbackInfo<v8::Value> &v8args, ValueArray &outArr) {
v8::Isolate *isolate = v8args.GetIsolate();
for (int i = 0; i < v8args.Length(); i++) {
jsToSeValue(isolate, v8args[i], &outArr[i]);
}
}
void seToJsArgs(v8::Isolate *isolate, const ValueArray &args, v8::Local<v8::Value> *outArr) {
CC_ASSERT_NOT_NULL(outArr);
uint32_t i = 0;
for (const auto &data : args) {
v8::Local<v8::Value> &jsval = outArr[i];
seToJsValue(isolate, data, &jsval);
++i;
}
}
void seToJsValue(v8::Isolate *isolate, const Value &v, v8::Local<v8::Value> *outJsVal) {
CC_ASSERT_NOT_NULL(outJsVal);
switch (v.getType()) {
case Value::Type::Number:
*outJsVal = v8::Number::New(isolate, v.toDouble());
break;
case Value::Type::String: {
v8::MaybeLocal<v8::String> str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast<int>(v.toString().length()));
if (!str.IsEmpty()) {
*outJsVal = str.ToLocalChecked();
} else {
outJsVal->Clear();
}
} break;
case Value::Type::Boolean:
*outJsVal = v8::Boolean::New(isolate, v.toBoolean());
break;
case Value::Type::Object:
*outJsVal = v.toObject()->_getJSObject();
break;
case Value::Type::Null:
*outJsVal = v8::Null(isolate);
break;
case Value::Type::Undefined:
*outJsVal = v8::Undefined(isolate);
break;
case Value::Type::BigInt:
*outJsVal = v8::BigInt::New(isolate, v.toInt64());
break;
default:
CC_ABORT();
break;
}
}
void jsToSeValue(v8::Isolate *isolate, v8::Local<v8::Value> jsval, Value *v) {
CC_ASSERT_NOT_NULL(v);
v8::HandleScope handleScope(isolate);
if (jsval->IsUndefined()) {
v->setUndefined();
} else if (jsval->IsNull()) {
v->setNull();
} else if (jsval->IsNumber()) {
v8::MaybeLocal<v8::Number> jsNumber = jsval->ToNumber(isolate->GetCurrentContext());
if (!jsNumber.IsEmpty()) {
v->setDouble(jsNumber.ToLocalChecked()->Value());
} else {
v->setUndefined();
}
} else if (jsval->IsBigInt()) {
v8::MaybeLocal<v8::BigInt> jsBigInt = jsval->ToBigInt(isolate->GetCurrentContext());
if (!jsBigInt.IsEmpty()) {
auto bigInt = jsBigInt.ToLocalChecked();
v->setInt64(bigInt->Int64Value());
} else {
v->setUndefined();
}
} else if (jsval->IsString()) {
v8::String::Utf8Value utf8(isolate, jsval);
const char *utf8Str = *utf8;
v->setString(utf8Str);
} else if (jsval->IsBoolean()) {
v8::MaybeLocal<v8::Boolean> jsBoolean = jsval->ToBoolean(isolate);
if (!jsBoolean.IsEmpty()) {
v->setBoolean(jsBoolean.ToLocalChecked()->Value());
} else {
v->setUndefined();
}
} else if (jsval->IsObject()) {
v8::MaybeLocal<v8::Object> jsObj = jsval->ToObject(isolate->GetCurrentContext());
if (!jsObj.IsEmpty()) {
auto *obj = internal::getPrivate(isolate, jsObj.ToLocalChecked());
if (obj == nullptr) {
obj = Object::_createJSObject(nullptr, jsObj.ToLocalChecked());
} else {
obj->incRef();
}
v->setObject(obj, true);
obj->decRef();
} else {
v->setUndefined();
}
}
}
template <int N>
static void warnWithinTimesInReleaseMode(const char *msg) {
static int timesLimit = N;
#if CC_DEBUG
CC_LOG_DEBUG(msg);
#else
if (timesLimit > 0) {
CC_LOG_WARNING(msg);
timesLimit--;
}
#endif
}
template <typename T>
void setReturnValueTemplate(const Value &data, const T &argv) {
switch (data.getType()) {
case Value::Type::Undefined: {
argv.GetReturnValue().Set(v8::Undefined(argv.GetIsolate()));
break;
}
case Value::Type::Null: {
argv.GetReturnValue().Set(v8::Null(argv.GetIsolate()));
break;
}
case Value::Type::Number: {
argv.GetReturnValue().Set(v8::Number::New(argv.GetIsolate(), data.toDouble()));
break;
}
case Value::Type::BigInt: {
constexpr int64_t maxSafeInt = 9007199254740991LL; // value refer to JS Number.MAX_SAFE_INTEGER
constexpr int64_t minSafeInt = -9007199254740991LL; // value refer to JS Number.MIN_SAFE_INTEGER
if (data.toInt64() > maxSafeInt || data.toInt64() < minSafeInt) {
// NOTICE: Precision loss will happend here.
warnWithinTimesInReleaseMode<100>("int64 value is out of range for double");
CC_ABORT(); // should be fixed in debug mode.
}
argv.GetReturnValue().Set(v8::Number::New(argv.GetIsolate(), static_cast<double>(data.toInt64())));
break;
}
case Value::Type::String: {
v8::MaybeLocal<v8::String> value = v8::String::NewFromUtf8(argv.GetIsolate(), data.toString().c_str(), v8::NewStringType::kNormal);
CC_ASSERT(!value.IsEmpty());
argv.GetReturnValue().Set(value.ToLocalChecked());
break;
}
case Value::Type::Boolean: {
argv.GetReturnValue().Set(v8::Boolean::New(argv.GetIsolate(), data.toBoolean()));
break;
}
case Value::Type::Object: {
argv.GetReturnValue().Set(data.toObject()->_getJSObject());
break;
}
}
}
void setReturnValue(const Value &data, const v8::FunctionCallbackInfo<v8::Value> &argv) {
setReturnValueTemplate(data, argv);
}
void setReturnValue(const Value &data, const v8::PropertyCallbackInfo<v8::Value> &argv) {
setReturnValueTemplate(data, argv);
}
bool hasPrivate(v8::Isolate * /*isolate*/, v8::Local<v8::Value> value) {
v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(value);
return obj->InternalFieldCount() > 0;
}
void setPrivate(v8::Isolate *isolate, ObjectWrap &wrap, Object *thizObj) {
v8::Local<v8::Object> obj = wrap.handle(isolate);
int c = obj->InternalFieldCount();
CC_ASSERT_GT(c, 0);
if (c == 1) {
wrap.wrap(thizObj, 0);
}
}
Object *getPrivate(v8::Isolate *isolate, v8::Local<v8::Value> value) {
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::MaybeLocal<v8::Object> obj = value->ToObject(context);
if (obj.IsEmpty()) {
return nullptr;
}
v8::Local<v8::Object> objChecked = obj.ToLocalChecked();
int c = objChecked->InternalFieldCount();
if (c == 1) {
return static_cast<Object *>(ObjectWrap::unwrap(objChecked, 0));
}
return nullptr;
}
void clearPrivate(v8::Isolate *isolate, ObjectWrap &wrap) {
v8::Local<v8::Object> obj = wrap.handle(isolate);
int c = obj->InternalFieldCount();
if (c == 1) {
wrap.wrap(nullptr, 0);
}
}
} // namespace internal
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,56 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
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.
****************************************************************************/
#pragma once
#include "../config.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "../Value.h"
#include "Base.h"
#include "ObjectWrap.h"
namespace se {
namespace internal {
void jsToSeArgs(const v8::FunctionCallbackInfo<v8::Value> &v8args, ValueArray &outArr);
void jsToSeValue(v8::Isolate *isolate, v8::Local<v8::Value> jsval, Value *v);
void seToJsArgs(v8::Isolate *isolate, const ValueArray &args, v8::Local<v8::Value> *outArr);
void seToJsValue(v8::Isolate *isolate, const Value &v, v8::Local<v8::Value> *outJsVal);
void setReturnValue(const Value &data, const v8::FunctionCallbackInfo<v8::Value> &argv);
void setReturnValue(const Value &data, const v8::PropertyCallbackInfo<v8::Value> &argv);
bool hasPrivate(v8::Isolate *isolate, v8::Local<v8::Value> value);
void setPrivate(v8::Isolate *isolate, ObjectWrap &wrap, Object *obj);
Object *getPrivate(v8::Isolate *isolate, v8::Local<v8::Value> value);
void clearPrivate(v8::Isolate *isolate, ObjectWrap &wrap);
} // namespace internal
} // namespace se
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8

View File

@@ -0,0 +1,597 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//#include "mozilla/Assertions.h"
//#include "mozilla/EndianUtils.h"
#include "SHA1.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include <string.h>
#include <assert.h>
#if defined(_MSC_VER)
#include <stdlib.h>
#pragma intrinsic(_byteswap_ushort)
#pragma intrinsic(_byteswap_ulong)
#pragma intrinsic(_byteswap_uint64)
#endif
#define MOZ_ASSERT(cond, ...) assert(cond)
//using se::NativeEndian;
using se::SHA1Sum;
namespace {
#if defined(_WIN64)
#if defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_)
#define MOZ_LITTLE_ENDIAN 1
#else
#error "CPU type is unknown"
#endif
#elif defined(_WIN32)
#if defined(_M_IX86)
#define MOZ_LITTLE_ENDIAN 1
#elif defined(_M_ARM)
#define MOZ_LITTLE_ENDIAN 1
#else
#error "CPU type is unknown"
#endif
#elif defined(__APPLE__) || defined(__powerpc__) || defined(__ppc__)
#if __LITTLE_ENDIAN__
#define MOZ_LITTLE_ENDIAN 1
#elif __BIG_ENDIAN__
#define MOZ_BIG_ENDIAN 1
#endif
#elif defined(__GNUC__) && \
defined(__BYTE_ORDER__) && \
defined(__ORDER_LITTLE_ENDIAN__) && \
defined(__ORDER_BIG_ENDIAN__)
/*
* Some versions of GCC provide architecture-independent macros for
* this. Yes, there are more than two values for __BYTE_ORDER__.
*/
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define MOZ_LITTLE_ENDIAN 1
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define MOZ_BIG_ENDIAN 1
#else
#error "Can't handle mixed-endian architectures"
#endif
/*
* We can't include useful headers like <endian.h> or <sys/isa_defs.h>
* here because they're not present on all platforms. Instead we have
* this big conditional that ideally will catch all the interesting
* cases.
*/
#elif defined(__sparc) || defined(__sparc__) || \
defined(_POWER) || defined(__hppa) || \
defined(_MIPSEB) || defined(__ARMEB__) || \
defined(__s390__) || defined(__AARCH64EB__) || \
(defined(__sh__) && defined(__LITTLE_ENDIAN__)) || \
(defined(__ia64) && defined(__BIG_ENDIAN__))
#define MOZ_BIG_ENDIAN 1
#elif defined(__i386) || defined(__i386__) || \
defined(__x86_64) || defined(__x86_64__) || \
defined(_MIPSEL) || defined(__ARMEL__) || \
defined(__alpha__) || defined(__AARCH64EL__) || \
(defined(__sh__) && defined(__BIG_ENDIAN__)) || \
(defined(__ia64) && !defined(__BIG_ENDIAN__))
#define MOZ_LITTLE_ENDIAN 1
#endif
#if MOZ_BIG_ENDIAN
#define MOZ_LITTLE_ENDIAN 0
#elif MOZ_LITTLE_ENDIAN
#define MOZ_BIG_ENDIAN 0
#else
#error "Cannot determine endianness"
#endif
#if defined(__clang__)
#if __has_builtin(__builtin_bswap16)
#define MOZ_HAVE_BUILTIN_BYTESWAP16 __builtin_bswap16
#endif
#elif defined(__GNUC__)
#define MOZ_HAVE_BUILTIN_BYTESWAP16 __builtin_bswap16
#elif defined(_MSC_VER)
#define MOZ_HAVE_BUILTIN_BYTESWAP16 _byteswap_ushort
#endif
enum Endianness { Little,
Big };
#if MOZ_BIG_ENDIAN
#define MOZ_NATIVE_ENDIANNESS Big
#else
#define MOZ_NATIVE_ENDIANNESS Little
#endif
/*
* We need wrappers here because free functions with default template
* arguments and/or partial specialization of function templates are not
* supported by all the compilers we use.
*/
template <typename T, size_t Size = sizeof(T)>
struct Swapper;
template <typename T>
struct Swapper<T, 2> {
static T swap(T aValue) {
#if defined(MOZ_HAVE_BUILTIN_BYTESWAP16)
return MOZ_HAVE_BUILTIN_BYTESWAP16(aValue);
#else
return T(((aValue & 0x00ff) << 8) | ((aValue & 0xff00) >> 8));
#endif
}
};
template <typename T>
struct Swapper<T, 4> {
static T swap(T aValue) {
#if defined(__clang__) || defined(__GNUC__)
return T(__builtin_bswap32(aValue));
#elif defined(_MSC_VER)
return T(_byteswap_ulong(aValue));
#else
return T(((aValue & 0x000000ffU) << 24) |
((aValue & 0x0000ff00U) << 8) |
((aValue & 0x00ff0000U) >> 8) |
((aValue & 0xff000000U) >> 24));
#endif
}
};
template <typename T>
struct Swapper<T, 8> {
static inline T swap(T aValue) {
#if defined(__clang__) || defined(__GNUC__)
return T(__builtin_bswap64(aValue));
#elif defined(_MSC_VER)
return T(_byteswap_uint64(aValue));
#else
return T(((aValue & 0x00000000000000ffULL) << 56) |
((aValue & 0x000000000000ff00ULL) << 40) |
((aValue & 0x0000000000ff0000ULL) << 24) |
((aValue & 0x00000000ff000000ULL) << 8) |
((aValue & 0x000000ff00000000ULL) >> 8) |
((aValue & 0x0000ff0000000000ULL) >> 24) |
((aValue & 0x00ff000000000000ULL) >> 40) |
((aValue & 0xff00000000000000ULL) >> 56));
#endif
}
};
template <Endianness ThisEndian>
class Endian {
public:
template <typename T>
static T swapToBigEndian(T aValue) {
return maybeSwap<ThisEndian, Big>(aValue);
}
private:
/**
* Return |aValue| converted from SourceEndian encoding to DestEndian
* encoding.
*/
template <Endianness SourceEndian, Endianness DestEndian, typename T>
static inline T maybeSwap(T aValue) {
if (SourceEndian == DestEndian) {
return aValue;
}
return Swapper<T>::swap(aValue);
}
};
class NativeEndian final : public Endian<MOZ_NATIVE_ENDIANNESS> {
private:
typedef Endian<MOZ_NATIVE_ENDIANNESS> super;
public:
using super::swapToBigEndian;
};
} // namespace
static inline uint32_t
SHA_ROTL(uint32_t aT, uint32_t aN) {
MOZ_ASSERT(aN < 32);
return (aT << aN) | (aT >> (32 - aN));
}
static void
shaCompress(volatile unsigned *aX, const uint32_t *aBuf);
#define SHA_F1(X, Y, Z) ((((Y) ^ (Z)) & (X)) ^ (Z))
#define SHA_F2(X, Y, Z) ((X) ^ (Y) ^ (Z))
#define SHA_F3(X, Y, Z) (((X) & (Y)) | ((Z) & ((X) | (Y))))
#define SHA_F4(X, Y, Z) ((X) ^ (Y) ^ (Z))
#define SHA_MIX(n, a, b, c) XW(n) = SHA_ROTL(XW(a) ^ XW(b) ^ XW(c) ^ XW(n), 1)
SHA1Sum::SHA1Sum()
: mSize(0),
mDone(false) {
// Initialize H with constants from FIPS180-1.
mH[0] = 0x67452301L;
mH[1] = 0xefcdab89L;
mH[2] = 0x98badcfeL;
mH[3] = 0x10325476L;
mH[4] = 0xc3d2e1f0L;
}
/*
* Explanation of H array and index values:
*
* The context's H array is actually the concatenation of two arrays
* defined by SHA1, the H array of state variables (5 elements),
* and the W array of intermediate values, of which there are 16 elements.
* The W array starts at H[5], that is W[0] is H[5].
* Although these values are defined as 32-bit values, we use 64-bit
* variables to hold them because the AMD64 stores 64 bit values in
* memory MUCH faster than it stores any smaller values.
*
* Rather than passing the context structure to shaCompress, we pass
* this combined array of H and W values. We do not pass the address
* of the first element of this array, but rather pass the address of an
* element in the middle of the array, element X. Presently X[0] is H[11].
* So we pass the address of H[11] as the address of array X to shaCompress.
* Then shaCompress accesses the members of the array using positive AND
* negative indexes.
*
* Pictorially: (each element is 8 bytes)
* H | H0 H1 H2 H3 H4 W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 Wa Wb Wc Wd We Wf |
* X |-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 |
*
* The byte offset from X[0] to any member of H and W is always
* representable in a signed 8-bit value, which will be encoded
* as a single byte offset in the X86-64 instruction set.
* If we didn't pass the address of H[11], and instead passed the
* address of H[0], the offsets to elements H[16] and above would be
* greater than 127, not representable in a signed 8-bit value, and the
* x86-64 instruction set would encode every such offset as a 32-bit
* signed number in each instruction that accessed element H[16] or
* higher. This results in much bigger and slower code.
*/
#define H2X 11 /* X[0] is H[11], and H[0] is X[-11] */
#define W2X 6 /* X[0] is W[6], and W[0] is X[-6] */
/*
* SHA: Add data to context.
*/
void SHA1Sum::update(const void *aData, uint32_t aLen) {
MOZ_ASSERT(!mDone, "SHA1Sum can only be used to compute a single hash.");
const uint8_t *data = static_cast<const uint8_t *>(aData);
if (aLen == 0) {
return;
}
/* Accumulate the byte count. */
unsigned int lenB = static_cast<unsigned int>(mSize) & 63U;
mSize += aLen;
/* Read the data into W and process blocks as they get full. */
unsigned int togo;
if (lenB > 0) {
togo = 64U - lenB;
if (aLen < togo) {
togo = aLen;
}
memcpy(mU.mB + lenB, data, togo);
aLen -= togo;
data += togo;
lenB = (lenB + togo) & 63U;
if (!lenB) {
shaCompress(&mH[H2X], mU.mW);
}
}
while (aLen >= 64U) {
aLen -= 64U;
shaCompress(&mH[H2X], reinterpret_cast<const uint32_t *>(data));
data += 64U;
}
if (aLen > 0) {
memcpy(mU.mB, data, aLen);
}
}
/*
* SHA: Generate hash value
*/
void SHA1Sum::finish(SHA1Sum::Hash &aHashOut) {
MOZ_ASSERT(!mDone, "SHA1Sum can only be used to compute a single hash.");
uint64_t size = mSize;
uint32_t lenB = uint32_t(size) & 63;
static const uint8_t bulk_pad[64] =
{0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/* Pad with a binary 1 (e.g. 0x80), then zeroes, then length in bits. */
update(bulk_pad, (((55 + 64) - lenB) & 63) + 1);
MOZ_ASSERT((uint32_t(mSize) & 63) == 56);
/* Convert size from bytes to bits. */
size <<= 3;
mU.mW[14] = NativeEndian::swapToBigEndian(uint32_t(size >> 32));
mU.mW[15] = NativeEndian::swapToBigEndian(uint32_t(size));
shaCompress(&mH[H2X], mU.mW);
/* Output hash. */
mU.mW[0] = NativeEndian::swapToBigEndian(mH[0]);
mU.mW[1] = NativeEndian::swapToBigEndian(mH[1]);
mU.mW[2] = NativeEndian::swapToBigEndian(mH[2]);
mU.mW[3] = NativeEndian::swapToBigEndian(mH[3]);
mU.mW[4] = NativeEndian::swapToBigEndian(mH[4]);
memcpy(aHashOut, mU.mW, 20);
mDone = true;
}
/*
* SHA: Compression function, unrolled.
*
* Some operations in shaCompress are done as 5 groups of 16 operations.
* Others are done as 4 groups of 20 operations.
* The code below shows that structure.
*
* The functions that compute the new values of the 5 state variables
* A-E are done in 4 groups of 20 operations (or you may also think
* of them as being done in 16 groups of 5 operations). They are
* done by the SHA_RNDx macros below, in the right column.
*
* The functions that set the 16 values of the W array are done in
* 5 groups of 16 operations. The first group is done by the
* LOAD macros below, the latter 4 groups are done by SHA_MIX below,
* in the left column.
*
* gcc's optimizer observes that each member of the W array is assigned
* a value 5 times in this code. It reduces the number of store
* operations done to the W array in the context (that is, in the X array)
* by creating a W array on the stack, and storing the W values there for
* the first 4 groups of operations on W, and storing the values in the
* context's W array only in the fifth group. This is undesirable.
* It is MUCH bigger code than simply using the context's W array, because
* all the offsets to the W array in the stack are 32-bit signed offsets,
* and it is no faster than storing the values in the context's W array.
*
* The original code for sha_fast.c prevented this creation of a separate
* W array in the stack by creating a W array of 80 members, each of
* whose elements is assigned only once. It also separated the computations
* of the W array values and the computations of the values for the 5
* state variables into two separate passes, W's, then A-E's so that the
* second pass could be done all in registers (except for accessing the W
* array) on machines with fewer registers. The method is suboptimal
* for machines with enough registers to do it all in one pass, and it
* necessitates using many instructions with 32-bit offsets.
*
* This code eliminates the separate W array on the stack by a completely
* different means: by declaring the X array volatile. This prevents
* the optimizer from trying to reduce the use of the X array by the
* creation of a MORE expensive W array on the stack. The result is
* that all instructions use signed 8-bit offsets and not 32-bit offsets.
*
* The combination of this code and the -O3 optimizer flag on GCC 3.4.3
* results in code that is 3 times faster than the previous NSS sha_fast
* code on AMD64.
*/
static void
shaCompress(volatile unsigned *aX, const uint32_t *aBuf) {
unsigned A, B, C, D, E;
#define XH(n) aX[n - H2X]
#define XW(n) aX[n - W2X]
#define K0 0x5a827999L
#define K1 0x6ed9eba1L
#define K2 0x8f1bbcdcL
#define K3 0xca62c1d6L
#define SHA_RND1(a, b, c, d, e, n) \
a = SHA_ROTL(b, 5) + SHA_F1(c, d, e) + a + XW(n) + K0; \
c = SHA_ROTL(c, 30)
#define SHA_RND2(a, b, c, d, e, n) \
a = SHA_ROTL(b, 5) + SHA_F2(c, d, e) + a + XW(n) + K1; \
c = SHA_ROTL(c, 30)
#define SHA_RND3(a, b, c, d, e, n) \
a = SHA_ROTL(b, 5) + SHA_F3(c, d, e) + a + XW(n) + K2; \
c = SHA_ROTL(c, 30)
#define SHA_RND4(a, b, c, d, e, n) \
a = SHA_ROTL(b, 5) + SHA_F4(c, d, e) + a + XW(n) + K3; \
c = SHA_ROTL(c, 30)
#define LOAD(n) XW(n) = NativeEndian::swapToBigEndian(aBuf[n])
A = XH(0);
B = XH(1);
C = XH(2);
D = XH(3);
E = XH(4);
LOAD(0);
SHA_RND1(E, A, B, C, D, 0);
LOAD(1);
SHA_RND1(D, E, A, B, C, 1);
LOAD(2);
SHA_RND1(C, D, E, A, B, 2);
LOAD(3);
SHA_RND1(B, C, D, E, A, 3);
LOAD(4);
SHA_RND1(A, B, C, D, E, 4);
LOAD(5);
SHA_RND1(E, A, B, C, D, 5);
LOAD(6);
SHA_RND1(D, E, A, B, C, 6);
LOAD(7);
SHA_RND1(C, D, E, A, B, 7);
LOAD(8);
SHA_RND1(B, C, D, E, A, 8);
LOAD(9);
SHA_RND1(A, B, C, D, E, 9);
LOAD(10);
SHA_RND1(E, A, B, C, D, 10);
LOAD(11);
SHA_RND1(D, E, A, B, C, 11);
LOAD(12);
SHA_RND1(C, D, E, A, B, 12);
LOAD(13);
SHA_RND1(B, C, D, E, A, 13);
LOAD(14);
SHA_RND1(A, B, C, D, E, 14);
LOAD(15);
SHA_RND1(E, A, B, C, D, 15);
SHA_MIX(0, 13, 8, 2);
SHA_RND1(D, E, A, B, C, 0);
SHA_MIX(1, 14, 9, 3);
SHA_RND1(C, D, E, A, B, 1);
SHA_MIX(2, 15, 10, 4);
SHA_RND1(B, C, D, E, A, 2);
SHA_MIX(3, 0, 11, 5);
SHA_RND1(A, B, C, D, E, 3);
SHA_MIX(4, 1, 12, 6);
SHA_RND2(E, A, B, C, D, 4);
SHA_MIX(5, 2, 13, 7);
SHA_RND2(D, E, A, B, C, 5);
SHA_MIX(6, 3, 14, 8);
SHA_RND2(C, D, E, A, B, 6);
SHA_MIX(7, 4, 15, 9);
SHA_RND2(B, C, D, E, A, 7);
SHA_MIX(8, 5, 0, 10);
SHA_RND2(A, B, C, D, E, 8);
SHA_MIX(9, 6, 1, 11);
SHA_RND2(E, A, B, C, D, 9);
SHA_MIX(10, 7, 2, 12);
SHA_RND2(D, E, A, B, C, 10);
SHA_MIX(11, 8, 3, 13);
SHA_RND2(C, D, E, A, B, 11);
SHA_MIX(12, 9, 4, 14);
SHA_RND2(B, C, D, E, A, 12);
SHA_MIX(13, 10, 5, 15);
SHA_RND2(A, B, C, D, E, 13);
SHA_MIX(14, 11, 6, 0);
SHA_RND2(E, A, B, C, D, 14);
SHA_MIX(15, 12, 7, 1);
SHA_RND2(D, E, A, B, C, 15);
SHA_MIX(0, 13, 8, 2);
SHA_RND2(C, D, E, A, B, 0);
SHA_MIX(1, 14, 9, 3);
SHA_RND2(B, C, D, E, A, 1);
SHA_MIX(2, 15, 10, 4);
SHA_RND2(A, B, C, D, E, 2);
SHA_MIX(3, 0, 11, 5);
SHA_RND2(E, A, B, C, D, 3);
SHA_MIX(4, 1, 12, 6);
SHA_RND2(D, E, A, B, C, 4);
SHA_MIX(5, 2, 13, 7);
SHA_RND2(C, D, E, A, B, 5);
SHA_MIX(6, 3, 14, 8);
SHA_RND2(B, C, D, E, A, 6);
SHA_MIX(7, 4, 15, 9);
SHA_RND2(A, B, C, D, E, 7);
SHA_MIX(8, 5, 0, 10);
SHA_RND3(E, A, B, C, D, 8);
SHA_MIX(9, 6, 1, 11);
SHA_RND3(D, E, A, B, C, 9);
SHA_MIX(10, 7, 2, 12);
SHA_RND3(C, D, E, A, B, 10);
SHA_MIX(11, 8, 3, 13);
SHA_RND3(B, C, D, E, A, 11);
SHA_MIX(12, 9, 4, 14);
SHA_RND3(A, B, C, D, E, 12);
SHA_MIX(13, 10, 5, 15);
SHA_RND3(E, A, B, C, D, 13);
SHA_MIX(14, 11, 6, 0);
SHA_RND3(D, E, A, B, C, 14);
SHA_MIX(15, 12, 7, 1);
SHA_RND3(C, D, E, A, B, 15);
SHA_MIX(0, 13, 8, 2);
SHA_RND3(B, C, D, E, A, 0);
SHA_MIX(1, 14, 9, 3);
SHA_RND3(A, B, C, D, E, 1);
SHA_MIX(2, 15, 10, 4);
SHA_RND3(E, A, B, C, D, 2);
SHA_MIX(3, 0, 11, 5);
SHA_RND3(D, E, A, B, C, 3);
SHA_MIX(4, 1, 12, 6);
SHA_RND3(C, D, E, A, B, 4);
SHA_MIX(5, 2, 13, 7);
SHA_RND3(B, C, D, E, A, 5);
SHA_MIX(6, 3, 14, 8);
SHA_RND3(A, B, C, D, E, 6);
SHA_MIX(7, 4, 15, 9);
SHA_RND3(E, A, B, C, D, 7);
SHA_MIX(8, 5, 0, 10);
SHA_RND3(D, E, A, B, C, 8);
SHA_MIX(9, 6, 1, 11);
SHA_RND3(C, D, E, A, B, 9);
SHA_MIX(10, 7, 2, 12);
SHA_RND3(B, C, D, E, A, 10);
SHA_MIX(11, 8, 3, 13);
SHA_RND3(A, B, C, D, E, 11);
SHA_MIX(12, 9, 4, 14);
SHA_RND4(E, A, B, C, D, 12);
SHA_MIX(13, 10, 5, 15);
SHA_RND4(D, E, A, B, C, 13);
SHA_MIX(14, 11, 6, 0);
SHA_RND4(C, D, E, A, B, 14);
SHA_MIX(15, 12, 7, 1);
SHA_RND4(B, C, D, E, A, 15);
SHA_MIX(0, 13, 8, 2);
SHA_RND4(A, B, C, D, E, 0);
SHA_MIX(1, 14, 9, 3);
SHA_RND4(E, A, B, C, D, 1);
SHA_MIX(2, 15, 10, 4);
SHA_RND4(D, E, A, B, C, 2);
SHA_MIX(3, 0, 11, 5);
SHA_RND4(C, D, E, A, B, 3);
SHA_MIX(4, 1, 12, 6);
SHA_RND4(B, C, D, E, A, 4);
SHA_MIX(5, 2, 13, 7);
SHA_RND4(A, B, C, D, E, 5);
SHA_MIX(6, 3, 14, 8);
SHA_RND4(E, A, B, C, D, 6);
SHA_MIX(7, 4, 15, 9);
SHA_RND4(D, E, A, B, C, 7);
SHA_MIX(8, 5, 0, 10);
SHA_RND4(C, D, E, A, B, 8);
SHA_MIX(9, 6, 1, 11);
SHA_RND4(B, C, D, E, A, 9);
SHA_MIX(10, 7, 2, 12);
SHA_RND4(A, B, C, D, E, 10);
SHA_MIX(11, 8, 3, 13);
SHA_RND4(E, A, B, C, D, 11);
SHA_MIX(12, 9, 4, 14);
SHA_RND4(D, E, A, B, C, 12);
SHA_MIX(13, 10, 5, 15);
SHA_RND4(C, D, E, A, B, 13);
SHA_MIX(14, 11, 6, 0);
SHA_RND4(B, C, D, E, A, 14);
SHA_MIX(15, 12, 7, 1);
SHA_RND4(A, B, C, D, E, 15);
XH(0) += A;
XH(1) += B;
XH(2) += C;
XH(3) += D;
XH(4) += E;
}
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,69 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Simple class for computing SHA1. */
#pragma once
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
//#include "mozilla/Types.h"
#include <stddef.h>
#include <stdint.h>
#ifdef MFBT_API
#undef MFBT_API
#endif
#define MFBT_API
namespace se {
/**
* This class computes the SHA1 hash of a byte sequence, or of the concatenation
* of multiple sequences. For example, computing the SHA1 of two sequences of
* bytes could be done as follows:
*
* void SHA1(const uint8_t* buf1, uint32_t size1,
* const uint8_t* buf2, uint32_t size2,
* SHA1Sum::Hash& hash)
* {
* SHA1Sum s;
* s.update(buf1, size1);
* s.update(buf2, size2);
* s.finish(hash);
* }
*
* The finish method may only be called once and cannot be followed by calls
* to update.
*/
class SHA1Sum {
union {
uint32_t mW[16]; /* input buffer */
uint8_t mB[64];
} mU;
uint64_t mSize; /* count of hashed bytes. */
unsigned mH[22]; /* 5 state variables, 16 tmp values, 1 extra */
bool mDone;
public:
MFBT_API SHA1Sum();
static const size_t kHashSize = 20;
typedef uint8_t Hash[kHashSize];
/* Add len bytes of dataIn to the data sequence being hashed. */
MFBT_API void update(const void *aData, uint32_t aLength);
/* Compute the final hash of all data into hashOut. */
MFBT_API void finish(SHA1Sum::Hash &aHashOut);
};
} /* namespace se */
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,188 @@
#ifndef SRC_BASE64_H_
#define SRC_BASE64_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "util.h"
#include <stddef.h>
namespace node {
//// Base 64 ////
#define base64_encoded_size(size) ((size + 2 - ((size + 2) % 3)) / 3 * 4)
// Doesn't check for padding at the end. Can be 1-2 bytes over.
static inline size_t base64_decoded_size_fast(size_t size) {
size_t remainder = size % 4;
size = (size / 4) * 3;
if (remainder) {
if (size == 0 && remainder == 1) {
// special case: 1-byte input cannot be decoded
size = 0;
} else {
// non-padded input, add 1 or 2 extra bytes
size += 1 + (remainder == 3);
}
}
return size;
}
template <typename TypeName>
size_t base64_decoded_size(const TypeName *src, size_t size) {
if (size == 0)
return 0;
if (src[size - 1] == '=')
size--;
if (size > 0 && src[size - 1] == '=')
size--;
return base64_decoded_size_fast(size);
}
extern const int8_t unbase64_table[256];
#define unbase64(x) \
static_cast<uint8_t>(unbase64_table[static_cast<uint8_t>(x)])
template <typename TypeName>
bool base64_decode_group_slow(char *const dst, const size_t dstlen,
const TypeName *const src, const size_t srclen,
size_t *const i, size_t *const k) {
uint8_t hi;
uint8_t lo;
#define V(expr) \
for (;;) { \
const uint8_t c = src[*i]; \
lo = unbase64(c); \
*i += 1; \
if (lo < 64) \
break; /* Legal character. */ \
if (c == '=' || *i >= srclen) \
return false; /* Stop decoding. */ \
} \
expr; \
if (*i >= srclen) \
return false; \
if (*k >= dstlen) \
return false; \
hi = lo;
V(do {} while (false) /* empty*/);
V(dst[(*k)++] = ((hi & 0x3F) << 2) | ((lo & 0x30) >> 4));
V(dst[(*k)++] = ((hi & 0x0F) << 4) | ((lo & 0x3C) >> 2));
V(dst[(*k)++] = ((hi & 0x03) << 6) | ((lo & 0x3F) >> 0));
#undef V
return true; // Continue decoding.
}
template <typename TypeName>
size_t base64_decode_fast(char *const dst, const size_t dstlen,
const TypeName *const src, const size_t srclen,
const size_t decoded_size) {
const size_t available = dstlen < decoded_size ? dstlen : decoded_size;
const size_t max_k = available / 3 * 3;
size_t max_i = srclen / 4 * 4;
size_t i = 0;
size_t k = 0;
while (i < max_i && k < max_k) {
const uint32_t v =
unbase64(src[i + 0]) << 24 |
unbase64(src[i + 1]) << 16 |
unbase64(src[i + 2]) << 8 |
unbase64(src[i + 3]);
// If MSB is set, input contains whitespace or is not valid base64.
if (v & 0x80808080) {
if (!base64_decode_group_slow(dst, dstlen, src, srclen, &i, &k))
return k;
max_i = i + (srclen - i) / 4 * 4; // Align max_i again.
} else {
dst[k + 0] = ((v >> 22) & 0xFC) | ((v >> 20) & 0x03);
dst[k + 1] = ((v >> 12) & 0xF0) | ((v >> 10) & 0x0F);
dst[k + 2] = ((v >> 2) & 0xC0) | ((v >> 0) & 0x3F);
i += 4;
k += 3;
}
}
if (i < srclen && k < dstlen) {
base64_decode_group_slow(dst, dstlen, src, srclen, &i, &k);
}
return k;
}
template <typename TypeName>
size_t base64_decode(char *const dst, const size_t dstlen,
const TypeName *const src, const size_t srclen) {
const size_t decoded_size = base64_decoded_size(src, srclen);
return base64_decode_fast(dst, dstlen, src, srclen, decoded_size);
}
static size_t base64_encode(const char *src,
size_t slen,
char *dst,
size_t dlen) {
// We know how much we'll write, just make sure that there's space.
CHECK(dlen >= base64_encoded_size(slen) &&
"not enough space provided for base64 encode");
dlen = base64_encoded_size(slen);
unsigned a;
unsigned b;
unsigned c;
unsigned i;
unsigned k;
unsigned n;
static const char table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
i = 0;
k = 0;
n = static_cast<unsigned>(slen / 3 * 3);
while (i < n) {
a = src[i + 0] & 0xff;
b = src[i + 1] & 0xff;
c = src[i + 2] & 0xff;
dst[k + 0] = table[a >> 2];
dst[k + 1] = table[((a & 3) << 4) | (b >> 4)];
dst[k + 2] = table[((b & 0x0f) << 2) | (c >> 6)];
dst[k + 3] = table[c & 0x3f];
i += 3;
k += 4;
}
if (n != slen) {
switch (slen - n) {
case 1:
a = src[i + 0] & 0xff;
dst[k + 0] = table[a >> 2];
dst[k + 1] = table[(a & 3) << 4];
dst[k + 2] = '=';
dst[k + 3] = '=';
break;
case 2:
a = src[i + 0] & 0xff;
b = src[i + 1] & 0xff;
dst[k + 0] = table[a >> 2];
dst[k + 1] = table[((a & 3) << 4) | (b >> 4)];
dst[k + 2] = table[(b & 0x0f) << 2];
dst[k + 3] = '=';
break;
}
}
return dlen;
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_BASE64_H_

View File

@@ -0,0 +1,117 @@
#include "env.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "uv.h"
using namespace v8;
namespace node {
void Environment::Start(int argc,
const char *const *argv,
int exec_argc,
const char *const *exec_argv,
bool start_profiler_idle_notifier) {
HandleScope handle_scope(isolate());
Context::Scope context_scope(context());
// uv_check_init(event_loop(), immediate_check_handle());
// uv_unref(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));
//
// uv_idle_init(event_loop(), immediate_idle_handle());
//
// // Inform V8's CPU profiler when we're idle. The profiler is sampling-based
// // but not all samples are created equal; mark the wall clock time spent in
// // epoll_wait() and friends so profiling tools can filter it out. The samples
// // still end up in v8.log but with state=IDLE rather than state=EXTERNAL.
// // REFINE(bnoordhuis) Depends on a libuv implementation detail that we should
// // probably fortify in the API contract, namely that the last started prepare
// // or check watcher runs first. It's not 100% foolproof; if an add-on starts
// // a prepare or check watcher after us, any samples attributed to its callback
// // will be recorded with state=IDLE.
// uv_prepare_init(event_loop(), &idle_prepare_handle_);
// uv_check_init(event_loop(), &idle_check_handle_);
// uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
// uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
//
// uv_timer_init(event_loop(), destroy_ids_timer_handle());
//
// auto close_and_finish = [](Environment* env, uv_handle_t* handle, void* arg) {
// handle->data = env;
//
// uv_close(handle, [](uv_handle_t* handle) {
// static_cast<Environment*>(handle->data)->FinishHandleCleanup(handle);
// });
// };
//
// RegisterHandleCleanup(
// reinterpret_cast<uv_handle_t*>(immediate_check_handle()),
// close_and_finish,
// nullptr);
// RegisterHandleCleanup(
// reinterpret_cast<uv_handle_t*>(immediate_idle_handle()),
// close_and_finish,
// nullptr);
// RegisterHandleCleanup(
// reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_),
// close_and_finish,
// nullptr);
// RegisterHandleCleanup(
// reinterpret_cast<uv_handle_t*>(&idle_check_handle_),
// close_and_finish,
// nullptr);
if (start_profiler_idle_notifier) {
StartProfilerIdleNotifier();
}
auto process_template = FunctionTemplate::New(isolate());
process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process"));
auto process_object =
process_template->GetFunction(context()).ToLocalChecked()->NewInstance(context()).ToLocalChecked();
set_process_object(process_object);
SetupProcessObject(this, argc, argv, exec_argc, exec_argv);
// LoadAsyncWrapperInfo(this);
}
void Environment::AssignToContext(v8::Local<v8::Context> context) {
context->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex, this);
}
void Environment::CleanupHandles() {
// while (HandleCleanup* hc = handle_cleanup_queue_.PopFront()) {
// handle_cleanup_waiting_++;
// hc->cb_(this, hc->handle_, hc->arg_);
// delete hc;
// }
//
// while (handle_cleanup_waiting_ != 0)
// uv_run(event_loop(), UV_RUN_ONCE);
//
// while (handle_cleanup_waiting_ != 0)
// uv_run(event_loop(), UV_RUN_ONCE);
}
void Environment::StartProfilerIdleNotifier() {
// uv_prepare_start(&idle_prepare_handle_, [](uv_prepare_t* handle) {
// Environment* env = ContainerOf(&Environment::idle_prepare_handle_, handle);
// env->isolate()->GetCpuProfiler()->SetIdle(true);
// });
//
// uv_check_start(&idle_check_handle_, [](uv_check_t* handle) {
// Environment* env = ContainerOf(&Environment::idle_check_handle_, handle);
// env->isolate()->GetCpuProfiler()->SetIdle(false);
// });
}
void Environment::StopProfilerIdleNotifier() {
// uv_prepare_stop(&idle_prepare_handle_);
// uv_check_stop(&idle_check_handle_);
}
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,618 @@
#pragma once
// clang-format off
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "inspector_agent.h"
#include "v8.h"
#include "uv.h"
#include "util.h"
#include "node.h"
namespace node {
// Pick an index that's hopefully out of the way when we're embedded inside
// another application. Performance-wise or memory-wise it doesn't matter:
// Context::SetAlignedPointerInEmbedderData() is backed by a FixedArray,
// worst case we pay a one-time penalty for resizing the array.
#ifndef NODE_CONTEXT_EMBEDDER_DATA_INDEX
#define NODE_CONTEXT_EMBEDDER_DATA_INDEX 32
#endif
// PER_ISOLATE_* macros: We have a lot of per-isolate properties
// and adding and maintaining their getters and setters by hand would be
// difficult so let's make the preprocessor generate them for us.
//
// In each macro, `V` is expected to be the name of a macro or function which
// accepts the number of arguments provided in each tuple in the macro body,
// typically two. The named function will be invoked against each tuple.
//
// Make sure that any macro V defined for use with the PER_ISOLATE_* macros is
// undefined again after use.
// Private symbols are per-isolate primitives but Environment proxies them
// for the sake of convenience. Strings should be ASCII-only and have a
// "node:" prefix to avoid name clashes with third-party code.
#define PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) \
V(alpn_buffer_private_symbol, "node:alpnBuffer") \
V(arrow_message_private_symbol, "node:arrowMessage") \
V(contextify_context_private_symbol, "node:contextify:context") \
V(contextify_global_private_symbol, "node:contextify:global") \
V(inspector_delegate_private_symbol, "node:inspector:delegate") \
V(decorated_private_symbol, "node:decorated") \
V(npn_buffer_private_symbol, "node:npnBuffer") \
V(processed_private_symbol, "node:processed") \
V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer")
// Strings are per-isolate primitives but Environment proxies them
// for the sake of convenience. Strings should be ASCII-only.
#define PER_ISOLATE_STRING_PROPERTIES(V) \
V(address_string, "address") \
V(args_string, "args") \
V(async, "async") \
V(buffer_string, "buffer") \
V(bytes_string, "bytes") \
V(bytes_parsed_string, "bytesParsed") \
V(bytes_read_string, "bytesRead") \
V(cached_data_string, "cachedData") \
V(cached_data_produced_string, "cachedDataProduced") \
V(cached_data_rejected_string, "cachedDataRejected") \
V(callback_string, "callback") \
V(change_string, "change") \
V(channel_string, "channel") \
V(oncertcb_string, "oncertcb") \
V(onclose_string, "_onclose") \
V(code_string, "code") \
V(configurable_string, "configurable") \
V(cwd_string, "cwd") \
V(dest_string, "dest") \
V(detached_string, "detached") \
V(disposed_string, "_disposed") \
V(dns_a_string, "A") \
V(dns_aaaa_string, "AAAA") \
V(dns_cname_string, "CNAME") \
V(dns_mx_string, "MX") \
V(dns_naptr_string, "NAPTR") \
V(dns_ns_string, "NS") \
V(dns_ptr_string, "PTR") \
V(dns_soa_string, "SOA") \
V(dns_srv_string, "SRV") \
V(dns_txt_string, "TXT") \
V(domain_string, "domain") \
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
V(exchange_string, "exchange") \
V(enumerable_string, "enumerable") \
V(idle_string, "idle") \
V(irq_string, "irq") \
V(encoding_string, "encoding") \
V(enter_string, "enter") \
V(entries_string, "entries") \
V(env_pairs_string, "envPairs") \
V(errno_string, "errno") \
V(error_string, "error") \
V(events_string, "_events") \
V(exiting_string, "_exiting") \
V(exit_code_string, "exitCode") \
V(exit_string, "exit") \
V(expire_string, "expire") \
V(exponent_string, "exponent") \
V(exports_string, "exports") \
V(ext_key_usage_string, "ext_key_usage") \
V(external_stream_string, "_externalStream") \
V(family_string, "family") \
V(fatal_exception_string, "_fatalException") \
V(fd_string, "fd") \
V(file_string, "file") \
V(fingerprint_string, "fingerprint") \
V(flags_string, "flags") \
V(get_string, "get") \
V(get_data_clone_error_string, "_getDataCloneError") \
V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \
V(gid_string, "gid") \
V(handle_string, "handle") \
V(homedir_string, "homedir") \
V(hostmaster_string, "hostmaster") \
V(ignore_string, "ignore") \
V(immediate_callback_string, "_immediateCallback") \
V(infoaccess_string, "infoAccess") \
V(inherit_string, "inherit") \
V(input_string, "input") \
V(internal_string, "internal") \
V(ipv4_string, "IPv4") \
V(ipv6_string, "IPv6") \
V(isalive_string, "isAlive") \
V(isclosing_string, "isClosing") \
V(issuer_string, "issuer") \
V(issuercert_string, "issuerCertificate") \
V(kill_signal_string, "killSignal") \
V(length_string, "length") \
V(mac_string, "mac") \
V(max_buffer_string, "maxBuffer") \
V(message_string, "message") \
V(minttl_string, "minttl") \
V(model_string, "model") \
V(modulus_string, "modulus") \
V(name_string, "name") \
V(netmask_string, "netmask") \
V(nice_string, "nice") \
V(nsname_string, "nsname") \
V(ocsp_request_string, "OCSPRequest") \
V(onchange_string, "onchange") \
V(onclienthello_string, "onclienthello") \
V(oncomplete_string, "oncomplete") \
V(onconnection_string, "onconnection") \
V(ondone_string, "ondone") \
V(onerror_string, "onerror") \
V(onexit_string, "onexit") \
V(onhandshakedone_string, "onhandshakedone") \
V(onhandshakestart_string, "onhandshakestart") \
V(onmessage_string, "onmessage") \
V(onnewsession_string, "onnewsession") \
V(onnewsessiondone_string, "onnewsessiondone") \
V(onocspresponse_string, "onocspresponse") \
V(onread_string, "onread") \
V(onreadstart_string, "onreadstart") \
V(onreadstop_string, "onreadstop") \
V(onselect_string, "onselect") \
V(onshutdown_string, "onshutdown") \
V(onsignal_string, "onsignal") \
V(onstop_string, "onstop") \
V(onwrite_string, "onwrite") \
V(output_string, "output") \
V(order_string, "order") \
V(owner_string, "owner") \
V(parse_error_string, "Parse Error") \
V(path_string, "path") \
V(pbkdf2_error_string, "PBKDF2 Error") \
V(pid_string, "pid") \
V(pipe_string, "pipe") \
V(port_string, "port") \
V(preference_string, "preference") \
V(priority_string, "priority") \
V(produce_cached_data_string, "produceCachedData") \
V(raw_string, "raw") \
V(read_host_object_string, "_readHostObject") \
V(readable_string, "readable") \
V(received_shutdown_string, "receivedShutdown") \
V(refresh_string, "refresh") \
V(regexp_string, "regexp") \
V(rename_string, "rename") \
V(replacement_string, "replacement") \
V(retry_string, "retry") \
V(serial_string, "serial") \
V(scopeid_string, "scopeid") \
V(sent_shutdown_string, "sentShutdown") \
V(serial_number_string, "serialNumber") \
V(service_string, "service") \
V(servername_string, "servername") \
V(session_id_string, "sessionId") \
V(set_string, "set") \
V(shell_string, "shell") \
V(signal_string, "signal") \
V(size_string, "size") \
V(sni_context_err_string, "Invalid SNI context") \
V(sni_context_string, "sni_context") \
V(speed_string, "speed") \
V(stack_string, "stack") \
V(status_string, "status") \
V(stdio_string, "stdio") \
V(subject_string, "subject") \
V(subjectaltname_string, "subjectaltname") \
V(sys_string, "sys") \
V(syscall_string, "syscall") \
V(tick_callback_string, "_tickCallback") \
V(tick_domain_cb_string, "_tickDomainCallback") \
V(ticketkeycallback_string, "onticketkeycallback") \
V(timeout_string, "timeout") \
V(times_string, "times") \
V(tls_ticket_string, "tlsTicket") \
V(ttl_string, "ttl") \
V(type_string, "type") \
V(uid_string, "uid") \
V(unknown_string, "<unknown>") \
V(user_string, "user") \
V(username_string, "username") \
V(valid_from_string, "valid_from") \
V(valid_to_string, "valid_to") \
V(value_string, "value") \
V(verify_error_string, "verifyError") \
V(version_string, "version") \
V(weight_string, "weight") \
V(windows_verbatim_arguments_string, "windowsVerbatimArguments") \
V(wrap_string, "wrap") \
V(writable_string, "writable") \
V(write_host_object_string, "_writeHostObject") \
V(write_queue_size_string, "writeQueueSize") \
V(x_forwarded_string, "x-forwarded-for") \
V(zero_return_string, "ZERO_RETURN")
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
V(as_external, v8::External) \
V(async_hooks_destroy_function, v8::Function) \
V(async_hooks_init_function, v8::Function) \
V(async_hooks_before_function, v8::Function) \
V(async_hooks_after_function, v8::Function) \
V(binding_cache_object, v8::Object) \
V(buffer_constructor_function, v8::Function) \
V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \
V(domain_array, v8::Array) \
V(domains_stack_array, v8::Array) \
V(jsstream_constructor_template, v8::FunctionTemplate) \
V(module_load_list_array, v8::Array) \
V(pbkdf2_constructor_template, v8::ObjectTemplate) \
V(pipe_constructor_template, v8::FunctionTemplate) \
V(process_object, v8::Object) \
V(promise_reject_function, v8::Function) \
V(promise_wrap_template, v8::ObjectTemplate) \
V(push_values_to_array_function, v8::Function) \
V(randombytes_constructor_template, v8::ObjectTemplate) \
V(script_context_constructor_template, v8::FunctionTemplate) \
V(script_data_constructor_function, v8::Function) \
V(secure_context_constructor_template, v8::FunctionTemplate) \
V(tcp_constructor_template, v8::FunctionTemplate) \
V(tick_callback_function, v8::Function) \
V(tls_wrap_constructor_function, v8::Function) \
V(tls_wrap_constructor_template, v8::FunctionTemplate) \
V(tty_constructor_template, v8::FunctionTemplate) \
V(udp_constructor_function, v8::Function) \
V(url_constructor_function, v8::Function) \
V(write_wrap_constructor_function, v8::Function)
class IsolateData {
public:
inline IsolateData(v8::Isolate *isolate, uv_loop_t *event_loop,
uint32_t *zero_fill_field = nullptr);
inline uv_loop_t *event_loop() const;
inline uint32_t *zero_fill_field() const;
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define V(TypeName, PropertyName) \
inline v8::Local<TypeName> PropertyName(v8::Isolate *isolate) const;
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VP
private:
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define V(TypeName, PropertyName) \
v8::Eternal<TypeName> PropertyName##_;
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VP
uv_loop_t *const event_loop_;
uint32_t *const zero_fill_field_;
NODE_DISALLOW_COPY_AND_ASSIGN(IsolateData);
};
class Environment {
public:
static const int kContextEmbedderDataIndex = NODE_CONTEXT_EMBEDDER_DATA_INDEX;
static inline Environment *GetCurrent(v8::Isolate *isolate);
static inline Environment *GetCurrent(v8::Local<v8::Context> context);
static inline Environment *GetCurrent(const v8::FunctionCallbackInfo<v8::Value> &info);
template <typename T>
static inline Environment *GetCurrent(const v8::PropertyCallbackInfo<T> &info);
inline Environment(IsolateData *isolate_data, v8::Local<v8::Context> context);
inline ~Environment();
void Start(int argc,
const char *const *argv,
int exec_argc,
const char *const *exec_argv,
bool start_profiler_idle_notifier);
void AssignToContext(v8::Local<v8::Context> context);
void CleanupHandles();
void StartProfilerIdleNotifier();
void StopProfilerIdleNotifier();
inline v8::Isolate *isolate() const {
return isolate_;
}
inline IsolateData *isolate_data() const {
return isolate_data_;
}
inline uv_loop_t *event_loop() const {
return isolate_data()->event_loop();
}
inline inspector::Agent *inspector_agent() {
return &inspector_agent_;
}
// Strings and private symbols are shared across shared contexts
// The getters simply proxy to the per-isolate primitive.
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define V(TypeName, PropertyName) \
inline v8::Local<TypeName> PropertyName() const;
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VP
#define V(PropertyName, TypeName) \
inline v8::Local<TypeName> PropertyName() const; \
inline void set_##PropertyName(v8::Local<TypeName> value);
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V
inline void ThrowError(const char *errmsg);
inline void ThrowTypeError(const char *errmsg);
inline void ThrowRangeError(const char *errmsg);
inline void ThrowErrnoException(int errorno,
const char *syscall = nullptr,
const char *message = nullptr,
const char *path = nullptr);
inline void ThrowUVException(int errorno,
const char *syscall = nullptr,
const char *message = nullptr,
const char *path = nullptr,
const char *dest = nullptr);
inline v8::Local<v8::FunctionTemplate>
NewFunctionTemplate(v8::FunctionCallback callback,
v8::Local<v8::Signature> signature =
v8::Local<v8::Signature>());
// Convenience methods for NewFunctionTemplate().
inline void SetMethod(v8::Local<v8::Object> that,
const char *name,
v8::FunctionCallback callback);
class AsyncCallbackScope {
public:
AsyncCallbackScope() = delete;
explicit AsyncCallbackScope(Environment *env);
~AsyncCallbackScope();
inline bool in_makecallback();
private:
Environment *env_;
NODE_DISALLOW_COPY_AND_ASSIGN(AsyncCallbackScope);
};
private:
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
const char *errmsg);
v8::Isolate *const isolate_;
IsolateData *const isolate_data_;
#define V(PropertyName, TypeName) \
v8::Persistent<TypeName> PropertyName##_;
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V
inspector::Agent inspector_agent_;
size_t makecallback_cntr_;
};
inline IsolateData::IsolateData(v8::Isolate *isolate, uv_loop_t *event_loop,
uint32_t *zero_fill_field) :
// Create string and private symbol properties as internalized one byte strings.
//
// Internalized because it makes property lookups a little faster and because
// the string is created in the old space straight away. It's going to end up
// in the old space sooner or later anyway but now it doesn't go through
// v8::Eternal's new space handling first.
//
// One byte because our strings are ASCII and we can safely skip V8's UTF-8
// decoding step. It's a one-time cost, but why pay it when you don't have to?
#define V(PropertyName, StringValue) \
PropertyName##_( \
isolate, \
v8::Private::New( \
isolate, \
v8::String::NewFromOneByte( \
isolate, \
reinterpret_cast<const uint8_t *>(StringValue), \
v8::NewStringType::kInternalized, \
sizeof(StringValue) - 1) \
.ToLocalChecked())),
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V)
#undef V
#define V(PropertyName, StringValue) \
PropertyName##_( \
isolate, \
v8::String::NewFromOneByte( \
isolate, \
reinterpret_cast<const uint8_t *>(StringValue), \
v8::NewStringType::kInternalized, \
sizeof(StringValue) - 1) \
.ToLocalChecked()),
PER_ISOLATE_STRING_PROPERTIES(V)
#undef V
event_loop_(event_loop),
zero_fill_field_(zero_fill_field) {
}
inline uv_loop_t *IsolateData::event_loop() const {
return event_loop_;
}
inline uint32_t *IsolateData::zero_fill_field() const {
return zero_fill_field_;
}
inline Environment *Environment::GetCurrent(v8::Isolate *isolate) {
return GetCurrent(isolate->GetCurrentContext());
}
inline Environment *Environment::GetCurrent(v8::Local<v8::Context> context) {
return static_cast<Environment *>(
context->GetAlignedPointerFromEmbedderData(kContextEmbedderDataIndex));
}
inline Environment *Environment::GetCurrent(
const v8::FunctionCallbackInfo<v8::Value> &info) {
CHECK(info.Data()->IsExternal());
return static_cast<Environment *>(info.Data().As<v8::External>()->Value());
}
template <typename T>
inline Environment *Environment::GetCurrent(
const v8::PropertyCallbackInfo<T> &info) {
CHECK(info.Data()->IsExternal());
// XXX(bnoordhuis) Work around a g++ 4.9.2 template type inferrer bug
// when the expression is written as info.Data().As<v8::External>().
v8::Local<v8::Value> data = info.Data();
return static_cast<Environment *>(data.As<v8::External>()->Value());
}
inline Environment::Environment(IsolateData *isolate_data, v8::Local<v8::Context> context)
: isolate_(context->GetIsolate()),
isolate_data_(isolate_data),
inspector_agent_(this),
makecallback_cntr_(0),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
v8::HandleScope handle_scope(isolate());
v8::Context::Scope context_scope(context);
set_as_external(v8::External::New(isolate(), this));
set_binding_cache_object(v8::Object::New(isolate()));
set_module_load_list_array(v8::Array::New(isolate()));
AssignToContext(context);
//cjh destroy_ids_list_.reserve(512);
}
inline Environment::~Environment() {
v8::HandleScope handle_scope(isolate());
context()->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex,
nullptr);
#define V(PropertyName, TypeName) PropertyName##_.Reset();
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V
// delete[] heap_statistics_buffer_;
// delete[] heap_space_statistics_buffer_;
// delete[] http_parser_buffer_;
}
inline void Environment::SetMethod(v8::Local<v8::Object> that,
const char *name,
v8::FunctionCallback callback) {
v8::Local<v8::Function> function =
NewFunctionTemplate(callback)->GetFunction(context()).ToLocalChecked();
// kInternalized strings are created in the old space.
const v8::NewStringType type = v8::NewStringType::kInternalized;
v8::Local<v8::String> name_string =
v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked();
that->Set(isolate()->GetCurrentContext(), name_string, function).Check();
function->SetName(name_string); // NODE_SET_METHOD() compatibility.
}
inline void Environment::ThrowError(const char *errmsg) {
ThrowError(v8::Exception::Error, errmsg);
}
inline void Environment::ThrowTypeError(const char *errmsg) {
ThrowError(v8::Exception::TypeError, errmsg);
}
inline void Environment::ThrowRangeError(const char *errmsg) {
ThrowError(v8::Exception::RangeError, errmsg);
}
inline void Environment::ThrowError(
v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
const char *errmsg) {
v8::HandleScope handle_scope(isolate());
isolate()->ThrowException(fun(OneByteString(isolate(), errmsg)));
}
inline void Environment::ThrowErrnoException(int errorno,
const char *syscall,
const char *message,
const char *path) {
isolate()->ThrowException(
ErrnoException(isolate(), errorno, syscall, message, path));
}
inline void Environment::ThrowUVException(int errorno,
const char *syscall,
const char *message,
const char *path,
const char *dest) {
isolate()->ThrowException(
UVException(isolate(), errorno, syscall, message, path, dest));
}
inline v8::Local<v8::FunctionTemplate>
Environment::NewFunctionTemplate(v8::FunctionCallback callback,
v8::Local<v8::Signature> signature) {
v8::Local<v8::External> external = as_external();
return v8::FunctionTemplate::New(isolate(), callback, external, signature);
}
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define V(TypeName, PropertyName) \
inline v8::Local<TypeName> IsolateData::PropertyName(v8::Isolate *isolate) const { \
/* Strings are immutable so casting away const-ness here is okay. */ \
return const_cast<IsolateData *>(this)->PropertyName##_.Get(isolate); \
}
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VP
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
#define V(TypeName, PropertyName) \
inline v8::Local<TypeName> Environment::PropertyName() const { \
return isolate_data()->PropertyName(isolate()); \
}
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VP
#define V(PropertyName, TypeName) \
inline v8::Local<TypeName> Environment::PropertyName() const { \
return StrongPersistentToLocal(PropertyName##_); \
} \
inline void Environment::set_##PropertyName(v8::Local<TypeName> value) { \
PropertyName##_.Reset(isolate(), value); \
}
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V
inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment *env)
: env_(env) {
env_->makecallback_cntr_++;
}
inline Environment::AsyncCallbackScope::~AsyncCallbackScope() {
env_->makecallback_cntr_--;
}
inline bool Environment::AsyncCallbackScope::in_makecallback() {
return env_->makecallback_cntr_ > 1;
}
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
// clang-format on

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,353 @@
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* 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.
*/
#ifndef http_parser_h
#define http_parser_h
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#ifdef __cplusplus
extern "C" {
#endif
/* Also update SONAME in the Makefile whenever you change these. */
#define HTTP_PARSER_VERSION_MAJOR 2
#define HTTP_PARSER_VERSION_MINOR 7
#define HTTP_PARSER_VERSION_PATCH 0
#include <sys/types.h>
#if defined(_WIN32) && !defined(__MINGW32__) && \
(!defined(_MSC_VER) || _MSC_VER < 1600) && !defined(__WINE__)
#include <BaseTsd.h>
#include <stddef.h>
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
#include <cstdint>
#endif
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
* faster
*/
#ifndef HTTP_PARSER_STRICT
#define HTTP_PARSER_STRICT 1
#endif
/* Maximium header size allowed. If the macro is not defined
* before including this header then the default is used. To
* change the maximum header size, define the macro in the build
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
* the effective limit on the size of the header, define the macro
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
*/
#ifndef HTTP_MAX_HEADER_SIZE
#define HTTP_MAX_HEADER_SIZE (80 * 1024)
#endif
using http_parser = struct http_parser;
using http_parser_settings = struct http_parser_settings;
/* Callbacks should return non-zero to indicate an error. The parser will
* then halt execution.
*
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
* returning '1' from on_headers_complete will tell the parser that it
* should not expect a body. This is used when receiving a response to a
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
* chunked' headers that indicate the presence of a body.
*
* Returning `2` from on_headers_complete will tell parser that it should not
* expect neither a body nor any futher responses on this connection. This is
* useful for handling responses to a CONNECT request which may not contain
* `Upgrade` or `Connection: upgrade` headers.
*
* http_data_cb does not return data chunks. It will be called arbitrarily
* many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data.
*/
using http_data_cb = int (*)(http_parser *, const char *, size_t);
using http_cb = int (*)(http_parser *);
/* Request Methods */
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M - SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK)
enum http_method { //NOLINT(readability-identifier-naming)
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
#undef XX
};
enum http_parser_type { HTTP_REQUEST, //NOLINT(readability-identifier-naming)
HTTP_RESPONSE,
HTTP_BOTH };
/* Flag values for http_parser.flags field */
enum flags { F_CHUNKED = 1 << 0, //NOLINT(readability-identifier-naming)
F_CONNECTION_KEEP_ALIVE = 1 << 1,
F_CONNECTION_CLOSE = 1 << 2,
F_CONNECTION_UPGRADE = 1 << 3,
F_TRAILING = 1 << 4,
F_UPGRADE = 1 << 5,
F_SKIPBODY = 1 << 6,
F_CONTENTLENGTH = 1 << 7
};
/* Map for errno-related constants
*
* The provided argument should be a macro that takes 2 arguments.
*/
#define HTTP_ERRNO_MAP(XX) \
/* No error */ \
XX(OK, "success") \
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_status, "the on_status callback failed") \
XX(CB_chunk_header, "the on_chunk_header callback failed") \
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
XX(HEADER_OVERFLOW, \
"too many header bytes seen; overflow detected") \
XX(CLOSED_CONNECTION, \
"data received after completed connection: close message") \
XX(INVALID_VERSION, "invalid HTTP version") \
XX(INVALID_STATUS, "invalid HTTP status code") \
XX(INVALID_METHOD, "invalid HTTP method") \
XX(INVALID_URL, "invalid URL") \
XX(INVALID_HOST, "invalid host") \
XX(INVALID_PORT, "invalid port") \
XX(INVALID_PATH, "invalid path") \
XX(INVALID_QUERY_STRING, "invalid query string") \
XX(INVALID_FRAGMENT, "invalid fragment") \
XX(LF_EXPECTED, "LF character expected") \
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
XX(INVALID_CONTENT_LENGTH, \
"invalid character in content-length header") \
XX(UNEXPECTED_CONTENT_LENGTH, \
"unexpected content-length header") \
XX(INVALID_CHUNK_SIZE, \
"invalid character in chunk size header") \
XX(INVALID_CONSTANT, "invalid constant string") \
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state") \
XX(STRICT, "strict mode assertion failed") \
XX(PAUSED, "parser is paused") \
XX(UNKNOWN, "an unknown error occurred")
/* Define HPE_* values for each errno value above */
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
enum http_errno { //NOLINT(readability-identifier-naming)
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
};
#undef HTTP_ERRNO_GEN
/* Get an http_errno value from an http_parser */
#define HTTP_PARSER_ERRNO(p) ((enum http_errno)(p)->http_errno)
struct http_parser {
/** PRIVATE **/
unsigned int type : 2; /* enum http_parser_type */
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
unsigned int state : 7; /* enum state from http_parser.c */
unsigned int header_state : 7; /* enum header_state from http_parser.c */
unsigned int index : 7; /* index into current matcher */
unsigned int lenient_http_headers : 1;
uint32_t nread; /* # bytes read in various scenarios */
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
/** READ-ONLY **/
unsigned short http_major; //NOLINT(google-runtime-int)
unsigned short http_minor; //NOLINT(google-runtime-int)
unsigned int status_code : 16; /* responses only */
unsigned int method : 8; /* requests only */
unsigned int http_errno : 7;
/* 1 = Upgrade header was present and the parser has exited because of that.
* 0 = No upgrade header present.
* Should be checked when http_parser_execute() returns in addition to
* error checking.
*/
unsigned int upgrade : 1;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
};
struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_url;
http_data_cb on_status;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
http_data_cb on_body;
http_cb on_message_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
*/
http_cb on_chunk_header;
http_cb on_chunk_complete;
};
enum http_parser_url_fields { UF_SCHEMA = 0, //NOLINT(readability-identifier-naming)
UF_HOST = 1,
UF_PORT = 2,
UF_PATH = 3,
UF_QUERY = 4,
UF_FRAGMENT = 5,
UF_USERINFO = 6,
UF_MAX = 7
};
/* Result structure for http_parser_parse_url().
*
* Callers should index into field_data[] with UF_* values iff field_set
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
* because we probably have padding left over), we convert any port to
* a uint16_t.
*/
struct http_parser_url { //NOLINT(readability-identifier-naming)
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
uint16_t port; /* Converted UF_PORT string */
struct {
uint16_t off; /* Offset into buffer in which field starts */
uint16_t len; /* Length of run in buffer */
} field_data[UF_MAX];
};
/* Returns the library version. Bits 16-23 contain the major version number,
* bits 8-15 the minor version number and bits 0-7 the patch level.
* Usage example:
*
* unsigned long version = http_parser_version();
* unsigned major = (version >> 16) & 255;
* unsigned minor = (version >> 8) & 255;
* unsigned patch = version & 255;
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
*/
unsigned long http_parser_version(void); //NOLINT(readability-identifier-naming,google-runtime-int)
void http_parser_init(http_parser *parser, enum http_parser_type type); //NOLINT(readability-identifier-naming)
/* Initialize http_parser_settings members to 0
*/
void http_parser_settings_init(http_parser_settings *settings); //NOLINT(readability-identifier-naming)
/* Executes the parser. Returns number of parsed bytes. Sets
* `parser->http_errno` on error. */
size_t http_parser_execute(http_parser *parser, //NOLINT(readability-identifier-naming)
const http_parser_settings *settings,
const char *data,
size_t len);
/* If http_should_keep_alive() in the on_headers_complete or
* on_message_complete callback returns 0, then this should be
* the last message on the connection.
* If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection.
*/
int http_should_keep_alive(const http_parser *parser); //NOLINT(readability-identifier-naming)
/* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method m); //NOLINT(readability-identifier-naming)
/* Return a string name of the given error */
const char *http_errno_name(enum http_errno err); //NOLINT(readability-identifier-naming)
/* Return a string description of the given error */
const char *http_errno_description(enum http_errno err); //NOLINT(readability-identifier-naming)
/* Initialize all http_parser_url members to 0 */
void http_parser_url_init(struct http_parser_url *u); //NOLINT(readability-identifier-naming)
/* Parse a URL; return nonzero on failure */
int http_parser_parse_url(const char *buf, size_t buflen, //NOLINT(readability-identifier-naming)
int is_connect, //NOLINT(readability-identifier-naming)
struct http_parser_url *u);
/* Pause or un-pause the parser; a nonzero value pauses */
void http_parser_pause(http_parser *parser, int paused); //NOLINT(readability-identifier-naming)
/* Checks if this is the final chunk of the body. */
int http_body_is_final(const http_parser *parser); //NOLINT(readability-identifier-naming)
#ifdef __cplusplus
}
#endif
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#endif

View File

@@ -0,0 +1,822 @@
#include "inspector_agent.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "env.h"
#include "inspector_io.h"
#include "node.h"
#include "util.h"
#include "v8-inspector.h"
#include "v8-platform.h"
#include "zlib.h"
#include "libplatform/libplatform.h"
#include <string.h>
#include <unordered_map>
#include <vector>
#ifdef __POSIX__
#include <unistd.h> // setuid, getuid
#endif // __POSIX__
namespace node {
namespace inspector {
namespace {
using v8::Context;
using v8::External;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Object;
using v8::Persistent;
using v8::String;
using v8::Value;
using v8_inspector::StringBuffer;
using v8_inspector::StringView;
using v8_inspector::V8Inspector;
using v8_inspector::V8InspectorClient;
static uv_sem_t start_io_thread_semaphore;
static uv_async_t start_io_thread_async;
class StartIoTask : public v8::Task {
public:
explicit StartIoTask(Agent *agent) : agent(agent) {}
void Run() override {
agent->StartIoThread(false);
}
private:
Agent *agent;
};
std::unique_ptr<StringBuffer> ToProtocolString(Isolate *isolate,
Local<Value> value) {
TwoByteValue buffer(isolate, value);
return StringBuffer::create(StringView(*buffer, buffer.length()));
}
// Called on the main thread.
void StartIoThreadAsyncCallback(uv_async_t *handle) {
static_cast<Agent *>(handle->data)->StartIoThread(false);
}
void StartIoInterrupt(Isolate *isolate, void *agent) {
static_cast<Agent *>(agent)->StartIoThread(false);
}
#ifdef __POSIX__
static void StartIoThreadWakeup(int signo) {
uv_sem_post(&start_io_thread_semaphore);
}
inline void *StartIoThreadMain(void *unused) {
for (;;) {
uv_sem_wait(&start_io_thread_semaphore);
Agent *agent = static_cast<Agent *>(start_io_thread_async.data);
if (agent != nullptr)
agent->RequestIoThreadStart();
}
return nullptr;
}
static int StartDebugSignalHandler() {
// Start a watchdog thread for calling v8::Debug::DebugBreak() because
// it's not safe to call directly from the signal handler, it can
// deadlock with the thread it interrupts.
CHECK_EQ(0, uv_sem_init(&start_io_thread_semaphore, 0));
pthread_attr_t attr;
CHECK_EQ(0, pthread_attr_init(&attr));
// Don't shrink the thread's stack on FreeBSD. Said platform decided to
// follow the pthreads specification to the letter rather than in spirit:
// https://lists.freebsd.org/pipermail/freebsd-current/2014-March/048885.html
#ifndef __FreeBSD__
CHECK_EQ(0, pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN));
#endif // __FreeBSD__
CHECK_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
sigset_t sigmask;
// Mask all signals.
sigfillset(&sigmask);
CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask));
pthread_t thread;
const int err = pthread_create(&thread, &attr,
StartIoThreadMain, nullptr);
// Restore original mask
CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
CHECK_EQ(0, pthread_attr_destroy(&attr));
if (err != 0) {
SE_LOGE("node[%d]: pthread_create: %s\n", getpid(), strerror(err));
// Leave SIGUSR1 blocked. We don't install a signal handler,
// receiving the signal would terminate the process.
return -err;
}
RegisterSignalHandler(SIGUSR1, StartIoThreadWakeup);
// Unblock SIGUSR1. A pending SIGUSR1 signal will now be delivered.
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGUSR1);
CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sigmask, nullptr));
return 0;
}
#endif // __POSIX__
#ifdef _WIN32
DWORD WINAPI StartIoThreadProc(void *arg) {
Agent *agent = static_cast<Agent *>(start_io_thread_async.data);
if (agent != nullptr)
agent->RequestIoThreadStart();
return 0;
}
static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t *buf,
size_t buf_len) {
return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid);
}
static int StartDebugSignalHandler() {
wchar_t mapping_name[32];
HANDLE mapping_handle;
DWORD pid;
LPTHREAD_START_ROUTINE *handler;
pid = GetCurrentProcessId();
if (GetDebugSignalHandlerMappingName(pid,
mapping_name,
arraysize(mapping_name)) < 0) {
return -1;
}
mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE,
nullptr,
PAGE_READWRITE,
0,
sizeof *handler,
mapping_name);
if (mapping_handle == nullptr) {
return -1;
}
handler = reinterpret_cast<LPTHREAD_START_ROUTINE *>(
MapViewOfFile(mapping_handle,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof *handler));
if (handler == nullptr) {
CloseHandle(mapping_handle);
return -1;
}
*handler = StartIoThreadProc;
UnmapViewOfFile(static_cast<void *>(handler));
return 0;
}
#endif // _WIN32
class JsBindingsSessionDelegate : public InspectorSessionDelegate {
public:
JsBindingsSessionDelegate(Environment *env,
Local<Object> session,
Local<Object> receiver,
Local<Function> callback)
: env_(env),
session_(env->isolate(), session),
receiver_(env->isolate(), receiver),
callback_(env->isolate(), callback) {
session_.SetWeak(this, JsBindingsSessionDelegate::Release,
v8::WeakCallbackType::kParameter);
}
~JsBindingsSessionDelegate() override {
session_.Reset();
receiver_.Reset();
callback_.Reset();
}
bool WaitForFrontendMessageWhilePaused() override {
return false;
}
void SendMessageToFrontend(const v8_inspector::StringView &message) override {
Isolate *isolate = env_->isolate();
v8::HandleScope handle_scope(isolate);
Context::Scope context_scope(env_->context());
MaybeLocal<String> v8string =
String::NewFromTwoByte(isolate, message.characters16(),
NewStringType::kNormal, static_cast<int>(message.length()));
Local<Value> argument = v8string.ToLocalChecked().As<Value>();
Local<Function> callback = callback_.Get(isolate);
Local<Object> receiver = receiver_.Get(isolate);
callback->Call(env_->context(), receiver, 1, &argument)
.FromMaybe(Local<Value>());
}
void Disconnect() {
Agent *agent = env_->inspector_agent();
if (agent->delegate() == this)
agent->Disconnect();
}
private:
static void Release(
const v8::WeakCallbackInfo<JsBindingsSessionDelegate> &info) {
info.SetSecondPassCallback(ReleaseSecondPass);
info.GetParameter()->session_.Reset();
}
static void ReleaseSecondPass(
const v8::WeakCallbackInfo<JsBindingsSessionDelegate> &info) {
JsBindingsSessionDelegate *delegate = info.GetParameter();
delegate->Disconnect();
delete delegate;
}
Environment *env_;
Persistent<Object> session_;
Persistent<Object> receiver_;
Persistent<Function> callback_;
};
void SetDelegate(Environment *env, Local<Object> inspector,
JsBindingsSessionDelegate *delegate) {
inspector->SetPrivate(env->context(),
env->inspector_delegate_private_symbol(),
v8::External::New(env->isolate(), delegate));
}
Maybe<JsBindingsSessionDelegate *> GetDelegate(
const FunctionCallbackInfo<Value> &info) {
Environment *env = Environment::GetCurrent(info);
Local<Value> delegate;
MaybeLocal<Value> maybe_delegate =
info.This()->GetPrivate(env->context(),
env->inspector_delegate_private_symbol());
if (maybe_delegate.ToLocal(&delegate)) {
CHECK(delegate->IsExternal());
void *value = delegate.As<External>()->Value();
if (value != nullptr) {
return v8::Just(static_cast<JsBindingsSessionDelegate *>(value));
}
}
env->ThrowError("Inspector is not connected");
return v8::Nothing<JsBindingsSessionDelegate *>();
}
void Dispatch(const FunctionCallbackInfo<Value> &info) {
Environment *env = Environment::GetCurrent(info);
if (!info[0]->IsString()) {
env->ThrowError("Inspector message must be a string");
return;
}
Maybe<JsBindingsSessionDelegate *> maybe_delegate = GetDelegate(info);
if (maybe_delegate.IsNothing())
return;
Agent *inspector = env->inspector_agent();
CHECK_EQ(maybe_delegate.ToChecked(), inspector->delegate());
inspector->Dispatch(ToProtocolString(env->isolate(), info[0])->string());
}
void Disconnect(const FunctionCallbackInfo<Value> &info) {
Environment *env = Environment::GetCurrent(info);
Maybe<JsBindingsSessionDelegate *> delegate = GetDelegate(info);
if (delegate.IsNothing()) {
return;
}
delegate.ToChecked()->Disconnect();
SetDelegate(env, info.This(), nullptr);
delete delegate.ToChecked();
}
void ConnectJSBindingsSession(const FunctionCallbackInfo<Value> &info) {
Environment *env = Environment::GetCurrent(info);
if (!info[0]->IsFunction()) {
env->ThrowError("Message callback is required");
return;
}
Agent *inspector = env->inspector_agent();
if (inspector->delegate() != nullptr) {
env->ThrowError("Session is already attached");
return;
}
Local<Object> session = Object::New(env->isolate());
env->SetMethod(session, "dispatch", Dispatch);
env->SetMethod(session, "disconnect", Disconnect);
info.GetReturnValue().Set(session);
JsBindingsSessionDelegate *delegate =
new JsBindingsSessionDelegate(env, session, info.Holder(),
info[0].As<Function>());
inspector->Connect(delegate);
SetDelegate(env, session, delegate);
}
void InspectorConsoleCall(const v8::FunctionCallbackInfo<Value> &info) {
Isolate *isolate = info.GetIsolate();
HandleScope handle_scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
CHECK_LT(2, info.Length());
std::vector<Local<Value>> call_args;
for (int i = 3; i < info.Length(); ++i) {
call_args.push_back(info[i]);
}
Environment *env = Environment::GetCurrent(isolate);
if (env->inspector_agent()->enabled()) {
Local<Value> inspector_method = info[0];
CHECK(inspector_method->IsFunction());
Local<Value> config_value = info[2];
CHECK(config_value->IsObject());
Local<Object> config_object = config_value.As<Object>();
Local<String> in_call_key = FIXED_ONE_BYTE_STRING(isolate, "in_call");
if (!config_object->Has(context, in_call_key).FromMaybe(false)) {
CHECK(config_object->Set(context,
in_call_key,
v8::True(isolate))
.FromJust());
CHECK(!inspector_method.As<Function>()->Call(context,
info.Holder(),
static_cast<int>(call_args.size()),
call_args.data())
.IsEmpty());
}
CHECK(config_object->Delete(context, in_call_key).FromJust());
}
Local<Value> node_method = info[1];
CHECK(node_method->IsFunction());
node_method.As<Function>()->Call(context,
info.Holder(),
static_cast<int>(call_args.size()),
call_args.data())
.FromMaybe(Local<Value>());
}
void CallAndPauseOnStart(
const v8::FunctionCallbackInfo<v8::Value> &args) {
Environment *env = Environment::GetCurrent(args);
CHECK_GT(args.Length(), 1);
CHECK(args[0]->IsFunction());
std::vector<v8::Local<v8::Value>> call_args;
for (int i = 2; i < args.Length(); i++) {
call_args.push_back(args[i]);
}
env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start");
v8::MaybeLocal<v8::Value> retval =
args[0].As<v8::Function>()->Call(env->context(), args[1],
static_cast<int>(call_args.size()), call_args.data());
if (!retval.IsEmpty()) {
args.GetReturnValue().Set(retval.ToLocalChecked());
}
}
// Used in NodeInspectorClient::currentTimeMS() below.
const int NANOS_PER_MSEC = 1000000;
const int CONTEXT_GROUP_ID = 1;
class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
public:
explicit ChannelImpl(V8Inspector *inspector,
InspectorSessionDelegate *delegate)
: delegate_(delegate) {
#if V8_MAJOR_VERSION > 10 || (V8_MAJOR_VERSION == 10 && V8_MINOR_VERSION > 3)
session_ = inspector->connect(1, this, StringView(), v8_inspector::V8Inspector::ClientTrustLevel::kFullyTrusted);
#else
session_ = inspector->connect(1, this, StringView());
#endif
}
virtual ~ChannelImpl() {}
void dispatchProtocolMessage(const StringView &message) {
session_->dispatchProtocolMessage(message);
}
bool waitForFrontendMessage() {
return delegate_->WaitForFrontendMessageWhilePaused();
}
void schedulePauseOnNextStatement(const std::string &reason) {
std::unique_ptr<StringBuffer> buffer = Utf8ToStringView(reason);
session_->schedulePauseOnNextStatement(buffer->string(), buffer->string());
}
InspectorSessionDelegate *delegate() {
return delegate_;
}
private:
void sendResponse(
int callId,
std::unique_ptr<v8_inspector::StringBuffer> message) override {
sendMessageToFrontend(message->string());
}
void sendNotification(
std::unique_ptr<v8_inspector::StringBuffer> message) override {
sendMessageToFrontend(message->string());
}
void flushProtocolNotifications() override {}
void sendMessageToFrontend(const StringView &message) {
delegate_->SendMessageToFrontend(message);
}
InspectorSessionDelegate *const delegate_;
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
};
class InspectorTimer {
public:
InspectorTimer(uv_loop_t *loop,
double interval_s,
V8InspectorClient::TimerCallback callback,
void *data) : timer_(),
callback_(callback),
data_(data) {
uv_timer_init(loop, &timer_);
int64_t interval_ms = 1000 * interval_s;
uv_timer_start(&timer_, OnTimer, interval_ms, interval_ms);
}
InspectorTimer(const InspectorTimer &) = delete;
void Stop() {
uv_timer_stop(&timer_);
uv_close(reinterpret_cast<uv_handle_t *>(&timer_), TimerClosedCb);
}
private:
static void OnTimer(uv_timer_t *uvtimer) {
InspectorTimer *timer = node::ContainerOf(&InspectorTimer::timer_, uvtimer);
timer->callback_(timer->data_);
}
static void TimerClosedCb(uv_handle_t *uvtimer) {
InspectorTimer *timer =
node::ContainerOf(&InspectorTimer::timer_,
reinterpret_cast<uv_timer_t *>(uvtimer));
delete timer;
}
~InspectorTimer() {}
uv_timer_t timer_;
V8InspectorClient::TimerCallback callback_;
void *data_;
};
class InspectorTimerHandle {
public:
InspectorTimerHandle(uv_loop_t *loop, double interval_s,
V8InspectorClient::TimerCallback callback, void *data) {
timer_ = new InspectorTimer(loop, interval_s, callback, data);
}
InspectorTimerHandle(const InspectorTimerHandle &) = delete;
~InspectorTimerHandle() {
CHECK_NE(timer_, nullptr);
timer_->Stop();
timer_ = nullptr;
}
private:
InspectorTimer *timer_;
};
} // namespace
class NodeInspectorClient : public V8InspectorClient {
public:
NodeInspectorClient(node::Environment *env,
v8::Platform *platform) : env_(env),
platform_(platform),
terminated_(false),
running_nested_loop_(false) {
client_ = V8Inspector::create(env->isolate(), this);
}
void runMessageLoopOnPause(int context_group_id) override {
CHECK_NE(channel_, nullptr);
if (running_nested_loop_)
return;
terminated_ = false;
running_nested_loop_ = true;
while (!terminated_ && channel_->waitForFrontendMessage()) {
while (v8::platform::PumpMessageLoop(platform_, env_->isolate())) {
}
}
terminated_ = false;
running_nested_loop_ = false;
}
double currentTimeMS() override {
return uv_hrtime() * 1.0 / NANOS_PER_MSEC;
}
void contextCreated(Local<Context> context, const std::string &name) {
std::unique_ptr<StringBuffer> name_buffer = Utf8ToStringView(name);
v8_inspector::V8ContextInfo info(context, CONTEXT_GROUP_ID,
name_buffer->string());
client_->contextCreated(info);
}
void contextDestroyed(Local<Context> context) {
client_->contextDestroyed(context);
}
void quitMessageLoopOnPause() override {
terminated_ = true;
}
void connectFrontend(InspectorSessionDelegate *delegate) {
CHECK_EQ(channel_, nullptr);
channel_ = std::unique_ptr<ChannelImpl>(
new ChannelImpl(client_.get(), delegate));
}
void disconnectFrontend() {
quitMessageLoopOnPause();
channel_.reset();
}
void dispatchMessageFromFrontend(const StringView &message) {
if (channel_ == nullptr) return;
CHECK_NE(channel_, nullptr);
channel_->dispatchProtocolMessage(message);
}
Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
return env_->context();
}
void FatalException(Local<Value> error, Local<v8::Message> message) {
Local<Context> context = env_->context();
int script_id = message->GetScriptOrigin().ScriptId();
Local<v8::StackTrace> stack_trace = message->GetStackTrace();
if (!stack_trace.IsEmpty() &&
stack_trace->GetFrameCount() > 0 &&
script_id == stack_trace->GetFrame(env_->isolate(), 0)->GetScriptId()) {
script_id = 0;
}
const uint8_t DETAILS[] = "Uncaught";
Isolate *isolate = context->GetIsolate();
client_->exceptionThrown(
context,
StringView(DETAILS, sizeof(DETAILS) - 1),
error,
ToProtocolString(isolate, message->Get())->string(),
ToProtocolString(isolate, message->GetScriptResourceName())->string(),
message->GetLineNumber(context).FromMaybe(0),
message->GetStartColumn(context).FromMaybe(0),
client_->createStackTrace(stack_trace),
script_id);
}
ChannelImpl *channel() {
return channel_.get();
}
void startRepeatingTimer(double interval_s,
TimerCallback callback,
void *data) override {
timers_.emplace(std::piecewise_construct, std::make_tuple(data),
std::make_tuple(env_->event_loop(), interval_s, callback,
data));
}
void cancelTimer(void *data) override {
timers_.erase(data);
}
private:
node::Environment *env_;
v8::Platform *platform_;
bool terminated_;
bool running_nested_loop_;
std::unique_ptr<V8Inspector> client_;
std::unique_ptr<ChannelImpl> channel_;
std::unordered_map<void *, InspectorTimerHandle> timers_;
};
Agent::Agent(Environment *env) : parent_env_(env),
client_(nullptr),
platform_(nullptr),
enabled_(false) {}
// Destructor needs to be defined here in implementation file as the header
// does not have full definition of some classes.
Agent::~Agent() {
}
bool Agent::Start(v8::Platform *platform, const char *path,
const DebugOptions &options) {
path_ = path == nullptr ? "" : path;
debug_options_ = options;
client_ =
std::unique_ptr<NodeInspectorClient>(
new NodeInspectorClient(parent_env_, platform));
client_->contextCreated(parent_env_->context(), "Node.js Main Context");
platform_ = platform;
CHECK_EQ(0, uv_async_init(uv_default_loop(),
&start_io_thread_async,
StartIoThreadAsyncCallback));
start_io_thread_async.data = this;
uv_unref(reinterpret_cast<uv_handle_t *>(&start_io_thread_async));
// Ignore failure, SIGUSR1 won't work, but that should not block node start.
StartDebugSignalHandler();
if (options.inspector_enabled()) {
// This will return false if listen failed on the inspector port.
return StartIoThread(options.wait_for_connect());
}
return true;
}
bool Agent::StartIoThread(bool wait_for_connect) {
if (io_ != nullptr)
return true;
CHECK_NE(client_, nullptr);
enabled_ = true;
io_ = std::unique_ptr<InspectorIo>(
new InspectorIo(parent_env_, platform_, path_, debug_options_,
wait_for_connect));
if (!io_->Start()) {
client_.reset();
return false;
}
v8::Isolate *isolate = parent_env_->isolate();
// Send message to enable debug in workers
HandleScope handle_scope(isolate);
Local<Object> process_object = parent_env_->process_object();
Local<Value> emit_fn =
process_object->Get(parent_env_->context(), FIXED_ONE_BYTE_STRING(isolate, "emit")).ToLocalChecked();
// In case the thread started early during the startup
if (!emit_fn->IsFunction())
return true;
Local<Object> message = Object::New(isolate);
message->Set(parent_env_->context(), FIXED_ONE_BYTE_STRING(isolate, "cmd"),
FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED"))
.Check();
Local<Value> argv[] = {
FIXED_ONE_BYTE_STRING(isolate, "internalMessage"),
message};
MakeCallback(parent_env_->isolate(), process_object, emit_fn.As<Function>(),
arraysize(argv), argv, {0, 0});
return true;
}
void Agent::Stop() {
if (io_ != nullptr) {
io_->Stop();
io_.reset();
}
}
void Agent::Connect(InspectorSessionDelegate *delegate) {
enabled_ = true;
client_->connectFrontend(delegate);
}
bool Agent::IsConnected() {
return io_ && io_->IsConnected();
}
void Agent::WaitForDisconnect() {
CHECK_NE(client_, nullptr);
client_->contextDestroyed(parent_env_->context());
if (io_ != nullptr) {
io_->WaitForDisconnect();
}
}
void Agent::FatalException(Local<Value> error, Local<v8::Message> message) {
if (!IsStarted())
return;
client_->FatalException(error, message);
WaitForDisconnect();
}
void Agent::Dispatch(const StringView &message) {
if (client_ == nullptr) return;
CHECK_NE(client_, nullptr);
client_->dispatchMessageFromFrontend(message);
}
void Agent::Disconnect() {
CHECK_NE(client_, nullptr);
client_->disconnectFrontend();
}
void Agent::RunMessageLoop() {
CHECK_NE(client_, nullptr);
client_->runMessageLoopOnPause(CONTEXT_GROUP_ID);
}
InspectorSessionDelegate *Agent::delegate() {
CHECK_NE(client_, nullptr);
ChannelImpl *channel = client_->channel();
if (channel == nullptr)
return nullptr;
return channel->delegate();
}
void Agent::PauseOnNextJavascriptStatement(const std::string &reason) {
ChannelImpl *channel = client_->channel();
if (channel != nullptr)
channel->schedulePauseOnNextStatement(reason);
}
void Open(const FunctionCallbackInfo<Value> &args) {
Environment *env = Environment::GetCurrent(args);
inspector::Agent *agent = env->inspector_agent();
bool wait_for_connect = false;
if (args.Length() > 0 && args[0]->IsUint32()) {
uint32_t port = args[0]->Uint32Value(env->context()).ToChecked();
agent->options().set_port(static_cast<int>(port));
}
if (args.Length() > 1 && args[1]->IsString()) {
node::Utf8Value host(env->isolate(), args[1].As<String>());
agent->options().set_host_name(*host);
}
if (args.Length() > 2 && args[2]->IsBoolean()) {
wait_for_connect = args[2]->BooleanValue(env->isolate());
}
agent->StartIoThread(wait_for_connect);
}
void Url(const FunctionCallbackInfo<Value> &args) {
Environment *env = Environment::GetCurrent(args);
inspector::Agent *agent = env->inspector_agent();
inspector::InspectorIo *io = agent->io();
if (!io) return;
std::vector<std::string> ids = io->GetTargetIds();
if (ids.empty()) return;
std::string url = FormatWsAddress(io->host(), io->port(), ids[0], true);
args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str()));
}
// static
void Agent::InitInspector(Local<Object> target, Local<Value> unused,
Local<Context> context, void *priv) {
Environment *env = Environment::GetCurrent(context);
Agent *agent = env->inspector_agent();
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
if (agent->debug_options_.wait_for_connect())
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
env->SetMethod(target, "connect", ConnectJSBindingsSession);
env->SetMethod(target, "open", Open);
env->SetMethod(target, "url", Url);
}
void Agent::RequestIoThreadStart() {
// We need to attempt to interrupt V8 flow (in case Node is running
// continuous JS code) and to wake up libuv thread (in case Node is waiting
// for IO events)
uv_async_send(&start_io_thread_async);
v8::Isolate *isolate = parent_env_->isolate();
platform_->GetForegroundTaskRunner(isolate)->PostTask(std::make_unique<StartIoTask>(this));
isolate->RequestInterrupt(StartIoInterrupt, this);
uv_async_send(&start_io_thread_async);
}
} // namespace inspector
} // namespace node
//cjh NODE_MODULE_CONTEXT_AWARE_BUILTIN(inspector,
// node::inspector::Agent::InitInspector);
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,116 @@
#ifndef SRC_INSPECTOR_AGENT_H_
#define SRC_INSPECTOR_AGENT_H_
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include <memory>
#include <stddef.h>
#if !HAVE_INSPECTOR
#error("This header can only be used when inspector is enabled")
#endif
#include "node_debug_options.h"
// Forward declaration to break recursive dependency chain with src/env.h.
namespace node {
class Environment;
} // namespace node
namespace v8 {
class Context;
template <typename V>
class FunctionCallbackInfo;
template <typename T>
class Local;
class Message;
class Object;
class Platform;
class Value;
} // namespace v8
namespace v8_inspector {
class StringView;
} // namespace v8_inspector
namespace node {
namespace inspector {
class InspectorSessionDelegate {
public:
virtual ~InspectorSessionDelegate() = default;
virtual bool WaitForFrontendMessageWhilePaused() = 0;
virtual void SendMessageToFrontend(const v8_inspector::StringView &message) = 0;
};
class InspectorIo;
class NodeInspectorClient;
class Agent {
public:
explicit Agent(node::Environment *env);
~Agent();
// Create client_, may create io_ if option enabled
bool Start(v8::Platform *platform, const char *path,
const DebugOptions &options);
// Stop and destroy io_
void Stop();
bool IsStarted() { return !!client_; }
// IO thread started, and client connected
bool IsConnected();
void WaitForDisconnect();
void FatalException(v8::Local<v8::Value> error,
v8::Local<v8::Message> message);
// These methods are called by the WS protocol and JS binding to create
// inspector sessions. The inspector responds by using the delegate to send
// messages back.
void Connect(InspectorSessionDelegate *delegate);
void Disconnect();
void Dispatch(const v8_inspector::StringView &message);
InspectorSessionDelegate *delegate();
void RunMessageLoop();
bool enabled() { return enabled_; }
void PauseOnNextJavascriptStatement(const std::string &reason);
// Initialize 'inspector' module bindings
static void InitInspector(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void *priv);
InspectorIo *io() {
return io_.get();
}
// Can only be called from the the main thread.
bool StartIoThread(bool wait_for_connect);
// Calls StartIoThread() from off the main thread.
void RequestIoThreadStart();
DebugOptions &options() { return debug_options_; }
private:
node::Environment *parent_env_;
std::unique_ptr<NodeInspectorClient> client_;
std::unique_ptr<InspectorIo> io_;
v8::Platform *platform_;
bool enabled_;
std::string path_;
DebugOptions debug_options_;
};
} // namespace inspector
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#endif // SRC_INSPECTOR_AGENT_H_

View File

@@ -0,0 +1,533 @@
#include "inspector_io.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "inspector_socket_server.h"
#include "env.h"
#include "node.h"
//cjh #include "node_crypto.h"
#include "node_mutex.h"
#include "v8-inspector.h"
#include "util.h"
#include "zlib.h"
#include "libplatform/libplatform.h"
#include <sstream>
//cjh #include <unicode/unistr.h>
#include <string.h>
#include <vector>
#include "base/UTF8.h" //cjh added
namespace node {
namespace inspector {
namespace {
using AsyncAndAgent = std::pair<uv_async_t, Agent *>;
using v8_inspector::StringBuffer;
using v8_inspector::StringView;
template <typename Transport>
using TransportAndIo = std::pair<Transport *, InspectorIo *>;
std::string GetProcessTitle() {
char title[2048];
int err = uv_get_process_title(title, sizeof(title));
if (err == 0) {
return title;
} else {
// Title is too long, or could not be retrieved.
return "Node.js";
}
}
std::string ScriptPath(uv_loop_t *loop, const std::string &script_name) {
std::string script_path;
if (!script_name.empty()) {
uv_fs_t req;
req.ptr = nullptr;
if (0 == uv_fs_realpath(loop, &req, script_name.c_str(), nullptr)) {
CHECK_NE(req.ptr, nullptr);
script_path = std::string(static_cast<char *>(req.ptr));
}
uv_fs_req_cleanup(&req);
}
return script_path;
}
// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
// Used ver 4 - with numbers
std::string GenerateID() {
// uint16_t buffer[8];
//cjh same uuid
uint16_t buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};
//cjh
//cjh CHECK(crypto::EntropySource(reinterpret_cast<unsigned char*>(buffer),
// sizeof(buffer)));
char uuid[256];
snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
buffer[0], // time_low
buffer[1], // time_mid
buffer[2], // time_low
(buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version
(buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low
buffer[5], // node
buffer[6],
buffer[7]);
return uuid;
}
std::string StringViewToUtf8(const StringView &view) {
if (view.is8Bit()) {
return std::string(reinterpret_cast<const char *>(view.characters8()),
view.length());
}
const uint16_t *source = view.characters16();
std::u16string u16Str((char16_t *)source);
std::string ret;
cc::StringUtils::UTF16ToUTF8(u16Str, ret);
return ret;
// const UChar* unicodeSource = reinterpret_cast<const UChar*>(source);
// static_assert(sizeof(*source) == sizeof(*unicodeSource),
// "sizeof(*source) == sizeof(*unicodeSource)");
//
// size_t result_length = view.length() * sizeof(*source);
// std::string result(result_length, '\0');
//cjh UnicodeString utf16(unicodeSource, view.length());
// // ICU components for std::string compatibility are not enabled in build...
// bool done = false;
// while (!done) {
// CheckedArrayByteSink sink(&result[0], result_length);
// utf16.toUTF8(sink);
// result_length = sink.NumberOfBytesAppended();
// result.resize(result_length);
// done = !sink.Overflowed();
// }
// return result;
return "";
}
void HandleSyncCloseCb(uv_handle_t *handle) {
*static_cast<bool *>(handle->data) = true;
}
int CloseAsyncAndLoop(uv_async_t *async) {
bool is_closed = false;
async->data = &is_closed;
uv_close(reinterpret_cast<uv_handle_t *>(async), HandleSyncCloseCb);
while (!is_closed)
uv_run(async->loop, UV_RUN_ONCE);
async->data = nullptr;
return uv_loop_close(async->loop);
}
// Delete main_thread_req_ on async handle close
void ReleasePairOnAsyncClose(uv_handle_t *async) {
AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first,
reinterpret_cast<uv_async_t *>(async));
delete pair;
}
} // namespace
std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string &message) {
//cjh UnicodeString utf16 =
// UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));
std::u16string u16Str;
cc::StringUtils::UTF8ToUTF16(message, u16Str);
// StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()),
// utf16.length());
StringView view(reinterpret_cast<const uint16_t *>(u16Str.c_str()),
u16Str.length());
return StringBuffer::create(view);
}
class IoSessionDelegate : public InspectorSessionDelegate {
public:
explicit IoSessionDelegate(InspectorIo *io) : io_(io) {}
bool WaitForFrontendMessageWhilePaused() override;
void SendMessageToFrontend(const v8_inspector::StringView &message) override;
private:
InspectorIo *io_;
};
// Passed to InspectorSocketServer to handle WS inspector protocol events,
// mostly session start, message received, and session end.
class InspectorIoDelegate : public node::inspector::SocketServerDelegate {
public:
InspectorIoDelegate(InspectorIo *io, const std::string &script_path,
const std::string &script_name, bool wait);
// Calls PostIncomingMessage() with appropriate InspectorAction:
// kStartSession
bool StartSession(int session_id, const std::string &target_id) override;
// kSendMessage
void MessageReceived(int session_id, const std::string &message) override;
// kEndSession
void EndSession(int session_id) override;
std::vector<std::string> GetTargetIds() override;
std::string GetTargetTitle(const std::string &id) override;
std::string GetTargetUrl(const std::string &id) override;
bool IsConnected() { return connected_; }
void ServerDone() override {
io_->ServerDone();
}
private:
InspectorIo *io_;
bool connected_;
int session_id_;
const std::string script_name_;
const std::string script_path_;
const std::string target_id_;
bool waiting_;
};
void InterruptCallback(v8::Isolate *, void *agent) {
InspectorIo *io = static_cast<Agent *>(agent)->io();
if (io != nullptr)
io->DispatchMessages();
}
class DispatchMessagesTask : public v8::Task {
public:
explicit DispatchMessagesTask(Agent *agent) : agent_(agent) {}
void Run() override {
InspectorIo *io = agent_->io();
if (io != nullptr)
io->DispatchMessages();
}
private:
Agent *agent_;
};
InspectorIo::InspectorIo(Environment *env, v8::Platform *platform,
const std::string &path, const DebugOptions &options,
bool wait_for_connect)
: options_(options),
thread_(),
delegate_(nullptr),
state_(State::kNew),
parent_env_(env),
thread_req_(),
platform_(platform),
dispatching_messages_(false),
session_id_(0),
script_name_(path),
wait_for_connect_(wait_for_connect),
port_(-1) {
main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()});
CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first,
InspectorIo::MainThreadReqAsyncCb));
uv_unref(reinterpret_cast<uv_handle_t *>(&main_thread_req_->first));
CHECK_EQ(0, uv_sem_init(&thread_start_sem_, 0));
}
InspectorIo::~InspectorIo() {
uv_sem_destroy(&thread_start_sem_);
uv_close(reinterpret_cast<uv_handle_t *>(&main_thread_req_->first),
ReleasePairOnAsyncClose);
}
bool InspectorIo::Start() {
CHECK_EQ(state_, State::kNew);
CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0);
uv_sem_wait(&thread_start_sem_);
if (state_ == State::kError) {
return false;
}
state_ = State::kAccepting;
if (wait_for_connect_) {
DispatchMessages();
}
return true;
}
void InspectorIo::Stop() {
CHECK(state_ == State::kAccepting || state_ == State::kConnected);
Write(TransportAction::kKill, 0, StringView());
int err = uv_thread_join(&thread_);
CHECK_EQ(err, 0);
state_ = State::kShutDown;
DispatchMessages();
}
bool InspectorIo::IsConnected() {
return delegate_ != nullptr && delegate_->IsConnected();
}
bool InspectorIo::IsStarted() {
return platform_ != nullptr;
}
void InspectorIo::WaitForDisconnect() {
if (state_ == State::kAccepting)
state_ = State::kDone;
if (state_ == State::kConnected) {
state_ = State::kShutDown;
Write(TransportAction::kStop, 0, StringView());
SE_LOGD("Waiting for the debugger to disconnect...\n");
parent_env_->inspector_agent()->RunMessageLoop();
}
}
// static
void InspectorIo::ThreadMain(void *io) {
static_cast<InspectorIo *>(io)->ThreadMain<InspectorSocketServer>();
}
// static
template <typename Transport>
void InspectorIo::IoThreadAsyncCb(uv_async_t *async) {
TransportAndIo<Transport> *transport_and_io =
static_cast<TransportAndIo<Transport> *>(async->data);
if (transport_and_io == nullptr) {
return;
}
Transport *transport = transport_and_io->first;
InspectorIo *io = transport_and_io->second;
MessageQueue<TransportAction> outgoing_message_queue;
io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_message_queue);
for (const auto &outgoing : outgoing_message_queue) {
switch (std::get<0>(outgoing)) {
case TransportAction::kKill:
transport->TerminateConnections();
// Fallthrough
case TransportAction::kStop:
transport->Stop(nullptr);
break;
case TransportAction::kSendMessage:
std::string message = StringViewToUtf8(std::get<2>(outgoing)->string());
transport->Send(std::get<1>(outgoing), message);
break;
}
}
}
template <typename Transport>
void InspectorIo::ThreadMain() {
uv_loop_t loop;
loop.data = nullptr;
int err = uv_loop_init(&loop);
CHECK_EQ(err, 0);
thread_req_.data = nullptr;
err = uv_async_init(&loop, &thread_req_, IoThreadAsyncCb<Transport>);
CHECK_EQ(err, 0);
std::string script_path = ScriptPath(&loop, script_name_);
InspectorIoDelegate delegate(this, script_path, script_name_,
wait_for_connect_);
delegate_ = &delegate;
Transport server(&delegate, &loop, options_.host_name(), options_.port());
TransportAndIo<Transport> queue_transport(&server, this);
thread_req_.data = &queue_transport;
if (!server.Start()) {
state_ = State::kError; // Safe, main thread is waiting on semaphore
CHECK_EQ(0, CloseAsyncAndLoop(&thread_req_));
uv_sem_post(&thread_start_sem_);
return;
}
port_ = server.Port(); // Safe, main thread is waiting on semaphore.
if (!wait_for_connect_) {
uv_sem_post(&thread_start_sem_);
}
uv_run(&loop, UV_RUN_DEFAULT);
thread_req_.data = nullptr;
CHECK_EQ(uv_loop_close(&loop), 0);
delegate_ = nullptr;
}
template <typename ActionType>
bool InspectorIo::AppendMessage(MessageQueue<ActionType> *queue,
ActionType action, int session_id,
std::unique_ptr<StringBuffer> buffer) {
Mutex::ScopedLock scoped_lock(state_lock_);
bool trigger_pumping = queue->empty();
queue->push_back(std::make_tuple(action, session_id, std::move(buffer)));
return trigger_pumping;
}
template <typename ActionType>
void InspectorIo::SwapBehindLock(MessageQueue<ActionType> *vector1,
MessageQueue<ActionType> *vector2) {
Mutex::ScopedLock scoped_lock(state_lock_);
vector1->swap(*vector2);
}
void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id,
const std::string &message) {
if (AppendMessage(&incoming_message_queue_, action, session_id,
Utf8ToStringView(message))) {
Agent *agent = main_thread_req_->second;
v8::Isolate *isolate = parent_env_->isolate();
platform_->GetForegroundTaskRunner(isolate)->PostTask(std::make_unique<DispatchMessagesTask>(agent));
isolate->RequestInterrupt(InterruptCallback, agent);
CHECK_EQ(0, uv_async_send(&main_thread_req_->first));
}
NotifyMessageReceived();
}
std::vector<std::string> InspectorIo::GetTargetIds() const {
return delegate_ ? delegate_->GetTargetIds() : std::vector<std::string>();
}
void InspectorIo::WaitForFrontendMessageWhilePaused() {
dispatching_messages_ = false;
Mutex::ScopedLock scoped_lock(state_lock_);
if (incoming_message_queue_.empty())
incoming_message_cond_.Wait(scoped_lock);
}
void InspectorIo::NotifyMessageReceived() {
Mutex::ScopedLock scoped_lock(state_lock_);
incoming_message_cond_.Broadcast(scoped_lock);
}
void InspectorIo::DispatchMessages() {
// This function can be reentered if there was an incoming message while
// V8 was processing another inspector request (e.g. if the user is
// evaluating a long-running JS code snippet). This can happen only at
// specific points (e.g. the lines that call inspector_ methods)
if (dispatching_messages_)
return;
dispatching_messages_ = true;
bool had_messages = false;
do {
if (dispatching_message_queue_.empty())
SwapBehindLock(&incoming_message_queue_, &dispatching_message_queue_);
had_messages = !dispatching_message_queue_.empty();
while (!dispatching_message_queue_.empty()) {
MessageQueue<InspectorAction>::value_type task;
std::swap(dispatching_message_queue_.front(), task);
dispatching_message_queue_.pop_front();
StringView message = std::get<2>(task)->string();
switch (std::get<0>(task)) {
case InspectorAction::kStartSession:
CHECK_EQ(session_delegate_, nullptr);
session_id_ = std::get<1>(task);
state_ = State::kConnected;
SE_LOGD("Debugger attached.\n");
session_delegate_ = std::unique_ptr<InspectorSessionDelegate>(
new IoSessionDelegate(this));
parent_env_->inspector_agent()->Connect(session_delegate_.get());
break;
case InspectorAction::kEndSession:
CHECK_NE(session_delegate_, nullptr);
if (state_ == State::kShutDown) {
state_ = State::kDone;
} else {
state_ = State::kAccepting;
}
parent_env_->inspector_agent()->Disconnect();
session_delegate_.reset();
break;
case InspectorAction::kSendMessage:
parent_env_->inspector_agent()->Dispatch(message);
break;
}
}
} while (had_messages);
dispatching_messages_ = false;
}
// static
void InspectorIo::MainThreadReqAsyncCb(uv_async_t *req) {
AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first, req);
// Note that this may be called after io was closed or even after a new
// one was created and ran.
InspectorIo *io = pair->second->io();
if (io != nullptr)
io->DispatchMessages();
}
void InspectorIo::Write(TransportAction action, int session_id,
const StringView &inspector_message) {
AppendMessage(&outgoing_message_queue_, action, session_id,
StringBuffer::create(inspector_message));
int err = uv_async_send(&thread_req_);
CHECK_EQ(0, err);
}
InspectorIoDelegate::InspectorIoDelegate(InspectorIo *io,
const std::string &script_path,
const std::string &script_name,
bool wait)
: io_(io),
connected_(false),
session_id_(0),
script_name_(script_name),
script_path_(script_path),
target_id_(GenerateID()),
waiting_(wait) {}
bool InspectorIoDelegate::StartSession(int session_id,
const std::string &target_id) {
if (connected_)
return false;
connected_ = true;
session_id_++;
io_->PostIncomingMessage(InspectorAction::kStartSession, session_id, "");
return true;
}
void InspectorIoDelegate::MessageReceived(int session_id,
const std::string &message) {
// REFINE(pfeldman): Instead of blocking execution while debugger
// engages, node should wait for the run callback from the remote client
// and initiate its startup. This is a change to node.cc that should be
// upstreamed separately.
if (waiting_) {
if (message.find("\"Runtime.runIfWaitingForDebugger\"") !=
std::string::npos) {
waiting_ = false;
io_->ResumeStartup();
}
}
io_->PostIncomingMessage(InspectorAction::kSendMessage, session_id,
message);
}
void InspectorIoDelegate::EndSession(int session_id) {
connected_ = false;
io_->PostIncomingMessage(InspectorAction::kEndSession, session_id, "");
}
std::vector<std::string> InspectorIoDelegate::GetTargetIds() {
return {target_id_};
}
std::string InspectorIoDelegate::GetTargetTitle(const std::string &id) {
return script_name_.empty() ? GetProcessTitle() : script_name_;
}
std::string InspectorIoDelegate::GetTargetUrl(const std::string &id) {
return "file://" + script_path_;
}
bool IoSessionDelegate::WaitForFrontendMessageWhilePaused() {
io_->WaitForFrontendMessageWhilePaused();
return true;
}
void IoSessionDelegate::SendMessageToFrontend(
const v8_inspector::StringView &message) {
io_->Write(TransportAction::kSendMessage, io_->session_id_, message);
}
} // namespace inspector
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,181 @@
#ifndef SRC_INSPECTOR_IO_H_
#define SRC_INSPECTOR_IO_H_
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "inspector_socket_server.h"
#include "node_debug_options.h"
#include "node_mutex.h"
#include "uv.h"
#include <deque>
#include <memory>
#include <stddef.h>
#if !HAVE_INSPECTOR
#error("This header can only be used when inspector is enabled")
#endif
// Forward declaration to break recursive dependency chain with src/env.h.
namespace node {
class Environment;
} // namespace node
namespace v8_inspector {
class StringBuffer;
class StringView;
} // namespace v8_inspector
namespace node {
namespace inspector {
std::string FormatWsAddress(const std::string &host, int port,
const std::string &target_id,
bool include_protocol);
class InspectorIoDelegate;
enum class InspectorAction {
kStartSession,
kEndSession,
kSendMessage
};
// kKill closes connections and stops the server, kStop only stops the server
enum class TransportAction {
kKill,
kSendMessage,
kStop
};
class InspectorIo {
public:
InspectorIo(node::Environment *env, v8::Platform *platform,
const std::string &path, const DebugOptions &options,
bool wait_for_connect);
~InspectorIo();
// Start the inspector agent thread, waiting for it to initialize,
// and waiting as well for a connection if wait_for_connect.
bool Start();
// Stop the inspector agent thread.
void Stop();
bool IsStarted();
bool IsConnected();
void WaitForDisconnect();
// Called from thread to queue an incoming message and trigger
// DispatchMessages() on the main thread.
void PostIncomingMessage(InspectorAction action, int session_id,
const std::string &message);
void ResumeStartup() {
uv_sem_post(&thread_start_sem_);
}
void ServerDone() {
uv_close(reinterpret_cast<uv_handle_t *>(&thread_req_), nullptr);
}
int port() const { return port_; }
std::string host() const { return options_.host_name(); }
std::vector<std::string> GetTargetIds() const;
private:
template <typename Action>
using MessageQueue =
std::deque<std::tuple<Action, int,
std::unique_ptr<v8_inspector::StringBuffer>>>;
enum class State {
kNew,
kAccepting,
kConnected,
kDone,
kError,
kShutDown
};
// Callback for main_thread_req_'s uv_async_t
static void MainThreadReqAsyncCb(uv_async_t *req);
// Wrapper for agent->ThreadMain()
static void ThreadMain(void *agent);
// Runs a uv_loop_t
template <typename Transport>
void ThreadMain();
// Called by ThreadMain's loop when triggered by thread_req_, writes
// messages from outgoing_message_queue to the InspectorSockerServer
template <typename Transport>
static void IoThreadAsyncCb(uv_async_t *async);
void SetConnected(bool connected);
void DispatchMessages();
// Write action to outgoing_message_queue, and wake the thread
void Write(TransportAction action, int session_id,
const v8_inspector::StringView &message);
// Thread-safe append of message to a queue. Return true if the queue
// used to be empty.
template <typename ActionType>
bool AppendMessage(MessageQueue<ActionType> *vector, ActionType action,
int session_id,
std::unique_ptr<v8_inspector::StringBuffer> buffer);
// Used as equivalent of a thread-safe "pop" of an entire queue's content.
template <typename ActionType>
void SwapBehindLock(MessageQueue<ActionType> *vector1,
MessageQueue<ActionType> *vector2);
// Wait on incoming_message_cond_
void WaitForFrontendMessageWhilePaused();
// Broadcast incoming_message_cond_
void NotifyMessageReceived();
const DebugOptions options_;
// The IO thread runs its own uv_loop to implement the TCP server off
// the main thread.
uv_thread_t thread_;
// Used by Start() to wait for thread to initialize, or for it to initialize
// and receive a connection if wait_for_connect was requested.
uv_sem_t thread_start_sem_;
InspectorIoDelegate *delegate_;
State state_;
node::Environment *parent_env_;
// Attached to the uv_loop in ThreadMain()
uv_async_t thread_req_;
// Note that this will live while the async is being closed - likely, past
// the parent object lifespan
std::pair<uv_async_t, Agent *> *main_thread_req_;
std::unique_ptr<InspectorSessionDelegate> session_delegate_;
v8::Platform *platform_;
// Message queues
ConditionVariable incoming_message_cond_;
Mutex state_lock_; // Locked before mutating either queue.
MessageQueue<InspectorAction> incoming_message_queue_;
MessageQueue<TransportAction> outgoing_message_queue_;
MessageQueue<InspectorAction> dispatching_message_queue_;
bool dispatching_messages_;
int session_id_;
std::string script_name_;
std::string script_path_;
const bool wait_for_connect_;
int port_;
friend class DispatchMessagesTask;
friend class IoSessionDelegate;
friend void InterruptCallback(v8::Isolate *, void *agent);
};
std::unique_ptr<v8_inspector::StringBuffer> Utf8ToStringView(
const std::string &message);
} // namespace inspector
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#endif // SRC_INSPECTOR_IO_H_

View File

@@ -0,0 +1,643 @@
#include "inspector_socket.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "util.h"
#include "base64.h"
//#include "openssl/sha.h" // Sha-1 hash
#include "SHA1.h"
#include <string.h>
#include <vector>
#define ACCEPT_KEY_LENGTH base64_encoded_size(20)
#define BUFFER_GROWTH_CHUNK_SIZE 1024
#define DUMP_READS 0
#define DUMP_WRITES 0
namespace node {
namespace inspector {
static const char CLOSE_FRAME[] = {'\x88', '\x00'};
enum ws_decode_result {
FRAME_OK,
FRAME_INCOMPLETE,
FRAME_CLOSE,
FRAME_ERROR
};
#if DUMP_READS || DUMP_WRITES
static void dump_hex(const char *buf, size_t len) {
const char *ptr = buf;
const char *end = ptr + len;
const char *cptr;
char c;
int i;
while (ptr < end) {
cptr = ptr;
for (i = 0; i < 16 && ptr < end; i++) {
printf("%2.2X ", static_cast<unsigned char>(*(ptr++)));
}
for (i = 72 - (i * 4); i > 0; i--) {
printf(" ");
}
for (i = 0; i < 16 && cptr < end; i++) {
c = *(cptr++);
printf("%c", (c > 0x19) ? c : '.');
}
printf("\n");
}
printf("\n\n");
}
#endif
static void remove_from_beginning(std::vector<char> *buffer, size_t count) {
buffer->erase(buffer->begin(), buffer->begin() + count);
}
static void dispose_inspector(uv_handle_t *handle) {
InspectorSocket *inspector = inspector_from_stream(handle);
inspector_cb close =
inspector->ws_mode ? inspector->ws_state->close_cb : nullptr;
inspector->buffer.clear();
delete inspector->ws_state;
inspector->ws_state = nullptr;
if (close) {
close(inspector, 0);
}
}
static void close_connection(InspectorSocket *inspector) {
uv_handle_t *socket = reinterpret_cast<uv_handle_t *>(&inspector->tcp);
if (!uv_is_closing(socket)) {
uv_read_stop(reinterpret_cast<uv_stream_t *>(socket));
uv_close(socket, dispose_inspector);
}
}
struct WriteRequest {
WriteRequest(InspectorSocket *inspector, const char *data, size_t size)
: inspector(inspector),
storage(data, data + size),
buf(uv_buf_init(&storage[0], (unsigned int)storage.size())) {}
static WriteRequest *from_write_req(uv_write_t *req) {
return node::ContainerOf(&WriteRequest::req, req);
}
InspectorSocket *const inspector;
std::vector<char> storage;
uv_write_t req;
uv_buf_t buf;
};
// Cleanup
static void write_request_cleanup(uv_write_t *req, int status) {
delete WriteRequest::from_write_req(req);
}
static int write_to_client(InspectorSocket *inspector,
const char *msg,
size_t len,
uv_write_cb write_cb = write_request_cleanup) {
#if DUMP_WRITES
printf("%s (%ld bytes):\n", __FUNCTION__, len);
dump_hex(msg, len);
#endif
// Freed in write_request_cleanup
WriteRequest *wr = new WriteRequest(inspector, msg, len);
uv_stream_t *stream = reinterpret_cast<uv_stream_t *>(&inspector->tcp);
return uv_write(&wr->req, stream, &wr->buf, 1, write_cb) < 0;
}
// Constants for hybi-10 frame format.
typedef int OpCode;
const OpCode kOpCodeContinuation = 0x0;
const OpCode kOpCodeText = 0x1;
const OpCode kOpCodeBinary = 0x2;
const OpCode kOpCodeClose = 0x8;
const OpCode kOpCodePing = 0x9;
const OpCode kOpCodePong = 0xA;
const unsigned char kFinalBit = 0x80;
const unsigned char kReserved1Bit = 0x40;
const unsigned char kReserved2Bit = 0x20;
const unsigned char kReserved3Bit = 0x10;
const unsigned char kOpCodeMask = 0xF;
const unsigned char kMaskBit = 0x80;
const unsigned char kPayloadLengthMask = 0x7F;
const size_t kMaxSingleBytePayloadLength = 125;
const size_t kTwoBytePayloadLengthField = 126;
const size_t kEightBytePayloadLengthField = 127;
const size_t kMaskingKeyWidthInBytes = 4;
static std::vector<char> encode_frame_hybi17(const char *message,
size_t data_length) {
std::vector<char> frame;
OpCode op_code = kOpCodeText;
frame.push_back(kFinalBit | op_code);
if (data_length <= kMaxSingleBytePayloadLength) {
frame.push_back(static_cast<char>(data_length));
} else if (data_length <= 0xFFFF) {
frame.push_back(kTwoBytePayloadLengthField);
frame.push_back((data_length & 0xFF00) >> 8);
frame.push_back(data_length & 0xFF);
} else {
frame.push_back(kEightBytePayloadLengthField);
char extended_payload_length[8];
size_t remaining = data_length;
// Fill the length into extended_payload_length in the network byte order.
for (int i = 0; i < 8; ++i) {
extended_payload_length[7 - i] = remaining & 0xFF;
remaining >>= 8;
}
frame.insert(frame.end(), extended_payload_length,
extended_payload_length + 8);
CHECK_EQ(0, remaining);
}
frame.insert(frame.end(), message, message + data_length);
return frame;
}
static ws_decode_result decode_frame_hybi17(const std::vector<char> &buffer,
bool client_frame,
int *bytes_consumed,
std::vector<char> *output,
bool *compressed) {
*bytes_consumed = 0;
if (buffer.size() < 2)
return FRAME_INCOMPLETE;
auto it = buffer.begin();
unsigned char first_byte = *it++;
unsigned char second_byte = *it++;
bool final = (first_byte & kFinalBit) != 0;
bool reserved1 = (first_byte & kReserved1Bit) != 0;
bool reserved2 = (first_byte & kReserved2Bit) != 0;
bool reserved3 = (first_byte & kReserved3Bit) != 0;
int op_code = first_byte & kOpCodeMask;
bool masked = (second_byte & kMaskBit) != 0;
*compressed = reserved1;
if (!final || reserved2 || reserved3)
return FRAME_ERROR; // Only compression extension is supported.
bool closed = false;
switch (op_code) {
case kOpCodeClose:
closed = true;
break;
case kOpCodeText:
break;
case kOpCodeBinary: // We don't support binary frames yet.
case kOpCodeContinuation: // We don't support binary frames yet.
case kOpCodePing: // We don't support binary frames yet.
case kOpCodePong: // We don't support binary frames yet.
default:
return FRAME_ERROR;
}
// In Hybi-17 spec client MUST mask its frame.
if (client_frame && !masked) {
return FRAME_ERROR;
}
uint64_t payload_length64 = second_byte & kPayloadLengthMask;
if (payload_length64 > kMaxSingleBytePayloadLength) {
int extended_payload_length_size;
if (payload_length64 == kTwoBytePayloadLengthField) {
extended_payload_length_size = 2;
} else if (payload_length64 == kEightBytePayloadLengthField) {
extended_payload_length_size = 8;
} else {
return FRAME_ERROR;
}
if ((buffer.end() - it) < extended_payload_length_size)
return FRAME_INCOMPLETE;
payload_length64 = 0;
for (int i = 0; i < extended_payload_length_size; ++i) {
payload_length64 <<= 8;
payload_length64 |= static_cast<unsigned char>(*it++);
}
}
static const uint64_t max_payload_length = 0x7FFFFFFFFFFFFFFFull;
static const size_t max_length = SIZE_MAX;
if (payload_length64 > max_payload_length ||
payload_length64 > max_length - kMaskingKeyWidthInBytes) {
// WebSocket frame length too large.
return FRAME_ERROR;
}
size_t payload_length = static_cast<size_t>(payload_length64);
if (buffer.size() - kMaskingKeyWidthInBytes < payload_length)
return FRAME_INCOMPLETE;
std::vector<char>::const_iterator masking_key = it;
std::vector<char>::const_iterator payload = it + kMaskingKeyWidthInBytes;
for (size_t i = 0; i < payload_length; ++i) // Unmask the payload.
output->insert(output->end(),
payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes]);
size_t pos = it + kMaskingKeyWidthInBytes + payload_length - buffer.begin();
*bytes_consumed = (int)pos;
return closed ? FRAME_CLOSE : FRAME_OK;
}
static void invoke_read_callback(InspectorSocket *inspector,
int status, const uv_buf_t *buf) {
if (inspector->ws_state->read_cb) {
inspector->ws_state->read_cb(
reinterpret_cast<uv_stream_t *>(&inspector->tcp), status, buf);
}
}
static void shutdown_complete(InspectorSocket *inspector) {
close_connection(inspector);
}
static void on_close_frame_written(uv_write_t *req, int status) {
WriteRequest *wr = WriteRequest::from_write_req(req);
InspectorSocket *inspector = wr->inspector;
delete wr;
inspector->ws_state->close_sent = true;
if (inspector->ws_state->received_close) {
shutdown_complete(inspector);
}
}
static void close_frame_received(InspectorSocket *inspector) {
inspector->ws_state->received_close = true;
if (!inspector->ws_state->close_sent) {
invoke_read_callback(inspector, 0, 0);
write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
on_close_frame_written);
} else {
shutdown_complete(inspector);
}
}
static int parse_ws_frames(InspectorSocket *inspector) {
int bytes_consumed = 0;
std::vector<char> output;
bool compressed = false;
ws_decode_result r = decode_frame_hybi17(inspector->buffer,
true /* client_frame */,
&bytes_consumed, &output,
&compressed);
// Compressed frame means client is ignoring the headers and misbehaves
if (compressed || r == FRAME_ERROR) {
invoke_read_callback(inspector, UV_EPROTO, nullptr);
close_connection(inspector);
bytes_consumed = 0;
} else if (r == FRAME_CLOSE) {
close_frame_received(inspector);
bytes_consumed = 0;
} else if (r == FRAME_OK && inspector->ws_state->alloc_cb && inspector->ws_state->read_cb) {
uv_buf_t buffer;
size_t len = output.size();
inspector->ws_state->alloc_cb(
reinterpret_cast<uv_handle_t *>(&inspector->tcp),
len, &buffer);
CHECK_GE(buffer.len, len);
memcpy(buffer.base, &output[0], len);
invoke_read_callback(inspector, (int)len, &buffer);
}
return bytes_consumed;
}
static void prepare_buffer(uv_handle_t *stream, size_t len, uv_buf_t *buf) {
*buf = uv_buf_init(new char[len], (unsigned int)len);
}
static void reclaim_uv_buf(InspectorSocket *inspector, const uv_buf_t *buf,
ssize_t read) {
if (read > 0) {
std::vector<char> &buffer = inspector->buffer;
buffer.insert(buffer.end(), buf->base, buf->base + read);
}
delete[] buf->base;
}
static void websockets_data_cb(uv_stream_t *stream, ssize_t nread,
const uv_buf_t *buf) {
InspectorSocket *inspector = inspector_from_stream(stream);
reclaim_uv_buf(inspector, buf, nread);
if (nread < 0 || nread == UV_EOF) {
inspector->connection_eof = true;
if (!inspector->shutting_down && inspector->ws_state->read_cb) {
inspector->ws_state->read_cb(stream, nread, nullptr);
}
if (inspector->ws_state->close_sent &&
!inspector->ws_state->received_close) {
shutdown_complete(inspector); // invoke callback
}
} else {
#if DUMP_READS
printf("%s read %ld bytes\n", __FUNCTION__, nread);
if (nread > 0) {
dump_hex(inspector->buffer.data() + inspector->buffer.size() - nread,
nread);
}
#endif
// 2. Parse.
int processed = 0;
do {
processed = parse_ws_frames(inspector);
// 3. Fix the buffer size & length
if (processed > 0) {
remove_from_beginning(&inspector->buffer, processed);
}
} while (processed > 0 && !inspector->buffer.empty());
}
}
int inspector_read_start(InspectorSocket *inspector,
uv_alloc_cb alloc_cb, uv_read_cb read_cb) {
CHECK(inspector->ws_mode);
CHECK(!inspector->shutting_down || read_cb == nullptr);
inspector->ws_state->close_sent = false;
inspector->ws_state->alloc_cb = alloc_cb;
inspector->ws_state->read_cb = read_cb;
int err =
uv_read_start(reinterpret_cast<uv_stream_t *>(&inspector->tcp),
prepare_buffer,
websockets_data_cb);
if (err < 0) {
close_connection(inspector);
}
return err;
}
void inspector_read_stop(InspectorSocket *inspector) {
uv_read_stop(reinterpret_cast<uv_stream_t *>(&inspector->tcp));
inspector->ws_state->alloc_cb = nullptr;
inspector->ws_state->read_cb = nullptr;
}
static void generate_accept_string(const std::string &client_key,
char (*buffer)[ACCEPT_KEY_LENGTH]) {
// Magic string from websockets spec.
static const char ws_magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
std::string input(client_key + ws_magic);
// char hash[SHA_DIGEST_LENGTH];
// SHA1(reinterpret_cast<const unsigned char*>(&input[0]), input.size(),
// reinterpret_cast<unsigned char*>(hash));
se::SHA1Sum::Hash hash = {0};
se::SHA1Sum s;
s.update(reinterpret_cast<const unsigned char *>(&input[0]), (uint32_t)input.size());
s.finish(hash);
node::base64_encode((char *)hash, sizeof(hash), *buffer, sizeof(*buffer));
}
static int header_value_cb(http_parser *parser, const char *at, size_t length) {
static const char SEC_WEBSOCKET_KEY_HEADER[] = "Sec-WebSocket-Key";
auto inspector = static_cast<InspectorSocket *>(parser->data);
auto state = inspector->http_parsing_state;
state->parsing_value = true;
if (state->current_header.size() == sizeof(SEC_WEBSOCKET_KEY_HEADER) - 1 &&
node::StringEqualNoCaseN(state->current_header.data(),
SEC_WEBSOCKET_KEY_HEADER,
sizeof(SEC_WEBSOCKET_KEY_HEADER) - 1)) {
state->ws_key.append(at, length);
}
return 0;
}
static int header_field_cb(http_parser *parser, const char *at, size_t length) {
auto inspector = static_cast<InspectorSocket *>(parser->data);
auto state = inspector->http_parsing_state;
if (state->parsing_value) {
state->parsing_value = false;
state->current_header.clear();
}
state->current_header.append(at, length);
return 0;
}
static int path_cb(http_parser *parser, const char *at, size_t length) {
auto inspector = static_cast<InspectorSocket *>(parser->data);
auto state = inspector->http_parsing_state;
state->path.append(at, length);
return 0;
}
static void handshake_complete(InspectorSocket *inspector) {
uv_read_stop(reinterpret_cast<uv_stream_t *>(&inspector->tcp));
handshake_cb callback = inspector->http_parsing_state->callback;
inspector->ws_state = new ws_state_s();
inspector->ws_mode = true;
callback(inspector, kInspectorHandshakeUpgraded,
inspector->http_parsing_state->path);
}
static void cleanup_http_parsing_state(InspectorSocket *inspector) {
delete inspector->http_parsing_state;
inspector->http_parsing_state = nullptr;
}
static void report_handshake_failure_cb(uv_handle_t *handle) {
dispose_inspector(handle);
InspectorSocket *inspector = inspector_from_stream(handle);
handshake_cb cb = inspector->http_parsing_state->callback;
cleanup_http_parsing_state(inspector);
cb(inspector, kInspectorHandshakeFailed, std::string());
}
static void close_and_report_handshake_failure(InspectorSocket *inspector) {
uv_handle_t *socket = reinterpret_cast<uv_handle_t *>(&inspector->tcp);
if (uv_is_closing(socket)) {
report_handshake_failure_cb(socket);
} else {
uv_read_stop(reinterpret_cast<uv_stream_t *>(socket));
uv_close(socket, report_handshake_failure_cb);
}
}
static void then_close_and_report_failure(uv_write_t *req, int status) {
InspectorSocket *inspector = WriteRequest::from_write_req(req)->inspector;
write_request_cleanup(req, status);
close_and_report_handshake_failure(inspector);
}
static void handshake_failed(InspectorSocket *inspector) {
const char HANDSHAKE_FAILED_RESPONSE[] =
"HTTP/1.0 400 Bad Request\r\n"
"Content-Type: text/html; charset=UTF-8\r\n\r\n"
"WebSockets request was expected\r\n";
write_to_client(inspector, HANDSHAKE_FAILED_RESPONSE,
sizeof(HANDSHAKE_FAILED_RESPONSE) - 1,
then_close_and_report_failure);
}
// init_handshake references message_complete_cb
static void init_handshake(InspectorSocket *socket);
static int message_complete_cb(http_parser *parser) {
InspectorSocket *inspector = static_cast<InspectorSocket *>(parser->data);
struct http_parsing_state_s *state = inspector->http_parsing_state;
if (parser->method != HTTP_GET) {
handshake_failed(inspector);
} else if (!parser->upgrade) {
if (state->callback(inspector, kInspectorHandshakeHttpGet, state->path)) {
init_handshake(inspector);
} else {
handshake_failed(inspector);
}
} else if (state->ws_key.empty()) {
handshake_failed(inspector);
} else if (state->callback(inspector, kInspectorHandshakeUpgrading,
state->path)) {
char accept_string[ACCEPT_KEY_LENGTH];
generate_accept_string(state->ws_key, &accept_string);
const char accept_ws_prefix[] =
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: ";
const char accept_ws_suffix[] = "\r\n\r\n";
std::string reply(accept_ws_prefix, sizeof(accept_ws_prefix) - 1);
reply.append(accept_string, sizeof(accept_string));
reply.append(accept_ws_suffix, sizeof(accept_ws_suffix) - 1);
if (write_to_client(inspector, &reply[0], reply.size()) >= 0) {
handshake_complete(inspector);
inspector->http_parsing_state->done = true;
} else {
close_and_report_handshake_failure(inspector);
}
} else {
handshake_failed(inspector);
}
return 0;
}
static void data_received_cb(uv_stream_s *tcp, ssize_t nread,
const uv_buf_t *buf) {
#if DUMP_READS
if (nread >= 0) {
printf("%s (%ld bytes)\n", __FUNCTION__, nread);
dump_hex(buf->base, nread);
} else {
printf("[%s:%d] %s\n", __FUNCTION__, __LINE__, uv_err_name(nread));
}
#endif
InspectorSocket *inspector = inspector_from_stream(tcp);
reclaim_uv_buf(inspector, buf, nread);
if (nread < 0 || nread == UV_EOF) {
close_and_report_handshake_failure(inspector);
} else {
http_parsing_state_s *state = inspector->http_parsing_state;
http_parser *parser = &state->parser;
http_parser_execute(parser, &state->parser_settings,
inspector->buffer.data(), nread);
remove_from_beginning(&inspector->buffer, nread);
if (parser->http_errno != HPE_OK) {
handshake_failed(inspector);
}
if (inspector->http_parsing_state->done) {
cleanup_http_parsing_state(inspector);
}
}
}
static void init_handshake(InspectorSocket *socket) {
http_parsing_state_s *state = socket->http_parsing_state;
CHECK_NE(state, nullptr);
state->current_header.clear();
state->ws_key.clear();
state->path.clear();
state->done = false;
http_parser_init(&state->parser, HTTP_REQUEST);
state->parser.data = socket;
http_parser_settings *settings = &state->parser_settings;
http_parser_settings_init(settings);
settings->on_header_field = header_field_cb;
settings->on_header_value = header_value_cb;
settings->on_message_complete = message_complete_cb;
settings->on_url = path_cb;
}
int inspector_accept(uv_stream_t *server, InspectorSocket *socket,
handshake_cb callback) {
CHECK_NE(callback, nullptr);
CHECK_EQ(socket->http_parsing_state, nullptr);
socket->http_parsing_state = new http_parsing_state_s();
uv_stream_t *tcp = reinterpret_cast<uv_stream_t *>(&socket->tcp);
int err = uv_tcp_init(server->loop, &socket->tcp);
if (err == 0) {
err = uv_accept(server, tcp);
}
if (err == 0) {
init_handshake(socket);
socket->http_parsing_state->callback = callback;
err = uv_read_start(tcp, prepare_buffer,
data_received_cb);
}
if (err != 0) {
uv_close(reinterpret_cast<uv_handle_t *>(tcp), NULL);
}
return err;
}
void inspector_write(InspectorSocket *inspector, const char *data,
size_t len) {
if (inspector->ws_mode) {
std::vector<char> output = encode_frame_hybi17(data, len);
write_to_client(inspector, &output[0], output.size());
} else {
write_to_client(inspector, data, len);
}
}
void inspector_close(InspectorSocket *inspector,
inspector_cb callback) {
// libuv throws assertions when closing stream that's already closed - we
// need to do the same.
CHECK(!uv_is_closing(reinterpret_cast<uv_handle_t *>(&inspector->tcp)));
CHECK(!inspector->shutting_down);
inspector->shutting_down = true;
inspector->ws_state->close_cb = callback;
if (inspector->connection_eof) {
close_connection(inspector);
} else {
inspector_read_stop(inspector);
write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
on_close_frame_written);
inspector_read_start(inspector, nullptr, nullptr);
}
}
bool inspector_is_active(const InspectorSocket *inspector) {
const uv_handle_t *tcp =
reinterpret_cast<const uv_handle_t *>(&inspector->tcp);
return !inspector->shutting_down && !uv_is_closing(tcp);
}
void InspectorSocket::reinit() {
http_parsing_state = nullptr;
ws_state = nullptr;
buffer.clear();
ws_mode = false;
shutting_down = false;
connection_eof = false;
}
} // namespace inspector
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,109 @@
#ifndef SRC_INSPECTOR_SOCKET_H_
#define SRC_INSPECTOR_SOCKET_H_
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "http_parser.h"
#include "util.h"
#include "uv.h"
#include <string>
#include <vector>
namespace node {
namespace inspector {
enum inspector_handshake_event {
kInspectorHandshakeUpgrading,
kInspectorHandshakeUpgraded,
kInspectorHandshakeHttpGet,
kInspectorHandshakeFailed
};
class InspectorSocket;
typedef void (*inspector_cb)(InspectorSocket *, int);
// Notifies as handshake is progressing. Returning false as a response to
// kInspectorHandshakeUpgrading or kInspectorHandshakeHttpGet event will abort
// the connection. inspector_write can be used from the callback.
typedef bool (*handshake_cb)(InspectorSocket *,
enum inspector_handshake_event state,
const std::string &path);
struct http_parsing_state_s {
http_parser parser;
http_parser_settings parser_settings;
handshake_cb callback;
bool done;
bool parsing_value;
std::string ws_key;
std::string path;
std::string current_header;
};
struct ws_state_s {
uv_alloc_cb alloc_cb;
uv_read_cb read_cb;
inspector_cb close_cb;
bool close_sent;
bool received_close;
};
// HTTP Wrapper around a uv_tcp_t
class InspectorSocket {
public:
InspectorSocket() : data(nullptr),
http_parsing_state(nullptr),
ws_state(nullptr),
buffer(0),
ws_mode(false),
shutting_down(false),
connection_eof(false) {}
void reinit();
void *data;
struct http_parsing_state_s *http_parsing_state;
struct ws_state_s *ws_state;
std::vector<char> buffer;
uv_tcp_t tcp;
bool ws_mode;
bool shutting_down;
bool connection_eof;
private:
NODE_DISALLOW_COPY_AND_ASSIGN(InspectorSocket);
};
int inspector_accept(uv_stream_t *server, InspectorSocket *inspector,
handshake_cb callback);
void inspector_close(InspectorSocket *inspector,
inspector_cb callback);
// Callbacks will receive stream handles. Use inspector_from_stream to get
// InspectorSocket* from the stream handle.
int inspector_read_start(InspectorSocket *inspector, uv_alloc_cb,
uv_read_cb);
void inspector_read_stop(InspectorSocket *inspector);
void inspector_write(InspectorSocket *inspector,
const char *data, size_t len);
bool inspector_is_active(const InspectorSocket *inspector);
inline InspectorSocket *inspector_from_stream(uv_tcp_t *stream) {
return node::ContainerOf(&InspectorSocket::tcp, stream);
}
inline InspectorSocket *inspector_from_stream(uv_stream_t *stream) {
return inspector_from_stream(reinterpret_cast<uv_tcp_t *>(stream));
}
inline InspectorSocket *inspector_from_stream(uv_handle_t *stream) {
return inspector_from_stream(reinterpret_cast<uv_tcp_t *>(stream));
}
} // namespace inspector
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#endif // SRC_INSPECTOR_SOCKET_H_

View File

@@ -0,0 +1,700 @@
#include "inspector_socket_server.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "node.h"
#include "uv.h"
#include "zlib.h"
#include <algorithm>
#include <map>
#include <set>
#include <sstream>
namespace node {
namespace inspector {
// Function is declared in inspector_io.h so the rest of the node does not
// depend on inspector_socket_server.h
std::string FormatWsAddress(const std::string &host, int port,
const std::string &target_id,
bool include_protocol) {
// Host is valid (socket was bound) so colon means it's a v6 IP address
bool v6 = host.find(':') != std::string::npos;
std::ostringstream url;
if (include_protocol)
url << "ws://";
if (v6) {
url << '[';
}
url << host;
if (v6) {
url << ']';
}
url << ':' << port << '/' << target_id;
return url.str();
}
namespace {
static const uint8_t PROTOCOL_JSON[] = {
#include "v8_inspector_protocol_json.h" // NOLINT(build/include_order)
};
template <typename T>
static std::string to_string(T arg) {
std::stringstream ss;
ss << arg;
return ss.str();
}
void Escape(std::string *string) {
for (char &c : *string) {
c = (c == '\"' || c == '\\') ? '_' : c;
}
}
std::string MapToString(const std::map<std::string, std::string> &object) {
bool first = true;
std::ostringstream json;
json << "{\n";
for (const auto &name_value : object) {
if (!first)
json << ",\n";
first = false;
json << " \"" << name_value.first << "\": \"";
json << name_value.second << "\"";
}
json << "\n} ";
return json.str();
}
std::string MapsToString(
const std::vector<std::map<std::string, std::string>> &array) {
bool first = true;
std::ostringstream json;
json << "[ ";
for (const auto &object : array) {
if (!first)
json << ", ";
first = false;
json << MapToString(object);
}
json << "]\n\n";
return json.str();
}
const char *MatchPathSegment(const char *path, const char *expected) {
size_t len = strlen(expected);
if (StringEqualNoCaseN(path, expected, len)) {
if (path[len] == '/') return path + len + 1;
if (path[len] == '\0') return path + len;
}
return nullptr;
}
void OnBufferAlloc(uv_handle_t *handle, size_t len, uv_buf_t *buf) {
buf->base = new char[len];
buf->len = len;
}
void PrintDebuggerReadyMessage(const std::string &host,
int port,
const std::vector<std::string> &ids,
FILE *out) {
if (out == NULL) {
return;
}
std::vector<std::tuple<std::string, bool, std::string>> ipList;
{
char buf[512];
uv_interface_address_t *info = nullptr;
int count = 0;
int i = 0;
uv_interface_addresses(&info, &count);
i = count;
if (errno) {
SE_LOGE("failed to get addresses %s", strerror(errno));
}
SE_LOGD("Number of interfaces: %d", count);
while (i--) {
auto &networkInterface = info[i];
if (networkInterface.address.address4.sin_family == AF_INET) {
uv_ip4_name(&networkInterface.address.address4, buf, sizeof(buf));
ipList.emplace_back(networkInterface.name, networkInterface.is_internal, buf);
}
}
uv_free_interface_addresses(info, count);
}
// failed to query device interfaces,
if (ipList.empty()) {
#if ANDROID
SE_LOGD("Please query IP by running the following command in terminal: adb shell ip -4 -br addr");
#endif
ipList.emplace_back("none", false, "IP_ADDR_OF_THIS_DEVICE");
}
for (const std::string &id : ids) {
if (host != "0.0.0.0") {
SE_LOGD("Debugger listening..., visit [ devtools://devtools/bundled/js_app.html?v8only=true&ws=%s ] in chrome browser to debug!",
FormatWsAddress(host, port, id, false).c_str());
} else {
SE_LOGD("Debugger listening..., visit [");
for (auto &nif : ipList) {
SE_LOGD(" devtools://devtools/bundled/js_app.html?v8only=true&ws=%s",
FormatWsAddress(std::get<2>(nif), port, id, false).c_str());
}
SE_LOGD(" ] in chrome browser to debug!");
}
}
SE_LOGD("For help see %s",
"https://nodejs.org/en/docs/inspector");
}
void SendHttpResponse(InspectorSocket *socket, const std::string &response) {
const char HEADERS[] =
"HTTP/1.0 200 OK\r\n"
"Content-Type: application/json; charset=UTF-8\r\n"
"Cache-Control: no-cache\r\n"
"Content-Length: %zu\r\n"
"\r\n";
char header[sizeof(HEADERS) + 20];
int header_len = snprintf(header, sizeof(header), HEADERS, response.size());
inspector_write(socket, header, header_len);
inspector_write(socket, response.data(), response.size());
}
void SendVersionResponse(InspectorSocket *socket) {
std::map<std::string, std::string> response;
response["Browser"] = "Cocos Games"; //cjh
response["Protocol-Version"] = "1.1";
SendHttpResponse(socket, MapToString(response));
}
void SendProtocolJson(InspectorSocket *socket) {
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
CHECK_EQ(Z_OK, inflateInit(&strm));
static const size_t kDecompressedSize =
PROTOCOL_JSON[0] * 0x10000u +
PROTOCOL_JSON[1] * 0x100u +
PROTOCOL_JSON[2];
strm.next_in = const_cast<uint8_t *>(PROTOCOL_JSON + 3);
strm.avail_in = sizeof(PROTOCOL_JSON) - 3;
std::string data(kDecompressedSize, '\0');
strm.next_out = reinterpret_cast<Byte *>(&data[0]);
strm.avail_out = static_cast<uInt>(data.size());
CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH));
CHECK_EQ(0, strm.avail_out);
CHECK_EQ(Z_OK, inflateEnd(&strm));
SendHttpResponse(socket, data);
}
int GetSocketHost(uv_tcp_t *socket, std::string *out_host) {
char ip[INET6_ADDRSTRLEN];
sockaddr_storage addr;
int len = sizeof(addr);
int err = uv_tcp_getsockname(socket,
reinterpret_cast<struct sockaddr *>(&addr),
&len);
if (err != 0)
return err;
if (addr.ss_family == AF_INET6) {
const sockaddr_in6 *v6 = reinterpret_cast<const sockaddr_in6 *>(&addr);
err = uv_ip6_name(v6, ip, sizeof(ip));
} else {
const sockaddr_in *v4 = reinterpret_cast<const sockaddr_in *>(&addr);
err = uv_ip4_name(v4, ip, sizeof(ip));
}
if (err != 0)
return err;
*out_host = ip;
return err;
}
} // namespace
class Closer {
public:
explicit Closer(InspectorSocketServer *server) : server_(server),
close_count_(0) {}
void AddCallback(InspectorSocketServer::ServerCallback callback) {
if (callback == nullptr)
return;
callbacks_.insert(callback);
}
void DecreaseExpectedCount() {
--close_count_;
NotifyIfDone();
}
void IncreaseExpectedCount() {
++close_count_;
}
void NotifyIfDone() {
if (close_count_ == 0) {
for (auto callback : callbacks_) {
callback(server_);
}
InspectorSocketServer *server = server_;
delete server->closer_;
server->closer_ = nullptr;
}
}
private:
InspectorSocketServer *server_;
std::set<InspectorSocketServer::ServerCallback> callbacks_;
int close_count_;
};
class SocketSession {
public:
static int Accept(InspectorSocketServer *server, int server_port,
uv_stream_t *server_socket);
void Send(const std::string &message);
void Close();
int id() const { return id_; }
bool IsForTarget(const std::string &target_id) const {
return target_id_ == target_id;
}
static int ServerPortForClient(InspectorSocket *client) {
return From(client)->server_port_;
}
private:
SocketSession(InspectorSocketServer *server, int server_port);
static SocketSession *From(InspectorSocket *socket) {
return node::ContainerOf(&SocketSession::socket_, socket);
}
enum class State { kHttp,
kWebSocket,
kClosing,
kEOF,
kDeclined };
static bool HandshakeCallback(InspectorSocket *socket,
enum inspector_handshake_event state,
const std::string &path);
static void ReadCallback(uv_stream_t *stream, ssize_t read,
const uv_buf_t *buf);
static void CloseCallback(InspectorSocket *socket, int code);
void FrontendConnected();
void SetDeclined() { state_ = State::kDeclined; }
void SetTargetId(const std::string &target_id) {
CHECK(target_id_.empty());
target_id_ = target_id;
}
const int id_;
InspectorSocket socket_;
InspectorSocketServer *server_;
std::string target_id_;
State state_;
const int server_port_;
};
class ServerSocket {
public:
static int Listen(InspectorSocketServer *inspector_server,
sockaddr *addr, uv_loop_t *loop);
void Close() {
uv_close(reinterpret_cast<uv_handle_t *>(&tcp_socket_),
SocketClosedCallback);
}
int port() const { return port_; }
private:
explicit ServerSocket(InspectorSocketServer *server)
: tcp_socket_(uv_tcp_t()),
server_(server),
port_(-1) {}
template <typename UvHandle>
static ServerSocket *FromTcpSocket(UvHandle *socket) {
return node::ContainerOf(&ServerSocket::tcp_socket_,
reinterpret_cast<uv_tcp_t *>(socket));
}
static void SocketConnectedCallback(uv_stream_t *tcp_socket, int status);
static void SocketClosedCallback(uv_handle_t *tcp_socket);
static void FreeOnCloseCallback(uv_handle_t *tcp_socket_) {
delete FromTcpSocket(tcp_socket_);
}
int DetectPort();
uv_tcp_t tcp_socket_;
InspectorSocketServer *server_;
int port_;
};
InspectorSocketServer::InspectorSocketServer(SocketServerDelegate *delegate,
uv_loop_t *loop,
const std::string &host,
int port,
FILE *out) : loop_(loop),
delegate_(delegate),
host_(host),
port_(port),
closer_(nullptr),
next_session_id_(0),
out_(out) {
state_ = ServerState::kNew;
}
bool InspectorSocketServer::SessionStarted(SocketSession *session,
const std::string &id) {
if (TargetExists(id) && delegate_->StartSession(session->id(), id)) {
connected_sessions_[session->id()] = session;
return true;
} else {
return false;
}
}
void InspectorSocketServer::SessionTerminated(SocketSession *session) {
int id = session->id();
if (connected_sessions_.erase(id) != 0) {
delegate_->EndSession(id);
if (connected_sessions_.empty()) {
if (state_ == ServerState::kRunning && !server_sockets_.empty()) {
PrintDebuggerReadyMessage(host_, server_sockets_[0]->port(),
delegate_->GetTargetIds(), out_);
}
if (state_ == ServerState::kStopped) {
delegate_->ServerDone();
}
}
}
delete session;
}
bool InspectorSocketServer::HandleGetRequest(InspectorSocket *socket,
const std::string &path) {
const char *command = MatchPathSegment(path.c_str(), "/json");
if (command == nullptr)
return false;
if (MatchPathSegment(command, "list") || command[0] == '\0') {
SendListResponse(socket);
return true;
} else if (MatchPathSegment(command, "protocol")) {
SendProtocolJson(socket);
return true;
} else if (MatchPathSegment(command, "version")) {
SendVersionResponse(socket);
return true;
} else if (const char *target_id = MatchPathSegment(command, "activate")) {
if (TargetExists(target_id)) {
SendHttpResponse(socket, "Target activated");
return true;
}
return false;
}
return false;
}
void InspectorSocketServer::SendListResponse(InspectorSocket *socket) {
std::vector<std::map<std::string, std::string>> response;
for (const std::string &id : delegate_->GetTargetIds()) {
response.push_back(std::map<std::string, std::string>());
std::map<std::string, std::string> &target_map = response.back();
target_map["description"] = "node.js instance";
target_map["faviconUrl"] = "https://nodejs.org/static/favicon.ico";
target_map["id"] = id;
target_map["title"] = delegate_->GetTargetTitle(id);
Escape(&target_map["title"]);
target_map["type"] = "node";
// This attribute value is a "best effort" URL that is passed as a JSON
// string. It is not guaranteed to resolve to a valid resource.
target_map["url"] = delegate_->GetTargetUrl(id);
Escape(&target_map["url"]);
bool connected = false;
for (const auto &session : connected_sessions_) {
if (session.second->IsForTarget(id)) {
connected = true;
break;
}
}
if (!connected) {
std::string host;
int port = SocketSession::ServerPortForClient(socket);
GetSocketHost(&socket->tcp, &host);
std::ostringstream frontend_url;
frontend_url << "devtools://devtools/bundled";
frontend_url << "/js_app.html?experiments=true&v8only=true&ws=";
frontend_url << FormatWsAddress(host, port, id, false);
target_map["devtoolsFrontendUrl"] += frontend_url.str();
target_map["webSocketDebuggerUrl"] =
FormatWsAddress(host, port, id, true);
}
}
SendHttpResponse(socket, MapsToString(response));
}
bool InspectorSocketServer::Start() {
CHECK_EQ(state_, ServerState::kNew);
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICSERV;
hints.ai_socktype = SOCK_STREAM;
uv_getaddrinfo_t req;
const std::string port_string = to_string(port_);
int err = uv_getaddrinfo(loop_, &req, nullptr, host_.c_str(),
port_string.c_str(), &hints);
if (err < 0) {
SE_LOGE("Unable to resolve \"%s\": %s\n", host_.c_str(),
uv_strerror(err));
return false;
}
for (addrinfo *address = req.addrinfo; address != nullptr;
address = address->ai_next) {
err = ServerSocket::Listen(this, address->ai_addr, loop_);
}
uv_freeaddrinfo(req.addrinfo);
if (!connected_sessions_.empty()) {
return true;
}
// We only show error if we failed to start server on all addresses. We only
// show one error, for the last address.
if (server_sockets_.empty()) {
SE_LOGE("Starting inspector on %s:%d failed: %s\n",
host_.c_str(), port_, uv_strerror(err));
if (err == UV_EADDRINUSE) {
SE_LOGE("[FATAL ERROR]: Port [:%s] is occupied by other processes, try to kill the previous debug process or change the port number in `jsb_enable_debugger`.\n", port_string.c_str());
} else {
SE_LOGE("[FATAL ERROR]: Failed to bind port [%s], error code: %d.\n", port_string.c_str(), err);
}
assert(false); //failed to start socket server for chrome debugger
return false;
}
state_ = ServerState::kRunning;
// getaddrinfo sorts the addresses, so the first port is most relevant.
PrintDebuggerReadyMessage(host_, server_sockets_[0]->port(),
delegate_->GetTargetIds(), out_);
return true;
}
void InspectorSocketServer::Stop(ServerCallback cb) {
CHECK_EQ(state_, ServerState::kRunning);
if (closer_ == nullptr) {
closer_ = new Closer(this);
}
closer_->AddCallback(cb);
closer_->IncreaseExpectedCount();
state_ = ServerState::kStopping;
for (ServerSocket *server_socket : server_sockets_)
server_socket->Close();
closer_->NotifyIfDone();
}
void InspectorSocketServer::TerminateConnections() {
for (const auto &session : connected_sessions_) {
session.second->Close();
}
}
bool InspectorSocketServer::TargetExists(const std::string &id) {
const std::vector<std::string> &target_ids = delegate_->GetTargetIds();
const auto &found = std::find(target_ids.begin(), target_ids.end(), id);
return found != target_ids.end();
}
void InspectorSocketServer::Send(int session_id, const std::string &message) {
auto session_iterator = connected_sessions_.find(session_id);
if (session_iterator != connected_sessions_.end()) {
session_iterator->second->Send(message);
}
}
void InspectorSocketServer::ServerSocketListening(ServerSocket *server_socket) {
server_sockets_.push_back(server_socket);
}
void InspectorSocketServer::ServerSocketClosed(ServerSocket *server_socket) {
CHECK_EQ(state_, ServerState::kStopping);
server_sockets_.erase(std::remove(server_sockets_.begin(),
server_sockets_.end(), server_socket),
server_sockets_.end());
if (!server_sockets_.empty())
return;
if (closer_ != nullptr) {
closer_->DecreaseExpectedCount();
}
if (connected_sessions_.empty()) {
delegate_->ServerDone();
}
state_ = ServerState::kStopped;
}
int InspectorSocketServer::Port() const {
if (!server_sockets_.empty()) {
return server_sockets_[0]->port();
}
return port_;
}
// InspectorSession tracking
SocketSession::SocketSession(InspectorSocketServer *server, int server_port)
: id_(server->GenerateSessionId()),
server_(server),
state_(State::kHttp),
server_port_(server_port) {}
void SocketSession::Close() {
CHECK_NE(state_, State::kClosing);
state_ = State::kClosing;
inspector_close(&socket_, CloseCallback);
}
// static
int SocketSession::Accept(InspectorSocketServer *server, int server_port,
uv_stream_t *server_socket) {
// Memory is freed when the socket closes.
SocketSession *session = new SocketSession(server, server_port);
int err = inspector_accept(server_socket, &session->socket_,
HandshakeCallback);
if (err != 0) {
delete session;
}
return err;
}
// static
bool SocketSession::HandshakeCallback(InspectorSocket *socket,
inspector_handshake_event event,
const std::string &path) {
SocketSession *session = SocketSession::From(socket);
InspectorSocketServer *server = session->server_;
const std::string &id = path.empty() ? path : path.substr(1);
switch (event) {
case kInspectorHandshakeHttpGet:
return server->HandleGetRequest(socket, path);
case kInspectorHandshakeUpgrading:
if (server->SessionStarted(session, id)) {
session->SetTargetId(id);
return true;
} else {
session->SetDeclined();
return false;
}
case kInspectorHandshakeUpgraded:
session->FrontendConnected();
return true;
case kInspectorHandshakeFailed:
server->SessionTerminated(session);
return false;
default:
UNREACHABLE();
return false;
}
}
// static
void SocketSession::CloseCallback(InspectorSocket *socket, int code) {
SocketSession *session = SocketSession::From(socket);
CHECK_EQ(State::kClosing, session->state_);
session->server_->SessionTerminated(session);
}
void SocketSession::FrontendConnected() {
CHECK_EQ(State::kHttp, state_);
state_ = State::kWebSocket;
inspector_read_start(&socket_, OnBufferAlloc, ReadCallback);
}
// static
void SocketSession::ReadCallback(uv_stream_t *stream, ssize_t read,
const uv_buf_t *buf) {
InspectorSocket *socket = inspector_from_stream(stream);
SocketSession *session = SocketSession::From(socket);
if (read > 0) {
session->server_->MessageReceived(session->id_,
std::string(buf->base, read));
} else {
session->Close();
}
if (buf != nullptr && buf->base != nullptr)
delete[] buf->base;
}
void SocketSession::Send(const std::string &message) {
inspector_write(&socket_, message.data(), message.length());
}
// ServerSocket implementation
int ServerSocket::DetectPort() {
sockaddr_storage addr;
int len = sizeof(addr);
int err = uv_tcp_getsockname(&tcp_socket_,
reinterpret_cast<struct sockaddr *>(&addr), &len);
if (err != 0)
return err;
int port;
if (addr.ss_family == AF_INET6)
port = reinterpret_cast<const sockaddr_in6 *>(&addr)->sin6_port;
else
port = reinterpret_cast<const sockaddr_in *>(&addr)->sin_port;
port_ = ntohs(port);
return err;
}
// static
int ServerSocket::Listen(InspectorSocketServer *inspector_server,
sockaddr *addr, uv_loop_t *loop) {
ServerSocket *server_socket = new ServerSocket(inspector_server);
uv_tcp_t *server = &server_socket->tcp_socket_;
CHECK_EQ(0, uv_tcp_init(loop, server));
int err = uv_tcp_bind(server, addr, 0);
if (err == 0) {
err = uv_listen(reinterpret_cast<uv_stream_t *>(server), 1,
ServerSocket::SocketConnectedCallback);
}
if (err == 0) {
err = server_socket->DetectPort();
}
if (err == 0) {
inspector_server->ServerSocketListening(server_socket);
} else {
uv_close(reinterpret_cast<uv_handle_t *>(server), FreeOnCloseCallback);
}
return err;
}
// static
void ServerSocket::SocketConnectedCallback(uv_stream_t *tcp_socket,
int status) {
if (status == 0) {
ServerSocket *server_socket = ServerSocket::FromTcpSocket(tcp_socket);
// Memory is freed when the socket closes.
SocketSession::Accept(server_socket->server_, server_socket->port_,
tcp_socket);
}
}
// static
void ServerSocket::SocketClosedCallback(uv_handle_t *tcp_socket) {
ServerSocket *server_socket = ServerSocket::FromTcpSocket(tcp_socket);
server_socket->server_->ServerSocketClosed(server_socket);
delete server_socket;
}
} // namespace inspector
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,105 @@
#ifndef SRC_INSPECTOR_SOCKET_SERVER_H_
#define SRC_INSPECTOR_SOCKET_SERVER_H_
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "inspector_agent.h"
#include "inspector_socket.h"
#include "uv.h"
#include <map>
#include <string>
#include <vector>
#if !HAVE_INSPECTOR
#error("This header can only be used when inspector is enabled")
#endif
namespace node {
namespace inspector {
class Closer;
class SocketSession;
class ServerSocket;
class SocketServerDelegate {
public:
virtual bool StartSession(int session_id, const std::string &target_id) = 0;
virtual void EndSession(int session_id) = 0;
virtual void MessageReceived(int session_id, const std::string &message) = 0;
virtual std::vector<std::string> GetTargetIds() = 0;
virtual std::string GetTargetTitle(const std::string &id) = 0;
virtual std::string GetTargetUrl(const std::string &id) = 0;
virtual void ServerDone() = 0;
};
// HTTP Server, writes messages requested as TransportActions, and responds
// to HTTP requests and WS upgrades.
class InspectorSocketServer {
public:
using ServerCallback = void (*)(InspectorSocketServer *);
InspectorSocketServer(SocketServerDelegate *delegate,
uv_loop_t *loop,
const std::string &host,
int port,
FILE *out = stderr);
// Start listening on host/port
bool Start();
// Called by the TransportAction sent with InspectorIo::Write():
// kKill and kStop
void Stop(ServerCallback callback);
// kSendMessage
void Send(int session_id, const std::string &message);
// kKill
void TerminateConnections();
int Port() const;
// Server socket lifecycle. There may be multiple sockets
void ServerSocketListening(ServerSocket *server_socket);
void ServerSocketClosed(ServerSocket *server_socket);
// Session connection lifecycle
bool HandleGetRequest(InspectorSocket *socket, const std::string &path);
bool SessionStarted(SocketSession *session, const std::string &id);
void SessionTerminated(SocketSession *session);
void MessageReceived(int session_id, const std::string &message) {
delegate_->MessageReceived(session_id, message);
}
int GenerateSessionId() {
return next_session_id_++;
}
private:
void SendListResponse(InspectorSocket *socket);
bool TargetExists(const std::string &id);
enum class ServerState { kNew,
kRunning,
kStopping,
kStopped };
uv_loop_t *loop_;
SocketServerDelegate *const delegate_;
const std::string host_;
int port_;
std::string path_;
std::vector<ServerSocket *> server_sockets_;
Closer *closer_;
std::map<int, SocketSession *> connected_sessions_;
int next_session_id_;
FILE *out_;
ServerState state_;
friend class Closer;
};
} // namespace inspector
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#endif // SRC_INSPECTOR_SOCKET_SERVER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
#pragma once
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include "v8.h"
#include <stddef.h>
#ifdef _WIN32
#ifndef BUILDING_NODE_EXTENSION
#define NODE_EXTERN __declspec(dllexport)
#else
#define NODE_EXTERN __declspec(dllimport)
#endif
#else
#define NODE_EXTERN /* nothing */
#endif
#include <assert.h>
#include <stdint.h>
#ifndef NODE_STRINGIFY
#define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)
#define NODE_STRINGIFY_HELPER(n) #n
#endif
// The arraysize(arr) macro returns the # of elements in an array arr.
// The expression is a compile-time constant, and therefore can be
// used in defining new arrays, for example. If you use arraysize on
// a pointer by mistake, you will get a compile-time error.
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
// This template function declaration is used in defining arraysize.
// Note that the function doesn't need an implementation, as we only
// use its type.
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#if !V8_CC_MSVC
// That gcc wants both of these prototypes seems mysterious. VC, for
// its part, can't decide which to use (another mystery). Matching of
// template overloads: the final frontier.
template <typename T, size_t N>
char (&ArraySizeHelper(const T (&array)[N]))[N];
#endif
#ifdef __POSIX__
void RegisterSignalHandler(int signal,
void (*handler)(int signal),
bool reset_handler = false);
#endif // __POSIX__
namespace node {
NODE_EXTERN v8::Local<v8::Value> ErrnoException(v8::Isolate *isolate,
int errorno,
const char *syscall = NULL,
const char *message = NULL,
const char *path = NULL);
NODE_EXTERN v8::Local<v8::Value> UVException(v8::Isolate *isolate,
int errorno,
const char *syscall = NULL,
const char *message = NULL,
const char *path = NULL);
NODE_EXTERN v8::Local<v8::Value> UVException(v8::Isolate *isolate,
int errorno,
const char *syscall,
const char *message,
const char *path,
const char *dest);
typedef double async_id;
struct async_context {
::node::async_id async_id;
::node::async_id trigger_async_id;
};
/* An API specific to emit before/after callbacks is unnecessary because
* MakeCallback will automatically call them for you.
*
* These methods may create handles on their own, so run them inside a
* HandleScope.
*
* `asyncId` and `triggerAsyncId` should correspond to the values returned by
* `EmitAsyncInit()` and `AsyncHooksGetTriggerAsyncId()`, respectively, when the
* invoking resource was created. If these values are unknown, 0 can be passed.
* */
v8::MaybeLocal<v8::Value> MakeCallback(v8::Isolate *isolate,
v8::Local<v8::Object> recv,
v8::Local<v8::Function> callback,
int argc,
v8::Local<v8::Value> *argv,
async_context asyncContext);
v8::MaybeLocal<v8::Value> MakeCallback(v8::Isolate *isolate,
v8::Local<v8::Object> recv,
const char *method,
int argc,
v8::Local<v8::Value> *argv,
async_context asyncContext);
v8::MaybeLocal<v8::Value> MakeCallback(v8::Isolate *isolate,
v8::Local<v8::Object> recv,
v8::Local<v8::String> symbol,
int argc,
v8::Local<v8::Value> *argv,
async_context asyncContext);
/*
* These methods need to be called in a HandleScope.
*
* It is preferred that you use the `MakeCallback` overloads taking
* `async_id` arguments.
*/
v8::Local<v8::Value> MakeCallback(
v8::Isolate *isolate,
v8::Local<v8::Object> recv,
const char *method,
int argc,
v8::Local<v8::Value> *argv);
v8::Local<v8::Value> MakeCallback(
v8::Isolate *isolate,
v8::Local<v8::Object> recv,
v8::Local<v8::String> symbol,
int argc,
v8::Local<v8::Value> *argv);
v8::Local<v8::Value> MakeCallback(
v8::Isolate *isolate,
v8::Local<v8::Object> recv,
v8::Local<v8::Function> callback,
int argc,
v8::Local<v8::Value> *argv);
class IsolateData;
class Environment;
NODE_EXTERN IsolateData *CreateIsolateData(v8::Isolate *isolate,
struct uv_loop_s *loop);
NODE_EXTERN void FreeIsolateData(IsolateData *isolate_data);
NODE_EXTERN Environment *CreateEnvironment(IsolateData *isolate_data,
v8::Local<v8::Context> context,
int argc,
const char *const *argv,
int exec_argc,
const char *const *exec_argv);
NODE_EXTERN void FreeEnvironment(Environment *env);
void SetupProcessObject(Environment *env,
int argc,
const char *const *argv,
int exec_argc,
const char *const *exec_argv);
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,141 @@
#include "node_debug_options.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
namespace node {
namespace {
const int default_inspector_port = 9229;
inline std::string remove_brackets(const std::string &host) {
if (!host.empty() && host.front() == '[' && host.back() == ']')
return host.substr(1, host.size() - 2);
else
return host;
}
int parse_and_validate_port(const std::string &port) {
char *endptr;
errno = 0;
const long result = strtol(port.c_str(), &endptr, 10); // NOLINT(runtime/int)
if (errno != 0 || *endptr != '\0' ||
(result != 0 && result < 1024) || result > 65535) {
SE_LOGE("Debug port must be 0 or in range 1024 to 65535.\n");
exit(12);
}
return static_cast<int>(result);
}
std::pair<std::string, int> split_host_port(const std::string &arg) {
// remove_brackets only works if no port is specified
// so if it has an effect only an IPv6 address was specified
std::string host = remove_brackets(arg);
if (host.length() < arg.length())
return {host, -1};
size_t colon = arg.rfind(':');
if (colon == std::string::npos) {
// Either a port number or a host name. Assume that
// if it's not all decimal digits, it's a host name.
for (char c : arg) {
if (c < '0' || c > '9') {
return {arg, -1};
}
}
return {"", parse_and_validate_port(arg)};
}
// host and port found
return std::make_pair(remove_brackets(arg.substr(0, colon)),
parse_and_validate_port(arg.substr(colon + 1)));
}
} // namespace
DebugOptions::DebugOptions() : inspector_enabled_(false),
deprecated_debug_(false),
break_first_line_(false),
host_name_("127.0.0.1"),
port_(-1) {}
bool DebugOptions::ParseOption(const char *argv0, const std::string &option) {
bool has_argument = false;
std::string option_name;
std::string argument;
auto pos = option.find("=");
if (pos == std::string::npos) {
option_name = option;
} else {
option_name = option.substr(0, pos);
argument = option.substr(pos + 1);
if (argument.length() > 0)
has_argument = true;
else
argument.clear();
}
// Note that --debug-port and --debug-brk in conjunction with --inspect
// work but are undocumented.
// --debug is no longer valid.
// Ref: https://github.com/nodejs/node/issues/12630
// Ref: https://github.com/nodejs/node/pull/12949
if (option_name == "--inspect") {
inspector_enabled_ = true;
} else if (option_name == "--debug") {
deprecated_debug_ = true;
} else if (option_name == "--inspect-brk") {
inspector_enabled_ = true;
break_first_line_ = true;
} else if (option_name == "--debug-brk") {
break_first_line_ = true;
deprecated_debug_ = true;
} else if (option_name == "--debug-port" ||
option_name == "--inspect-port") {
if (!has_argument) {
SE_LOGE("%s: %s requires an argument\n",
argv0, option.c_str());
exit(9);
}
} else {
return false;
}
#if !HAVE_INSPECTOR
if (inspector_enabled_) {
SE_LOGE("Inspector support is not available with this Node.js build\n");
}
inspector_enabled_ = false;
return false;
#endif
// argument can be specified for *any* option to specify host:port
if (has_argument) {
std::pair<std::string, int> host_port = split_host_port(argument);
if (!host_port.first.empty()) {
host_name_ = host_port.first;
}
if (host_port.second >= 0) {
port_ = host_port.second;
}
}
return true;
}
int DebugOptions::port() const {
int port = port_;
if (port < 0) {
port = default_inspector_port;
}
return port;
}
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

View File

@@ -0,0 +1,45 @@
#ifndef SRC_NODE_DEBUG_OPTIONS_H_
#define SRC_NODE_DEBUG_OPTIONS_H_
#include "../../config.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#include <string>
// Forward declaration to break recursive dependency chain with src/env.h.
namespace node {
class DebugOptions {
public:
DebugOptions();
bool ParseOption(const char *argv0, const std::string &option);
void set_inspector_enabled(bool enabled) { inspector_enabled_ = enabled; }
bool inspector_enabled() const { return inspector_enabled_; }
bool deprecated_invocation() const {
return deprecated_debug_ &&
inspector_enabled_ &&
break_first_line_;
}
bool invalid_invocation() const {
return deprecated_debug_ && !inspector_enabled_;
}
void set_wait_for_connect(bool wait) { break_first_line_ = wait; }
bool wait_for_connect() const { return break_first_line_; }
std::string host_name() const { return host_name_; }
void set_host_name(std::string host_name) { host_name_ = host_name; }
int port() const;
void set_port(int port) { port_ = port; }
private:
bool inspector_enabled_;
bool deprecated_debug_;
bool break_first_line_;
std::string host_name_;
int port_;
};
} // namespace node
#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
#endif // SRC_NODE_DEBUG_OPTIONS_H_

Some files were not shown because too many files have changed in this diff Show More