#ifndef SRC_DEBUG_UTILS_INL_H_ #define SRC_DEBUG_UTILS_INL_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "debug_utils.h" #include "env.h" #include "util-inl.h" #include namespace node { template concept StringViewConvertible = requires(T a) { { a.ToStringView() } -> std::convertible_to; }; template concept StringConvertible = requires(T a) { { a.ToString() } -> std::convertible_to; }; // For std::filesystem::path and similar types template concept StringConvertibleFSPathLike = requires(T a) { { a.string() } -> std::convertible_to; }; struct ToStringHelper { template requires(StringConvertible) && (!StringViewConvertible) static std::string Convert(const T& value) { return value.ToString(); } template requires(StringConvertibleFSPathLike) && (!StringViewConvertible) && (!StringConvertible) static std::string Convert(const T& value) { return value.string(); } template requires StringViewConvertible static std::string_view Convert(const T& value) { return value.ToStringView(); } template || std::is_enum_v, bool>, typename dummy = bool> static std::string Convert(const T& value) { return std::to_string(value); } static std::string_view Convert(const char* value) { return value != nullptr ? value : "(null)"; } static std::string Convert(const std::string& value) { return value; } static std::string_view Convert(std::string_view value) { return value; } static std::string Convert(bool value) { return value ? "true" : "false"; } static std::string Convert(v8::Local value) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (value->IsString()) { Utf8Value utf8_value(isolate, value); return SPrintF("\"%s\"", utf8_value.ToString()); } v8::MaybeLocal maybe_detail = value->ToDetailString(isolate->GetCurrentContext()); v8::Local detail; if (!maybe_detail.ToLocal(&detail)) { // This will only occur when terminating. No exception is expected // with `ToDetailString`. return ""; } Utf8Value utf8_value(isolate, detail); return utf8_value.ToString(); } template >> static std::string BaseConvert(const T& value) { auto v = static_cast(value); char ret[3 * sizeof(T)]; char* ptr = ret + 3 * sizeof(T) - 1; *ptr = '\0'; const char* digits = "0123456789abcdef"; do { unsigned digit = v & ((1 << BASE_BITS) - 1); *--ptr = (BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]); } while ((v >>= BASE_BITS) != 0); return ptr; } template >> static auto BaseConvert(T&& value) { return Convert(std::forward(value)); } }; template auto ToStringOrStringView(const T& value) { return ToStringHelper::Convert(value); } template std::string ToString(const T& value) { return std::string(ToStringOrStringView(value)); } template auto ToBaseString(const T& value) { return ToStringHelper::BaseConvert(value); } inline std::string SPrintFImpl(std::string_view format) { auto offset = format.find('%'); if (offset == std::string_view::npos) return std::string(format); CHECK_LT(offset + 1, format.size()); CHECK_EQ(format[offset + 1], '%'); // Only '%%' allowed when there are no arguments. return std::string(format.substr(0, offset + 1)) + SPrintFImpl(format.substr(offset + 2)); } template std::string COLD_NOINLINE SPrintFImpl( // NOLINT(runtime/string) std::string_view format, Arg&& arg, Args&&... args) { auto offset = format.find('%'); CHECK_NE(offset, std::string_view::npos); // If you hit this, you passed in // too many arguments. std::string ret(format.substr(0, offset)); // Ignore long / size_t modifiers while (++offset < format.size() && (format[offset] == 'l' || format[offset] == 'z')) { } switch (offset == format.size() ? '\0' : format[offset]) { case '%': { return ret + '%' + SPrintFImpl(format.substr(offset + 1), std::forward(arg), std::forward(args)...); } default: { return ret + '%' + SPrintFImpl(format.substr(offset), std::forward(arg), std::forward(args)...); } case 'd': case 'i': case 'u': case 's': ret += ToStringOrStringView(arg); break; case 'o': ret += ToBaseString<3>(arg); break; case 'x': ret += ToBaseString<4>(arg); break; case 'X': ret += node::ToUpper(ToBaseString<4>(arg)); break; case 'p': { CHECK(std::is_pointer_v>); char out[20]; int n = snprintf( out, sizeof(out), "%p", *reinterpret_cast(&arg)); CHECK_GE(n, 0); ret += out; break; } } return ret + SPrintFImpl(format.substr(offset + 1), std::forward(args)...); } template std::string COLD_NOINLINE SPrintF( // NOLINT(runtime/string) std::string_view format, Args&&... args) { return SPrintFImpl(format, std::forward(args)...); } template void COLD_NOINLINE FPrintF(FILE* file, std::string_view format, Args&&... args) { FWrite(file, SPrintF(format, std::forward(args)...)); } template inline void FORCE_INLINE Debug(EnabledDebugList* list, DebugCategory cat, const char* format, Args&&... args) { if (!list->enabled(cat)) [[unlikely]] return; FPrintF(stderr, format, std::forward(args)...); } inline void FORCE_INLINE Debug(EnabledDebugList* list, DebugCategory cat, const char* message) { if (!list->enabled(cat)) [[unlikely]] return; FPrintF(stderr, "%s", message); } template inline void FORCE_INLINE Debug(Environment* env, DebugCategory cat, const char* format, Args&&... args) { Debug(env->enabled_debug_list(), cat, format, std::forward(args)...); } inline void FORCE_INLINE Debug(Environment* env, DebugCategory cat, const char* message) { Debug(env->enabled_debug_list(), cat, message); } template inline void Debug(Environment* env, DebugCategory cat, const std::string& format, Args&&... args) { Debug(env->enabled_debug_list(), cat, format.c_str(), std::forward(args)...); } // Used internally by the 'real' Debug(AsyncWrap*, ...) functions below, so that // the FORCE_INLINE flag on them doesn't apply to the contents of this function // as well. // We apply COLD_NOINLINE to tell the compiler that it's not worth optimizing // this function for speed and it should rather focus on keeping it out of // hot code paths. In particular, we want to keep the string concatenating code // out of the function containing the original `Debug()` call. template void COLD_NOINLINE UnconditionalAsyncWrapDebug(AsyncWrap* async_wrap, const char* format, Args&&... args) { Debug(async_wrap->env(), static_cast(async_wrap->provider_type()), async_wrap->diagnostic_name() + " " + format + "\n", std::forward(args)...); } template inline void FORCE_INLINE Debug(AsyncWrap* async_wrap, const char* format, Args&&... args) { DCHECK_NOT_NULL(async_wrap); if (auto cat = static_cast(async_wrap->provider_type()); !async_wrap->env()->enabled_debug_list()->enabled(cat)) [[unlikely]] { return; } UnconditionalAsyncWrapDebug(async_wrap, format, std::forward(args)...); } template inline void FORCE_INLINE Debug(AsyncWrap* async_wrap, const std::string& format, Args&&... args) { Debug(async_wrap, format.c_str(), std::forward(args)...); } namespace per_process { template inline void FORCE_INLINE Debug(DebugCategory cat, const char* format, Args&&... args) { Debug(&enabled_debug_list, cat, format, std::forward(args)...); } inline void FORCE_INLINE Debug(DebugCategory cat, const char* message) { Debug(&enabled_debug_list, cat, message); } } // namespace per_process } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_DEBUG_UTILS_INL_H_