| #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::ArrayBuffer; |
| using v8::ArrayBufferView; |
| using v8::BigInt; |
| using v8::Boolean; |
| using v8::CFunction; |
| using v8::Context; |
| using v8::DictionaryTemplate; |
| 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::MaybeLocal; |
| 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::SharedArrayBuffer; |
| 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<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| |
| CHECK(args[0]->IsObject()); |
| CHECK(args[1]->IsUint32()); |
| |
| Local<Object> object = args[0].As<Object>(); |
| |
| Local<Array> properties; |
| |
| PropertyFilter filter = FromV8Value<PropertyFilter>(args[1]); |
| |
| if (!object->GetPropertyNames( |
| context, KeyCollectionMode::kOwnOnly, |
| filter, |
| IndexFilter::kSkipIndices) |
| .ToLocal(&properties)) { |
| return; |
| } |
| args.GetReturnValue().Set(properties); |
| } |
| |
| static void GetConstructorName( |
| const FunctionCallbackInfo<Value>& args) { |
| CHECK(args[0]->IsObject()); |
| |
| Local<Object> object = args[0].As<Object>(); |
| Local<String> name = object->GetConstructorName(); |
| |
| args.GetReturnValue().Set(name); |
| } |
| |
| static void GetExternalValue( |
| const FunctionCallbackInfo<Value>& args) { |
| CHECK(args[0]->IsExternal()); |
| Isolate* isolate = args.GetIsolate(); |
| Local<External> external = args[0].As<External>(); |
| |
| void* ptr = external->Value(); |
| uint64_t value = reinterpret_cast<uint64_t>(ptr); |
| Local<BigInt> ret = BigInt::NewFromUnsigned(isolate, value); |
| args.GetReturnValue().Set(ret); |
| } |
| |
| static void GetPromiseDetails(const FunctionCallbackInfo<Value>& args) { |
| // Return undefined if it's not a Promise. |
| if (!args[0]->IsPromise()) |
| return; |
| |
| auto isolate = args.GetIsolate(); |
| |
| Local<Promise> promise = args[0].As<Promise>(); |
| |
| int state = promise->State(); |
| Local<Value> values[2] = { Integer::New(isolate, state) }; |
| size_t number_of_values = 1; |
| if (state != Promise::PromiseState::kPending) |
| values[number_of_values++] = promise->Result(); |
| Local<Array> ret = Array::New(isolate, values, number_of_values); |
| args.GetReturnValue().Set(ret); |
| } |
| |
| static void GetProxyDetails(const FunctionCallbackInfo<Value>& args) { |
| // Return undefined if it's not a proxy. |
| if (!args[0]->IsProxy()) |
| return; |
| |
| Local<Proxy> proxy = args[0].As<Proxy>(); |
| |
| // 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<Value> ret[] = { |
| proxy->GetTarget(), |
| proxy->GetHandler() |
| }; |
| |
| args.GetReturnValue().Set( |
| Array::New(args.GetIsolate(), ret, arraysize(ret))); |
| } else { |
| Local<Value> ret = proxy->GetTarget(); |
| |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| |
| static void GetCallerLocation(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<StackTrace> 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<StackFrame> frame = trace->GetFrame(isolate, 1); |
| Local<Value> file = frame->GetScriptNameOrSourceURL(); |
| |
| if (file.IsEmpty()) { |
| return; |
| } |
| |
| Local<Value> 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<Value>& args) { |
| if (!args[0]->IsObject()) |
| return; |
| |
| Isolate* isolate = args.GetIsolate(); |
| bool is_key_value; |
| Local<Array> entries; |
| if (!args[0].As<Object>()->PreviewEntries(&is_key_value).ToLocal(&entries)) |
| return; |
| // Fast path for WeakMap and WeakSet. |
| if (args.Length() == 1) |
| return args.GetReturnValue().Set(entries); |
| |
| Local<Value> ret[] = {entries, Boolean::New(isolate, is_key_value)}; |
| return args.GetReturnValue().Set(Array::New(isolate, ret, arraysize(ret))); |
| } |
| |
| static void Sleep(const FunctionCallbackInfo<Value>& args) { |
| CHECK(args[0]->IsUint32()); |
| uint32_t msec = args[0].As<Uint32>()->Value(); |
| uv_sleep(msec); |
| } |
| |
| void ArrayBufferViewHasBuffer(const FunctionCallbackInfo<Value>& args) { |
| CHECK(args[0]->IsArrayBufferView()); |
| args.GetReturnValue().Set(args[0].As<ArrayBufferView>()->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<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| int fd; |
| if (!args[0]->Int32Value(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<Value> 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<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| Environment* env = Environment::GetCurrent(context); |
| CHECK_EQ(args.Length(), 1); // content |
| CHECK(args[0]->IsString()); |
| Utf8Value content(isolate, args[0]); |
| Dotenv dotenv{}; |
| dotenv.ParseContent(content.ToStringView()); |
| Local<Object> obj; |
| if (dotenv.ToObject(env).ToLocal(&obj)) { |
| args.GetReturnValue().Set(obj); |
| } |
| } |
| |
| static void GetCallSites(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| Environment* env = Environment::GetCurrent(context); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsNumber()); |
| const uint32_t frames = args[0].As<Uint32>()->Value(); |
| CHECK(frames >= 1 && frames <= 200); |
| |
| // +1 for disregarding node:util |
| Local<StackTrace> stack = StackTrace::CurrentStackTrace(isolate, frames + 1); |
| const int frame_count = stack->GetFrameCount(); |
| LocalVector<Value> callsite_objects(isolate); |
| |
| auto callsite_template = env->callsite_template(); |
| if (callsite_template.IsEmpty()) { |
| static constexpr std::string_view names[] = { |
| "functionName", |
| "scriptId", |
| "scriptName", |
| "lineNumber", |
| "columnNumber", |
| // TODO(legendecas): deprecate CallSite.column. |
| "column"}; |
| callsite_template = DictionaryTemplate::New(isolate, names); |
| env->set_callsite_template(callsite_template); |
| } |
| |
| // Frame 0 is node:util. It should be skipped. |
| for (int i = 1; i < frame_count; ++i) { |
| Local<StackFrame> stack_frame = stack->GetFrame(isolate, i); |
| |
| Local<Value> function_name = stack_frame->GetFunctionName(); |
| if (function_name.IsEmpty()) { |
| function_name = v8::String::Empty(isolate); |
| } |
| |
| Local<Value> script_name = stack_frame->GetScriptName(); |
| if (script_name.IsEmpty()) { |
| script_name = v8::String::Empty(isolate); |
| } |
| |
| std::string script_id = std::to_string(stack_frame->GetScriptId()); |
| |
| MaybeLocal<Value> values[] = { |
| function_name, |
| OneByteString(isolate, script_id), |
| script_name, |
| Integer::NewFromUnsigned(isolate, stack_frame->GetLineNumber()), |
| Integer::NewFromUnsigned(isolate, stack_frame->GetColumn()), |
| // TODO(legendecas): deprecate CallSite.column. |
| Integer::NewFromUnsigned(isolate, stack_frame->GetColumn()), |
| }; |
| |
| Local<Object> callsite; |
| if (!NewDictionaryInstanceNullProto(context, callsite_template, values) |
| .ToLocal(&callsite)) { |
| return; |
| } |
| callsite_objects.push_back(callsite); |
| } |
| |
| Local<Array> callsites = |
| Array::New(isolate, callsite_objects.data(), callsite_objects.size()); |
| args.GetReturnValue().Set(callsites); |
| } |
| |
| static void IsInsideNodeModules(const FunctionCallbackInfo<Value>& 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<v8::Int32>()->Value(); |
| Local<StackTrace> 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<StackFrame> stack_frame = stack->GetFrame(isolate, i); |
| Local<String> 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); |
| } |
| |
| static void DefineLazyPropertiesGetter( |
| Local<v8::Name> name, const v8::PropertyCallbackInfo<Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| // This getter has no JavaScript function representation and is not |
| // invoked in the creation context. |
| // When this getter is invoked in a vm context, the `Realm::GetCurrent(info)` |
| // returns a nullptr and retrieve the creation context via `this` object and |
| // get the creation Realm. |
| Local<Value> receiver_val = info.HolderV2(); |
| if (!receiver_val->IsObject()) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| return; |
| } |
| Local<Object> receiver = receiver_val.As<Object>(); |
| Local<Context> context; |
| if (!receiver->GetCreationContext().ToLocal(&context)) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| return; |
| } |
| |
| Realm* realm = Realm::GetCurrent(context); |
| Local<Value> arg = info.Data(); |
| Local<Value> require_result; |
| if (!realm->builtin_module_require() |
| ->Call(context, Null(isolate), 1, &arg) |
| .ToLocal(&require_result)) { |
| // V8 will have scheduled an error to be thrown. |
| return; |
| } |
| Local<Value> ret; |
| if (!require_result.As<v8::Object>()->Get(context, name).ToLocal(&ret)) { |
| // V8 will have scheduled an error to be thrown. |
| return; |
| } |
| info.GetReturnValue().Set(ret); |
| } |
| |
| static void DefineLazyProperties(const FunctionCallbackInfo<Value>& args) { |
| // target: object, id: string, keys: string[][, enumerable = true] |
| CHECK_GE(args.Length(), 3); |
| // target: Object where to define the lazy properties. |
| CHECK(args[0]->IsObject()); |
| // id: Internal module to lazy-load where the API to expose are implemented. |
| CHECK(args[1]->IsString()); |
| // keys: Keys to map from `require(id)` and `target`. |
| CHECK(args[2]->IsArray()); |
| // enumerable: Whether the property should be enumerable. |
| CHECK(args.Length() == 3 || args[3]->IsBoolean()); |
| |
| auto context = args.GetIsolate()->GetCurrentContext(); |
| |
| auto target = args[0].As<Object>(); |
| Local<Value> id = args[1]; |
| v8::PropertyAttribute attribute = |
| args.Length() == 3 || args[3]->IsTrue() ? v8::None : v8::DontEnum; |
| |
| const Local<Array> keys = args[2].As<Array>(); |
| size_t length = keys->Length(); |
| for (size_t i = 0; i < length; i++) { |
| Local<Value> key; |
| if (!keys->Get(context, i).ToLocal(&key)) { |
| // V8 will have scheduled an error to be thrown. |
| return; |
| } |
| CHECK(key->IsString()); |
| if (target |
| ->SetLazyDataProperty(context, |
| key.As<String>(), |
| DefineLazyPropertiesGetter, |
| id, |
| attribute) |
| .IsNothing()) { |
| // V8 will have scheduled an error to be thrown. |
| return; |
| }; |
| } |
| } |
| |
| void ConstructSharedArrayBuffer(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| int64_t length; |
| // Note: IntegerValue() clamps its output, so excessively large input values |
| // will not overflow |
| if (!args[0]->IntegerValue(env->context()).To(&length)) { |
| return; |
| } |
| if (length < 0 || |
| static_cast<uint64_t>(length) > ArrayBuffer::kMaxByteLength) { |
| env->ThrowRangeError("Invalid array buffer length"); |
| return; |
| } |
| Local<SharedArrayBuffer> sab; |
| if (!SharedArrayBuffer::MaybeNew(env->isolate(), static_cast<size_t>(length)) |
| .ToLocal(&sab)) { |
| // Note: SharedArrayBuffer::MaybeNew doesn't schedule an exception if it |
| // fails |
| env->ThrowRangeError("Array buffer allocation failed"); |
| return; |
| } |
| args.GetReturnValue().Set(sab); |
| } |
| |
| 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(fast_guess_handle_type_); |
| registry->Register(ParseEnv); |
| registry->Register(IsInsideNodeModules); |
| registry->Register(DefineLazyProperties); |
| registry->Register(DefineLazyPropertiesGetter); |
| registry->Register(ConstructSharedArrayBuffer); |
| } |
| |
| void Initialize(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Environment* env = Environment::GetCurrent(context); |
| Isolate* isolate = env->isolate(); |
| |
| { |
| Local<ObjectTemplate> 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<Object> 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<int32_t>(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); |
| SetMethod(context, target, "defineLazyProperties", DefineLazyProperties); |
| 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); |
| SetMethod(context, |
| target, |
| "constructSharedArrayBuffer", |
| ConstructSharedArrayBuffer); |
| |
| Local<String> 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) |