#include "node_util.h" #include "base_object-inl.h" #include "node_errors.h" #include "node_external_reference.h" #include "util-inl.h" namespace node { namespace util { using v8::ALL_PROPERTIES; using v8::Array; using v8::ArrayBufferView; using v8::BigInt; using v8::Boolean; using v8::Context; using v8::External; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::IndexFilter; using v8::Integer; using v8::Isolate; using v8::KeyCollectionMode; using v8::Local; using v8::Object; using v8::ObjectTemplate; using v8::ONLY_CONFIGURABLE; using v8::ONLY_ENUMERABLE; using v8::ONLY_WRITABLE; using v8::Promise; using v8::PropertyFilter; using v8::Proxy; using v8::SKIP_STRINGS; using v8::SKIP_SYMBOLS; using v8::String; using v8::Uint32; using v8::Value; // Used in ToUSVString(). constexpr char16_t kUnicodeReplacementCharacter = 0xFFFD; // If a UTF-16 character is a low/trailing surrogate. CHAR_TEST(16, IsUnicodeTrail, (ch & 0xFC00) == 0xDC00) // If a UTF-16 character is a surrogate. CHAR_TEST(16, IsUnicodeSurrogate, (ch & 0xF800) == 0xD800) // If a UTF-16 surrogate is a low/trailing one. CHAR_TEST(16, IsUnicodeSurrogateTrail, (ch & 0x400) != 0) static void GetOwnNonIndexProperties( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); CHECK(args[0]->IsObject()); CHECK(args[1]->IsUint32()); Local object = args[0].As(); Local properties; PropertyFilter filter = static_cast(args[1].As()->Value()); if (!object->GetPropertyNames( context, KeyCollectionMode::kOwnOnly, filter, IndexFilter::kSkipIndices) .ToLocal(&properties)) { return; } args.GetReturnValue().Set(properties); } static void GetConstructorName( const FunctionCallbackInfo& args) { CHECK(args[0]->IsObject()); Local object = args[0].As(); Local name = object->GetConstructorName(); args.GetReturnValue().Set(name); } static void GetExternalValue( const FunctionCallbackInfo& args) { CHECK(args[0]->IsExternal()); Isolate* isolate = args.GetIsolate(); Local external = args[0].As(); void* ptr = external->Value(); uint64_t value = reinterpret_cast(ptr); Local ret = BigInt::NewFromUnsigned(isolate, value); args.GetReturnValue().Set(ret); } static void GetPromiseDetails(const FunctionCallbackInfo& args) { // Return undefined if it's not a Promise. if (!args[0]->IsPromise()) return; auto isolate = args.GetIsolate(); Local promise = args[0].As(); int state = promise->State(); Local values[2] = { Integer::New(isolate, state) }; size_t number_of_values = 1; if (state != Promise::PromiseState::kPending) values[number_of_values++] = promise->Result(); Local ret = Array::New(isolate, values, number_of_values); args.GetReturnValue().Set(ret); } static void GetProxyDetails(const FunctionCallbackInfo& args) { // Return undefined if it's not a proxy. if (!args[0]->IsProxy()) return; Local proxy = args[0].As(); // TODO(BridgeAR): Remove the length check as soon as we prohibit access to // the util binding layer. It's accessed in the wild and `esm` would break in // case the check is removed. if (args.Length() == 1 || args[1]->IsTrue()) { Local ret[] = { proxy->GetTarget(), proxy->GetHandler() }; args.GetReturnValue().Set( Array::New(args.GetIsolate(), ret, arraysize(ret))); } else { Local ret = proxy->GetTarget(); args.GetReturnValue().Set(ret); } } static void IsArrayBufferDetached(const FunctionCallbackInfo& args) { if (args[0]->IsArrayBuffer()) { auto buffer = args[0].As(); args.GetReturnValue().Set(buffer->WasDetached()); return; } args.GetReturnValue().Set(false); } static void PreviewEntries(const FunctionCallbackInfo& args) { if (!args[0]->IsObject()) return; Environment* env = Environment::GetCurrent(args); bool is_key_value; Local entries; if (!args[0].As()->PreviewEntries(&is_key_value).ToLocal(&entries)) return; // Fast path for WeakMap and WeakSet. if (args.Length() == 1) return args.GetReturnValue().Set(entries); Local ret[] = { entries, Boolean::New(env->isolate(), is_key_value) }; return args.GetReturnValue().Set( Array::New(env->isolate(), ret, arraysize(ret))); } static void Sleep(const FunctionCallbackInfo& args) { CHECK(args[0]->IsUint32()); uint32_t msec = args[0].As()->Value(); uv_sleep(msec); } void ArrayBufferViewHasBuffer(const FunctionCallbackInfo& args) { CHECK(args[0]->IsArrayBufferView()); args.GetReturnValue().Set(args[0].As()->HasBuffer()); } WeakReference::WeakReference(Realm* realm, Local object, Local target) : WeakReference(realm, object, target, 0) {} WeakReference::WeakReference(Realm* realm, Local object, Local target, uint64_t reference_count) : SnapshotableObject(realm, object, type_int), reference_count_(reference_count) { MakeWeak(); if (!target.IsEmpty()) { target_.Reset(realm->isolate(), target); if (reference_count_ == 0) { target_.SetWeak(); } } } bool WeakReference::PrepareForSerialization(Local context, v8::SnapshotCreator* creator) { if (target_.IsEmpty()) { target_index_ = 0; return true; } // Users can still hold strong references to target in addition to the // reference that we manage here, and they could expect that the referenced // object remains the same as long as that external strong reference // is alive. Since we have no way to know if there is any other reference // keeping the target alive, the best we can do to maintain consistency is to // simply save a reference to the target in the snapshot (effectively making // it strong) during serialization, and restore it during deserialization. // If there's no known counted reference from our side, we'll make the // reference here weak upon deserialization so that it can be GC'ed if users // do not hold additional references to it. Local target = target_.Get(context->GetIsolate()); target_index_ = creator->AddData(context, target); DCHECK_NE(target_index_, 0); target_.Reset(); return true; } InternalFieldInfoBase* WeakReference::Serialize(int index) { DCHECK_EQ(index, BaseObject::kEmbedderType); InternalFieldInfo* info = InternalFieldInfoBase::New(type()); info->target = target_index_; info->reference_count = reference_count_; return info; } void WeakReference::Deserialize(Local context, Local holder, int index, InternalFieldInfoBase* info) { DCHECK_EQ(index, BaseObject::kEmbedderType); HandleScope scope(context->GetIsolate()); InternalFieldInfo* weak_info = reinterpret_cast(info); Local target; if (weak_info->target != 0) { target = context->GetDataFromSnapshotOnce(weak_info->target) .ToLocalChecked(); } new WeakReference( Realm::GetCurrent(context), holder, target, weak_info->reference_count); } void WeakReference::New(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); CHECK(args.IsConstructCall()); CHECK(args[0]->IsObject()); new WeakReference(realm, args.This(), args[0].As()); } void WeakReference::Get(const FunctionCallbackInfo& args) { WeakReference* weak_ref = Unwrap(args.Holder()); Isolate* isolate = args.GetIsolate(); if (!weak_ref->target_.IsEmpty()) args.GetReturnValue().Set(weak_ref->target_.Get(isolate)); } void WeakReference::GetRef(const FunctionCallbackInfo& args) { WeakReference* weak_ref = Unwrap(args.Holder()); Isolate* isolate = args.GetIsolate(); args.GetReturnValue().Set( v8::Number::New(isolate, weak_ref->reference_count_)); } void WeakReference::IncRef(const FunctionCallbackInfo& args) { WeakReference* weak_ref = Unwrap(args.Holder()); weak_ref->reference_count_++; if (weak_ref->target_.IsEmpty()) return; if (weak_ref->reference_count_ == 1) weak_ref->target_.ClearWeak(); } void WeakReference::DecRef(const FunctionCallbackInfo& args) { WeakReference* weak_ref = Unwrap(args.Holder()); CHECK_GE(weak_ref->reference_count_, 1); weak_ref->reference_count_--; if (weak_ref->target_.IsEmpty()) return; if (weak_ref->reference_count_ == 0) weak_ref->target_.SetWeak(); } static void GuessHandleType(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); int fd; if (!args[0]->Int32Value(env->context()).To(&fd)) return; CHECK_GE(fd, 0); uv_handle_type t = uv_guess_handle(fd); const char* type = nullptr; switch (t) { case UV_TCP: type = "TCP"; break; case UV_TTY: type = "TTY"; break; case UV_UDP: type = "UDP"; break; case UV_FILE: type = "FILE"; break; case UV_NAMED_PIPE: type = "PIPE"; break; case UV_UNKNOWN_HANDLE: type = "UNKNOWN"; break; default: ABORT(); } args.GetReturnValue().Set(OneByteString(env->isolate(), type)); } static void ToUSVString(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); TwoByteValue value(env->isolate(), args[0]); for (size_t i = 0; i < value.length(); i++) { char16_t c = value[i]; if (!IsUnicodeSurrogate(c)) { continue; } else if (IsUnicodeSurrogateTrail(c) || i == value.length() - 1) { value[i] = kUnicodeReplacementCharacter; } else { char16_t d = value[i + 1]; if (IsUnicodeTrail(d)) { i++; } else { value[i] = kUnicodeReplacementCharacter; } } } args.GetReturnValue().Set( String::NewFromTwoByte(env->isolate(), *value, v8::NewStringType::kNormal, value.length()).ToLocalChecked()); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetPromiseDetails); registry->Register(GetProxyDetails); registry->Register(IsArrayBufferDetached); registry->Register(PreviewEntries); registry->Register(GetOwnNonIndexProperties); registry->Register(GetConstructorName); registry->Register(GetExternalValue); registry->Register(Sleep); registry->Register(ArrayBufferViewHasBuffer); registry->Register(WeakReference::New); registry->Register(WeakReference::Get); registry->Register(WeakReference::GetRef); registry->Register(WeakReference::IncRef); registry->Register(WeakReference::DecRef); registry->Register(GuessHandleType); registry->Register(ToUSVString); } void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); { Local tmpl = ObjectTemplate::New(isolate); #define V(PropertyName, _) \ tmpl->Set(FIXED_ONE_BYTE_STRING(env->isolate(), #PropertyName), \ env->PropertyName()); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) #undef V target ->Set(context, FIXED_ONE_BYTE_STRING(isolate, "privateSymbols"), tmpl->NewInstance(context).ToLocalChecked()) .Check(); } { Local constants = Object::New(isolate); #define V(name) \ constants \ ->Set(context, \ FIXED_ONE_BYTE_STRING(isolate, #name), \ Integer::New(isolate, Promise::PromiseState::name)) \ .Check(); V(kPending); V(kFulfilled); V(kRejected); #undef V #define V(name) \ constants \ ->Set(context, \ FIXED_ONE_BYTE_STRING(isolate, #name), \ Integer::New(isolate, Environment::ExitInfoField::name)) \ .Check(); V(kExiting); V(kExitCode); V(kHasExitCode); #undef V #define V(name) \ constants \ ->Set(context, \ FIXED_ONE_BYTE_STRING(isolate, #name), \ Integer::New(isolate, PropertyFilter::name)) \ .Check(); V(ALL_PROPERTIES); V(ONLY_WRITABLE); V(ONLY_ENUMERABLE); V(ONLY_CONFIGURABLE); V(SKIP_STRINGS); V(SKIP_SYMBOLS); #undef V target->Set(context, env->constants_string(), constants).Check(); } SetMethodNoSideEffect( context, target, "getPromiseDetails", GetPromiseDetails); SetMethodNoSideEffect(context, target, "getProxyDetails", GetProxyDetails); SetMethodNoSideEffect( context, target, "isArrayBufferDetached", IsArrayBufferDetached); SetMethodNoSideEffect(context, target, "previewEntries", PreviewEntries); SetMethodNoSideEffect( context, target, "getOwnNonIndexProperties", GetOwnNonIndexProperties); SetMethodNoSideEffect( context, target, "getConstructorName", GetConstructorName); SetMethodNoSideEffect(context, target, "getExternalValue", GetExternalValue); SetMethod(context, target, "sleep", Sleep); SetMethod( context, target, "arrayBufferViewHasBuffer", ArrayBufferViewHasBuffer); Local should_abort_on_uncaught_toggle = FIXED_ONE_BYTE_STRING(env->isolate(), "shouldAbortOnUncaughtToggle"); CHECK(target ->Set(context, should_abort_on_uncaught_toggle, env->should_abort_on_uncaught_toggle().GetJSArray()) .FromJust()); Local weak_ref = NewFunctionTemplate(isolate, WeakReference::New); weak_ref->InstanceTemplate()->SetInternalFieldCount( WeakReference::kInternalFieldCount); weak_ref->Inherit(BaseObject::GetConstructorTemplate(env)); SetProtoMethod(isolate, weak_ref, "get", WeakReference::Get); SetProtoMethod(isolate, weak_ref, "getRef", WeakReference::GetRef); SetProtoMethod(isolate, weak_ref, "incRef", WeakReference::IncRef); SetProtoMethod(isolate, weak_ref, "decRef", WeakReference::DecRef); SetConstructorFunction(context, target, "WeakReference", weak_ref); SetMethod(context, target, "guessHandleType", GuessHandleType); SetMethodNoSideEffect(context, target, "toUSVString", ToUSVString); } } // namespace util } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(util, node::util::Initialize) NODE_BINDING_EXTERNAL_REFERENCE(util, node::util::RegisterExternalReferences)