/* * 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 "V8Engine.h" #include "V8Helper.h" #include "V8Helper.hpp" namespace script { #define REF_IMPL_BASIC_FUNC(ValueType) \ Local::Local(const Local& copy) : val_(copy.val_) {} \ Local::Local(Local&& from) noexcept : val_(from.val_) { \ from.val_.Clear(); \ } \ Local::~Local() { \ assert(val_.IsEmpty() || EngineScope::currentEngine() != nullptr); \ } \ Local& Local::operator=(const Local& from) { \ if (&from != this) { \ val_ = from.val_; \ } \ return *this; \ } \ Local& Local::operator=(Local&& move) noexcept { \ if (&move != this) { \ val_ = move.val_; \ move.val_.Clear(); \ } \ return *this; \ } \ void Local::swap(Local& rhs) noexcept { \ if (&rhs != this) { \ std::swap(val_, rhs.val_); \ } \ } // if Local is created with null ref, it's an error #define REF_IMPL_BASIC_NOT_VALUE(ValueType) \ Local::Local(InternalLocalRef v8Local) : val_(v8Local) { \ if (val_.IsEmpty() || val_->IsNullOrUndefined()) throw Exception("null reference"); \ } \ Local Local::describe() const { return asValue().describe(); } \ std::string Local::describeUtf8() const { return asValue().describeUtf8(); } \ bool Local::operator==(const script::Local& other) const { \ if (other.isNull()) return false; \ return val_->StrictEquals(other.val_); \ } #define REF_IMPL_TO_VALUE(ValueType) \ Local Local::asValue() const { return Local(val_.As()); } REF_IMPL_BASIC_FUNC(Value) REF_IMPL_BASIC_FUNC(Object) REF_IMPL_BASIC_NOT_VALUE(Object) REF_IMPL_TO_VALUE(Object) REF_IMPL_BASIC_FUNC(String) REF_IMPL_BASIC_NOT_VALUE(String) REF_IMPL_TO_VALUE(String) REF_IMPL_BASIC_FUNC(Number) REF_IMPL_BASIC_NOT_VALUE(Number) REF_IMPL_TO_VALUE(Number) REF_IMPL_BASIC_FUNC(Boolean) REF_IMPL_BASIC_NOT_VALUE(Boolean) REF_IMPL_TO_VALUE(Boolean) REF_IMPL_BASIC_FUNC(Function) REF_IMPL_BASIC_NOT_VALUE(Function) REF_IMPL_TO_VALUE(Function) REF_IMPL_BASIC_FUNC(Array) REF_IMPL_BASIC_NOT_VALUE(Array) REF_IMPL_TO_VALUE(Array) REF_IMPL_BASIC_FUNC(ByteBuffer) REF_IMPL_BASIC_NOT_VALUE(ByteBuffer) REF_IMPL_TO_VALUE(ByteBuffer) REF_IMPL_BASIC_FUNC(Unsupported) REF_IMPL_BASIC_NOT_VALUE(Unsupported) REF_IMPL_TO_VALUE(Unsupported) // ==== value ==== Local::Local() noexcept : val_() {} Local::Local(InternalLocalRef v8Local) : val_(v8Local) {} bool Local::isNull() const { return val_.IsEmpty() || val_->IsNullOrUndefined(); } void Local::reset() { val_.Clear(); } ValueKind Local::getKind() const { if (isNull()) { return ValueKind::kNull; } else if (isString()) { return ValueKind::kString; } else if (isNumber()) { return ValueKind::kNumber; } else if (isBoolean()) { return ValueKind::kBoolean; } else if (isFunction()) { return ValueKind::kFunction; } else if (isArray()) { return ValueKind::kArray; } else if (isByteBuffer()) { return ValueKind::kByteBuffer; } else if (isObject()) { return ValueKind::kObject; } else { return ValueKind::kUnsupported; } } bool Local::operator==(const script::Local& other) const { if (isNull()) return other.isNull(); return val_->StrictEquals(other.val_); } bool Local::isObject() const { return !isNull() && val_->IsObject(); } Local Local::asObject() const { if (isObject()) return Local(val_.As()); throw Exception(u8"can't cast value as Object"); } bool Local::isArray() const { return !isNull() && val_->IsArray(); } Local Local::asArray() const { if (isArray()) return Local(val_.As()); throw Exception(u8"can't cast value as Array"); } bool Local::isByteBuffer() const { return !isNull() && (val_->IsArrayBuffer() || val_->IsArrayBufferView()); } Local Local::asByteBuffer() const { if (isByteBuffer()) return Local(val_); throw Exception(u8"can't cast value as ByteBuffer"); } bool Local::isString() const { return !isNull() && val_->IsString(); } Local Local::asString() const { if (isString()) return Local(val_.As()); throw Exception(u8"can't cast value as String"); } bool Local::isNumber() const { return !isNull() && val_->IsNumber(); } Local Local::asNumber() const { if (isNumber()) return Local(val_.As()); throw Exception(u8"can't cast value as Number"); } bool Local::isBoolean() const { return !isNull() && val_->IsBoolean(); } Local Local::asBoolean() const { if (isBoolean()) return Local(val_.As()); throw Exception(u8"can't cast value as Boolean"); } bool Local::isFunction() const { return !isNull() && val_->IsFunction(); } Local Local::asFunction() const { if (isFunction()) return Local(val_.As()); throw Exception(u8"can't cast value as Function"); } bool Local::isUnsupported() const { return !isNull() && !isObject() && !isString() && !isNumber() && !isBoolean() && !isFunction(); } Local Local::asUnsupported() const { if (isUnsupported()) return Local(val_.As()); throw Exception(u8"can't cast value as Unsupported"); } Local Local::describe() const { if (isNull()) return String::newString(u8"null"); auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto maybe = val_->ToString(context); return Local(maybe.ToLocalChecked()); } Local Local::get(const script::Local& key) const { auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto v8Key = v8_backend::V8Engine::toV8(isolate, key); auto maybe = val_->Get(context, v8Key); v8_backend::checkException(tryCatch); return v8_backend::V8Engine::make>(maybe.ToLocalChecked()); } void Local::set(const script::Local& key, const script::Local& value) const { auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto v8Key = v8_backend::V8Engine::toV8(isolate, key); auto v8Value = v8_backend::V8Engine::toV8(isolate, value); auto ret = val_->Set(context, v8Key, v8Value); (void)ret; v8_backend::checkException(tryCatch); } void Local::remove(const Local& key) const { auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto success = val_->Delete(context, v8_backend::V8Engine::toV8(isolate, key)); (void)success; v8_backend::checkException(tryCatch); } bool Local::has(const Local& key) const { auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto ret = val_->Has(context, v8_backend::V8Engine::toV8(isolate, key)); v8_backend::checkException(tryCatch); return ret.ToChecked(); } bool Local::instanceOf(const Local& type) const { if (!type.isObject()) { return false; } auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto ret = val_->InstanceOf(context, v8_backend::V8Engine::toV8(isolate, type.asObject())); v8_backend::checkException(tryCatch); return ret.ToChecked(); } std::vector> Local::getKeys() const { auto isolate = v8_backend::currentEngineIsolateChecked(); auto context = v8_backend::currentEngineContextChecked(); v8::TryCatch tryCatch(isolate); auto maybeArray = val_->GetOwnPropertyNames(context); v8_backend::checkException(tryCatch); auto array = maybeArray.ToLocalChecked(); std::vector> ret; ret.reserve(array->Length()); for (uint32_t i = 0; i < array->Length(); ++i) { v8::EscapableHandleScope scope(isolate); auto maybeValue = array->Get(context, i); v8_backend::checkException(tryCatch); auto value = maybeValue.ToLocalChecked(); if (value->IsString()) { ret.push_back( v8_backend::V8Engine::make>(scope.Escape(value.As()))); } } return ret; } int32_t Local::toInt32() const { return static_cast(val_->Value()); } int64_t Local::toInt64() const { return static_cast(val_->Value()); } float Local::toFloat() const { return static_cast(toDouble()); } double Local::toDouble() const { return val_->Value(); } bool Local::value() const { return val_->Value(); } Local Local::callImpl(const script::Local& thiz, size_t size, const Local* args) const { auto [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); return v8_backend::toV8ValueArray>( isolate, size, args, [this, &thiz, size, iso = isolate, &ctx = context](auto* v8Args) { v8::TryCatch tryCatch(iso); const v8::Local& receiver = v8_backend::V8Engine::toV8(iso, thiz); auto ret = val_->Call(ctx, receiver, static_cast(size), v8Args); v8_backend::checkException(tryCatch); return Local{ret.ToLocalChecked()}; }); } size_t Local::size() const { return val_->Length(); } Local Local::get(size_t index) const { auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto ret = val_->Get(context, static_cast(index)); v8_backend::checkException(tryCatch); return Local(ret.ToLocalChecked()); } void Local::set(size_t index, const script::Local& value) const { auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto ret = val_->Set(context, static_cast(index), v8_backend::V8Engine::toV8(isolate, value)); (void)ret; v8_backend::checkException(tryCatch); } void Local::add(const script::Local& value) const { set(size(), value); } void Local::clear() const { auto&& [isolate, context] = v8_backend::currentEngineIsolateAndContextChecked(); v8::TryCatch tryCatch(isolate); auto ret = val_.As()->Set( context, v8_backend::V8Engine::toV8(isolate, String::newString(u8"length")), v8::Number::New(isolate, 0)); (void)ret; v8_backend::checkException(tryCatch); } ByteBuffer::Type Local::getType() const { if (val_->IsArrayBuffer()) { return ByteBuffer::Type::kUnspecified; } else if (val_->IsArrayBufferView()) { if (val_->IsDataView()) { return ByteBuffer::Type::kUnspecified; } else if (val_->IsInt8Array()) { return ByteBuffer::Type::kInt8; } else if (val_->IsUint8Array() || val_->IsUint8ClampedArray()) { return ByteBuffer::Type::kUint8; } else if (val_->IsInt16Array()) { return ByteBuffer::Type::kInt16; } else if (val_->IsUint16Array()) { return ByteBuffer::Type::kUint16; } else if (val_->IsInt32Array()) { return ByteBuffer::Type::kInt32; } else if (val_->IsUint32Array()) { return ByteBuffer::Type::kUint32; } else if (val_->IsBigInt64Array()) { return ByteBuffer::Type::kInt64; } else if (val_->IsBigUint64Array()) { return ByteBuffer::Type::kUint64; } else if (val_->IsFloat32Array()) { return ByteBuffer::Type::KFloat32; } else if (val_->IsFloat64Array()) { return ByteBuffer::Type::kFloat64; } } throw Exception(u8"unsupported ArrayBufferView type"); } size_t Local::byteLength() const { if (val_->IsArrayBuffer()) { return val_.As()->ByteLength(); } else if (val_->IsArrayBufferView()) { auto view = val_.As(); return view->ByteLength(); } throw Exception(u8"unsupported ArrayBufferView type"); } #if V8_MAJOR_VERSION >= 8 // v8 8.0 introduced new api for this // https://docs.google.com/document/d/1sTc_jRL87Fu175Holm5SV0kajkseGl2r8ifGY76G35k/edit void* Local::getRawBytes() const { if (val_->IsArrayBuffer()) { return val_.As()->GetBackingStore()->Data(); } else if (val_->IsArrayBufferView()) { auto view = val_.As(); auto offset = view->ByteOffset(); auto data = view->Buffer()->GetBackingStore()->Data(); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) return static_cast(data) + offset; } throw Exception(u8"unsupported ArrayBufferView type"); } std::shared_ptr Local::getRawBytesShared() const { if (val_->IsArrayBuffer()) { auto store = val_.As()->GetBackingStore(); return {std::move(store), store->Data()}; // NOLINT(performance-move-const-arg) } else if (val_->IsArrayBufferView()) { auto view = val_.As(); auto offset = view->ByteOffset(); auto store = view->Buffer()->GetBackingStore(); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) auto data = static_cast(store->Data()) + offset; return {std::move(store), data}; // NOLINT(performance-move-const-arg) } throw Exception(u8"unsupported ArrayBufferView type"); } #else void* Local::getRawBytes() const { if (val_->IsArrayBuffer()) { return val_.As()->GetContents().Data(); } else if (val_->IsArrayBufferView()) { auto view = val_.As(); auto offset = view->ByteOffset(); auto data = view->Buffer()->GetContents().Data(); return static_cast(data) + offset; } throw Exception(u8"unsupported ArrayBufferView type"); } std::shared_ptr Local::getRawBytesShared() const { void* ptr; if (val_->IsArrayBuffer()) { ptr = val_.As()->GetContents().Data(); } else if (val_->IsArrayBufferView()) { auto view = val_.As(); auto offset = view->ByteOffset(); auto data = view->Buffer()->GetContents().Data(); ptr = static_cast(data) + offset; } else { throw Exception(u8"unsupported ArrayBufferView type"); } auto engine = v8_backend::currentEngine(); auto id = engine->keepReference(*this); return std::shared_ptr(ptr, [engine, id](void*) { engine->removeKeptReference(id); }); } #endif bool Local::isShared() const { return true; } void Local::commit() const {} void Local::sync() const {} } // namespace script