#include "base_object-inl.h" #include "node_dotenv.h" #include "node_errors.h" #include "node_external_reference.h" #include "util-inl.h" #include "v8-fast-api-calls.h" namespace node { namespace util { using v8::ALL_PROPERTIES; using v8::Array; using v8::ArrayBufferView; using v8::BigInt; using v8::Boolean; using v8::CFunction; using v8::Context; using v8::External; using v8::FunctionCallbackInfo; using v8::IndexFilter; using v8::Integer; using v8::Isolate; using v8::KeyCollectionMode; using v8::Local; using v8::LocalVector; using v8::Name; 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::StackFrame; using v8::StackTrace; using v8::String; using v8::Uint32; using v8::Value; // 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 GetCallerLocation(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local trace = StackTrace::CurrentStackTrace(isolate, 2); // This function is frame zero. The caller is frame one. If there aren't two // stack frames, return undefined. if (trace->GetFrameCount() != 2) { return; } Local frame = trace->GetFrame(isolate, 1); Local file = frame->GetScriptNameOrSourceURL(); if (file.IsEmpty()) { return; } Local ret[] = {Integer::New(isolate, frame->GetLineNumber()), Integer::New(isolate, frame->GetColumn()), file}; args.GetReturnValue().Set(Array::New(args.GetIsolate(), ret, arraysize(ret))); } 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()); } static uint32_t GetUVHandleTypeCode(const uv_handle_type type) { // TODO(anonrig): We can use an enum here and then create the array in the // binding, which will remove the hard-coding in C++ and JS land. // Currently, the return type of this function corresponds to the index of the // array defined in the JS land. This is done as an optimization to reduce the // string serialization overhead. switch (type) { case UV_TCP: return 0; case UV_TTY: return 1; case UV_UDP: return 2; case UV_FILE: return 3; case UV_NAMED_PIPE: return 4; case UV_UNKNOWN_HANDLE: return 5; default: ABORT(); } } 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); args.GetReturnValue().Set(GetUVHandleTypeCode(t)); } static uint32_t FastGuessHandleType(Local receiver, const uint32_t fd) { uv_handle_type t = uv_guess_handle(fd); return GetUVHandleTypeCode(t); } CFunction fast_guess_handle_type_(CFunction::Make(FastGuessHandleType)); static void ParseEnv(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); // content CHECK(args[0]->IsString()); Utf8Value content(env->isolate(), args[0]); Dotenv dotenv{}; dotenv.ParseContent(content.ToStringView()); args.GetReturnValue().Set(dotenv.ToObject(env)); } static void GetCallSites(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsNumber()); const uint32_t frames = args[0].As()->Value(); DCHECK(frames >= 1 && frames <= 200); // +1 for disregarding node:util Local stack = StackTrace::CurrentStackTrace(isolate, frames + 1); const int frame_count = stack->GetFrameCount(); LocalVector callsite_objects(isolate); // Frame 0 is node:util. It should be skipped. for (int i = 1; i < frame_count; ++i) { Local stack_frame = stack->GetFrame(isolate, i); Local function_name = stack_frame->GetFunctionName(); if (function_name.IsEmpty()) { function_name = v8::String::Empty(isolate); } Local script_name = stack_frame->GetScriptName(); if (script_name.IsEmpty()) { script_name = v8::String::Empty(isolate); } Local names[] = { env->function_name_string(), env->script_name_string(), env->line_number_string(), env->column_string(), }; Local values[] = { function_name, script_name, Integer::NewFromUnsigned(isolate, stack_frame->GetLineNumber()), Integer::NewFromUnsigned(isolate, stack_frame->GetColumn()), }; Local obj = Object::New( isolate, v8::Null(isolate), names, values, arraysize(names)); callsite_objects.push_back(obj); } Local callsites = Array::New(isolate, callsite_objects.data(), callsite_objects.size()); args.GetReturnValue().Set(callsites); } static void IsInsideNodeModules(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); CHECK_EQ(args.Length(), 2); CHECK(args[0]->IsInt32()); // frame_limit // The second argument is the default value. int frames_limit = args[0].As()->Value(); Local stack = StackTrace::CurrentStackTrace(isolate, frames_limit); int frame_count = stack->GetFrameCount(); // If the search requires looking into more than |frames_limit| frames, give // up and return the specified default value. if (frame_count == frames_limit) { return args.GetReturnValue().Set(args[1]); } bool result = false; for (int i = 0; i < frame_count; ++i) { Local stack_frame = stack->GetFrame(isolate, i); Local script_name = stack_frame->GetScriptName(); if (script_name.IsEmpty() || script_name->Length() == 0) { continue; } Utf8Value script_name_utf8(isolate, script_name); std::string_view script_name_str = script_name_utf8.ToStringView(); if (script_name_str.starts_with("node:")) { continue; } if (script_name_str.find("/node_modules/") != std::string::npos || script_name_str.find("\\node_modules\\") != std::string::npos || script_name_str.find("/node_modules\\") != std::string::npos || script_name_str.find("\\node_modules/") != std::string::npos) { result = true; break; } } args.GetReturnValue().Set(result); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetPromiseDetails); registry->Register(GetProxyDetails); registry->Register(GetCallerLocation); registry->Register(PreviewEntries); registry->Register(GetCallSites); registry->Register(GetOwnNonIndexProperties); registry->Register(GetConstructorName); registry->Register(GetExternalValue); registry->Register(Sleep); registry->Register(ArrayBufferViewHasBuffer); registry->Register(GuessHandleType); registry->Register(FastGuessHandleType); registry->Register(fast_guess_handle_type_.GetTypeInfo()); registry->Register(ParseEnv); registry->Register(IsInsideNodeModules); } 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 #define V(name) \ constants \ ->Set( \ context, \ FIXED_ONE_BYTE_STRING(isolate, #name), \ Integer::New(isolate, \ static_cast(BaseObject::TransferMode::name))) \ .Check(); V(kDisallowCloneAndTransfer); V(kTransferable); V(kCloneable); #undef V target->Set(context, env->constants_string(), constants).Check(); } SetMethod(context, target, "isInsideNodeModules", IsInsideNodeModules); SetMethodNoSideEffect( context, target, "getPromiseDetails", GetPromiseDetails); SetMethodNoSideEffect(context, target, "getProxyDetails", GetProxyDetails); SetMethodNoSideEffect( context, target, "getCallerLocation", GetCallerLocation); SetMethodNoSideEffect(context, target, "previewEntries", PreviewEntries); SetMethodNoSideEffect( context, target, "getOwnNonIndexProperties", GetOwnNonIndexProperties); SetMethodNoSideEffect( context, target, "getConstructorName", GetConstructorName); SetMethodNoSideEffect(context, target, "getExternalValue", GetExternalValue); SetMethodNoSideEffect(context, target, "getCallSites", GetCallSites); SetMethod(context, target, "sleep", Sleep); SetMethod(context, target, "parseEnv", ParseEnv); 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()); SetFastMethodNoSideEffect(context, target, "guessHandleType", GuessHandleType, &fast_guess_handle_type_); } } // namespace util } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(util, node::util::Initialize) NODE_BINDING_EXTERNAL_REFERENCE(util, node::util::RegisterExternalReferences)