/* * Tencent is pleased to support the open source community by making ScriptX available. * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "../../src/Native.hpp" #include "../../src/Reference.h" #include "../../src/Utils.h" #include "../../src/Value.h" #include "JscEngine.hpp" #include "JscHelper.hpp" #include "JscReference.hpp" #include "trait/TraitReference.h" namespace script { namespace jsc_backend { StringLocalRef::StringLocalRef(JSStringRef rawStringRef) : rawStringRef_(rawStringRef) { assert(rawStringRef); } StringLocalRef::StringLocalRef(JSValueRef stringRef) : stringRef_(stringRef) { assert(stringRef); } JSValueRef StringLocalRef::getValue(JSContextRef context) const { if (!stringRef_) { stringRef_ = JSValueMakeString(context, rawStringRef_.get()); } return stringRef_; } JSStringRef StringLocalRef::getString(JSContextRef context) const { if (!rawStringRef_) { JSValueRef jscException = nullptr; rawStringRef_ = StringLocalRef::SharedStringRef{JSValueToStringCopy(context, stringRef_, &jscException)}; JscEngine::checkException(jscException); } return rawStringRef_.get(); } StringLocalRef::operator JSValueRef() const { return getValue(jsc_backend::currentEngineContextChecked()); } StringLocalRef& StringLocalRef::operator=(nullptr_t) { stringRef_ = nullptr; rawStringRef_ = nullptr; return *this; } StringLocalRef::SharedStringRef StringLocalRef::getSharedStringRef(JSContextRef context) const { getString(context); return rawStringRef_; } bool isNullOrUndefined(JSValueRef value) { if (value == nullptr) return true; auto ctx = jsc_backend::currentEngineContextChecked(); auto type = JSValueGetType(ctx, value); return type == JSType::kJSTypeNull || type == JSType::kJSTypeUndefined; } JSObjectRef valueToObj(JSGlobalContextRef context, JSValueRef value) { JSValueRef jscException = nullptr; auto ret = JSValueToObject(context, value, &jscException); jsc_backend::JscEngine::checkException(jscException); return ret; } void valueConstructorCheck(JSValueRef value) { SCRIPTX_UNUSED(value); #ifndef NDEBUG if (jsc_backend::isNullOrUndefined(value)) throw Exception("null reference"); #endif } void valueConstructorCheck(const StringLocalRef&) {} } // namespace jsc_backend // val is created from JSC, so its ref-count is 1 // the create Local directly owns it // Local::Local(InternalLocalRef val) #define REF_IMPL_BASIC_FUNC(ValueType) \ Local::Local(const Local& copy) : val_(copy.val_) {} \ Local::Local(Local&& move) noexcept : val_(std::move(move.val_)) { \ move.val_ = nullptr; \ } \ Local::~Local() { val_ = nullptr; } \ Local& Local::operator=(const Local& from) { \ Local(from).swap(*this); \ return *this; \ } \ Local& Local::operator=(Local&& move) noexcept { \ Local(std::move(move)).swap(*this); \ return *this; \ } \ void Local::swap(Local& rhs) noexcept { std::swap(val_, rhs.val_); } #define REF_IMPL_BASIC_EQUALS(ValueType) \ bool Local::operator==(const script::Local& other) const { \ return asValue() == other; \ } #define REF_IMPL_BASIC_NOT_VALUE(ValueType) \ Local::Local(InternalLocalRef val) : val_(std::move(val)) { \ jsc_backend::valueConstructorCheck(val_); \ } \ Local Local::describe() const { return asValue().describe(); } \ std::string Local::describeUtf8() const { return asValue().describeUtf8(); } #define REF_IMPL_TO_VALUE(ValueType) \ Local Local::asValue() const { return Local(val_); } REF_IMPL_BASIC_FUNC(Value) REF_IMPL_BASIC_FUNC(Object) REF_IMPL_BASIC_NOT_VALUE(Object) REF_IMPL_BASIC_EQUALS(Object) REF_IMPL_TO_VALUE(Object) REF_IMPL_BASIC_FUNC(String) REF_IMPL_BASIC_NOT_VALUE(String) REF_IMPL_BASIC_EQUALS(String) REF_IMPL_BASIC_FUNC(Number) REF_IMPL_BASIC_NOT_VALUE(Number) REF_IMPL_BASIC_EQUALS(Number) REF_IMPL_TO_VALUE(Number) REF_IMPL_BASIC_FUNC(Boolean) REF_IMPL_BASIC_NOT_VALUE(Boolean) REF_IMPL_BASIC_EQUALS(Boolean) REF_IMPL_TO_VALUE(Boolean) REF_IMPL_BASIC_FUNC(Function) REF_IMPL_BASIC_NOT_VALUE(Function) REF_IMPL_BASIC_EQUALS(Function) REF_IMPL_TO_VALUE(Function) REF_IMPL_BASIC_FUNC(Array) REF_IMPL_BASIC_NOT_VALUE(Array) REF_IMPL_BASIC_EQUALS(Array) REF_IMPL_TO_VALUE(Array) REF_IMPL_BASIC_FUNC(ByteBuffer) REF_IMPL_BASIC_NOT_VALUE(ByteBuffer) REF_IMPL_BASIC_EQUALS(ByteBuffer) REF_IMPL_TO_VALUE(ByteBuffer) REF_IMPL_BASIC_FUNC(Unsupported) REF_IMPL_BASIC_NOT_VALUE(Unsupported) REF_IMPL_BASIC_EQUALS(Unsupported) REF_IMPL_TO_VALUE(Unsupported) // ==== value ==== Local::Local() noexcept : val_() {} Local::Local(InternalLocalRef v8Local) : val_(v8Local) {} bool Local::isNull() const { return jsc_backend::isNullOrUndefined(val_); } void Local::reset() { val_ = nullptr; } ValueKind Local::getKind() const { if (val_ == nullptr) { return ValueKind::kNull; } auto ctx = jsc_backend::currentEngineContextChecked(); auto type = JSValueGetType(ctx, val_); if (type == JSType::kJSTypeUndefined || type == JSType::kJSTypeNull) { return ValueKind::kNull; } else if (type == JSType::kJSTypeString) { return ValueKind::kString; } else if (type == JSType::kJSTypeNumber) { return ValueKind::kNumber; } else if (type == JSType::kJSTypeBoolean) { return ValueKind::kBoolean; } else if (JSValueIsArray(ctx, val_)) { return ValueKind::kArray; } else if (JSValueIsObject(ctx, val_)) { auto obj = jsc_backend::valueToObj(ctx, val_); if (JSObjectIsFunction(ctx, obj)) { return ValueKind::kFunction; } if (isByteBuffer()) { return ValueKind::kByteBuffer; } return ValueKind::kObject; } else { return ValueKind::kUnsupported; } } bool Local::isString() const { if (val_ == nullptr) return false; return JSValueGetType(jsc_backend::currentEngineContextChecked(), val_) == JSType::kJSTypeString; } bool Local::isNumber() const { if (val_ == nullptr) return false; return JSValueGetType(jsc_backend::currentEngineContextChecked(), val_) == JSType::kJSTypeNumber; } bool Local::isBoolean() const { if (val_ == nullptr) return false; return JSValueGetType(jsc_backend::currentEngineContextChecked(), val_) == JSType::kJSTypeBoolean; } bool Local::isFunction() const { if (val_ == nullptr) return false; auto ctx = jsc_backend::currentEngineContextChecked(); return JSValueIsObject(ctx, val_) && JSObjectIsFunction(ctx, const_cast(val_)); } bool Local::isArray() const { if (val_ == nullptr) return false; return JSValueIsArray(jsc_backend::currentEngineContextChecked(), val_); } bool Local::isByteBuffer() const { if (val_ == nullptr) return false; if (jsc_backend::JscEngine::hasByteBufferAPI_) { JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); bool ret = JSValueGetTypedArrayType(context, val_, &jscException) != kJSTypedArrayTypeNone; jsc_backend::JscEngine::checkException(jscException); if (ret) return true; // check is DataView if (JSValueIsObject(context, val_)) { auto ptr = JSObjectGetTypedArrayBytesPtr(context, jsc_backend::valueToObj(context, val_), &jscException); jsc_backend::JscEngine::checkException(jscException); return ptr != nullptr; } } else { return jsc_backend::currentEngine()->isByteBuffer_.get().call({}, *this).asBoolean().value(); } return false; } bool Local::isObject() const { return val_ != nullptr && JSValueIsObject(jsc_backend::currentEngineContextChecked(), val_); } bool Local::isUnsupported() const { return getKind() == ValueKind::kUnsupported; } Local Local::asString() const { if (isString()) return Local{jsc_backend::StringLocalRef(val_)}; throw Exception("can't cast value as String"); } Local Local::asNumber() const { if (isNumber()) return Local{val_}; throw Exception("can't cast value as Number"); } Local Local::asBoolean() const { if (isBoolean()) return Local{val_}; throw Exception("can't cast value as Boolean"); } Local Local::asFunction() const { if (isFunction()) { return Local{ jsc_backend::valueToObj(jsc_backend::currentEngineContextChecked(), val_)}; } throw Exception("can't cast value as Function"); } Local Local::asArray() const { if (isArray()) { return Local{jsc_backend::valueToObj(jsc_backend::currentEngineContextChecked(), val_)}; } throw Exception("can't cast value as Array"); } Local Local::asByteBuffer() const { if (isByteBuffer()) { return Local{ jsc_backend::valueToObj(jsc_backend::currentEngineContextChecked(), val_)}; } throw Exception("can't cast value as ByteBuffer"); } Local Local::asObject() const { if (isObject()) { return Local{jsc_backend::valueToObj(jsc_backend::currentEngineContextChecked(), val_)}; } throw Exception("can't cast value as Object"); } Local Local::asUnsupported() const { if (isUnsupported()) return Local{val_}; throw Exception("can't cast value as Unsupported"); } bool Local::operator==(const script::Local& other) const { if (isNull()) return other.isNull(); return JSValueIsStrictEqual(jsc_backend::currentEngineContextChecked(), const_cast(val_), const_cast(other.val_)); } Local Local::describe() const { if (isNull()) return String::newString("null"); if (isString()) { return asString(); } else { JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); auto str = JSValueToStringCopy(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); return Local(jsc_backend::StringLocalRef(str)); } } Local Local::asValue() const { return Local(val_.getValue(jsc_backend::currentEngineContextChecked())); } Local Local::get(const script::Local& key) const { JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); Local ret(JSObjectGetProperty(context, jsc_backend::JscEngine::toJsc(context, *this), key.val_.getString(context), &jscException)); jsc_backend::JscEngine::checkException(jscException); return ret; } void Local::set(const script::Local& key, const script::Local& value) const { JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); JSObjectSetProperty(context, jsc_backend::JscEngine::toJsc(context, *this), key.val_.getString(context), jsc_backend::JscEngine::toJsc(context, value), kJSPropertyAttributeNone, &jscException); jsc_backend::JscEngine::checkException(jscException); } void Local::remove(const Local& key) const { JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); JSObjectDeleteProperty(context, jsc_backend::JscEngine::toJsc(context, *this), key.val_.getString(context), &jscException); jsc_backend::JscEngine::checkException(jscException); } bool Local::has(const Local& key) const { auto context = jsc_backend::currentEngineContextChecked(); return JSObjectHasProperty(context, jsc_backend::JscEngine::toJsc(context, *this), key.val_.getString(context)); } bool Local::instanceOf(const Local& type) const { if (!type.isObject()) { return false; } JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); auto ret = JSValueIsInstanceOfConstructor(context, jsc_backend::JscEngine::toJsc(context, *this), jsc_backend::JscEngine::toJsc(context, type.asObject()), &jscException); jsc_backend::JscEngine::checkException(jscException); return ret; } std::vector> Local::getKeys() const { auto context = jsc_backend::currentEngineContextChecked(); auto array = JSObjectCopyPropertyNames(context, val_); auto count = JSPropertyNameArrayGetCount(array); std::vector> ret; ret.reserve(count); for (size_t i = 0; i < count; ++i) { auto name = JSPropertyNameArrayGetNameAtIndex(array, i); JSStringRetain(name); ret.push_back(Local(jsc_backend::StringLocalRef(name))); } JSPropertyNameArrayRelease(array); return ret; } float Local::toFloat() const { return static_cast(toDouble()); } double Local::toDouble() const { JSValueRef jscException = nullptr; double ret = JSValueToNumber(jsc_backend::currentEngineContextChecked(), val_, &jscException); jsc_backend::JscEngine::checkException(jscException); return ret; } int32_t Local::toInt32() const { return static_cast(toDouble()); } int64_t Local::toInt64() const { return static_cast(toDouble()); } bool Local::value() const { return JSValueToBoolean(jsc_backend::currentEngineContextChecked(), val_); } Local Local::callImpl(const script::Local& thiz, size_t size, const Local* args) const { JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); return jsc_backend::toJscValues>( context, size, args, [this, context, &thiz, size, &jscException](JSValueRef* array) { Local ret(JSObjectCallAsFunction( context, val_, thiz.isObject() ? jsc_backend::JscEngine::toJsc(context, thiz.asObject()) : nullptr, size, array, &jscException)); jsc_backend::JscEngine::checkException(jscException); return ret; }); } size_t Local::size() const { auto len = Local(val_).get("length"); if (!len.isNull()) return len.asNumber().toInt32(); return 0; } Local Local::get(size_t index) const { JSValueRef jscException = nullptr; Local ret(JSObjectGetPropertyAtIndex(jsc_backend::currentEngineContextChecked(), val_, static_cast(index), &jscException)); jsc_backend::JscEngine::checkException(jscException); return ret; } void Local::set(size_t index, const script::Local& value) const { JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); JSObjectSetPropertyAtIndex(context, val_, static_cast(index), jsc_backend::JscEngine::toJsc(context, value), &jscException); jsc_backend::JscEngine::checkException(jscException); } void Local::add(const script::Local& value) const { set(size(), value); } void Local::clear() const { JSValueRef jscException = nullptr; Local(val_).set("length", Number::newNumber(0)); jsc_backend::JscEngine::checkException(jscException); } namespace { ByteBuffer::Type mapType(JSTypedArrayType type) { switch (type) { case kJSTypedArrayTypeInt8Array: return ByteBuffer::Type::kInt8; case kJSTypedArrayTypeUint8Array: case kJSTypedArrayTypeUint8ClampedArray: return ByteBuffer::Type::kUint8; case kJSTypedArrayTypeInt16Array: return ByteBuffer::Type::kInt16; case kJSTypedArrayTypeUint16Array: return ByteBuffer::Type::kUint16; case kJSTypedArrayTypeInt32Array: return ByteBuffer::Type::kInt32; case kJSTypedArrayTypeUint32Array: return ByteBuffer::Type::kUint32; case kJSTypedArrayTypeFloat32Array: return ByteBuffer::Type::KFloat32; case kJSTypedArrayTypeFloat64Array: return ByteBuffer::Type::kFloat64; case kJSTypedArrayTypeArrayBuffer: default: return ByteBuffer::Type::kUnspecified; } } } // namespace static void assertLowLevelApiUsageOfByteBuffer(bool has) { if (!has) { throw std::runtime_error( "Calling script::ByteBuffer related api on iOS-9/macOS-10.11 or lower is not supported for " "now"); } } ByteBuffer::Type Local::getType() const { assertLowLevelApiUsageOfByteBuffer(jsc_backend::JscEngine::hasByteBufferAPI_); JSValueRef jscException = nullptr; auto type = JSValueGetTypedArrayType(jsc_backend::currentEngineContextChecked(), val_, &jscException); jsc_backend::JscEngine::checkException(jscException); return mapType(type); } size_t Local::byteLength() const { assertLowLevelApiUsageOfByteBuffer(jsc_backend::JscEngine::hasByteBufferAPI_); JSValueRef jscException = nullptr; auto context = jsc_backend::currentEngineContextChecked(); auto type = JSValueGetTypedArrayType(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); if (type == kJSTypedArrayTypeArrayBuffer) { auto len = JSObjectGetArrayBufferByteLength(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); return len; } else { auto len = JSObjectGetTypedArrayByteLength(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); return len; } } void* Local::getRawBytes() const { assertLowLevelApiUsageOfByteBuffer(jsc_backend::JscEngine::hasByteBufferAPI_); auto context = jsc_backend::currentEngineContextChecked(); JSValueRef jscException = nullptr; auto type = JSValueGetTypedArrayType(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); if (type == kJSTypedArrayTypeArrayBuffer) { auto ret = JSObjectGetArrayBufferBytesPtr(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); return ret; } else { auto ret = JSObjectGetTypedArrayBytesPtr(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); auto offset = JSObjectGetTypedArrayByteOffset(context, val_, &jscException); jsc_backend::JscEngine::checkException(jscException); // NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic) return static_cast(ret) + offset; } } std::shared_ptr Local::getRawBytesShared() const { assertLowLevelApiUsageOfByteBuffer(jsc_backend::JscEngine::hasByteBufferAPI_); auto ptr = getRawBytes(); auto engine = jsc_backend::currentEngine(); auto id = engine->keepReference(*this); return std::shared_ptr(ptr, [engine, id](void*) { engine->removeKeptReference(id); }); } bool Local::isShared() const { return true; } void Local::commit() const {} void Local::sync() const {} } // namespace script