Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: use custom fprintf alike to write errors to stderr #31446

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@
'src/connect_wrap.h',
'src/connection_wrap.h',
'src/debug_utils.h',
'src/debug_utils-inl.h',
'src/env.h',
'src/env-inl.h',
'src/handle_wrap.h',
Expand Down
95 changes: 95 additions & 0 deletions src/debug_utils-inl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#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 <type_traits>

namespace node {

struct ToStringHelper {
template <typename T>
static std::string Convert(
const T& value,
std::string(T::* to_string)() const = &T::ToString) {
return (value.*to_string)();
}
template <typename T,
typename test_for_number = typename std::
enable_if<std::is_arithmetic<T>::value, bool>::type,
typename dummy = bool>
static std::string Convert(const T& value) { return std::to_string(value); }
static std::string Convert(const char* value) { return value; }
static std::string Convert(const std::string& value) { return value; }
static std::string Convert(bool value) { return value ? "true" : "false"; }
};

template <typename T>
std::string ToString(const T& value) {
return ToStringHelper::Convert(value);
}

inline std::string SPrintFImpl(const char* format) {
const char* p = strchr(format, '%');
if (LIKELY(p == nullptr)) return format;
CHECK_EQ(p[1], '%'); // Only '%%' allowed when there are no arguments.

return std::string(format, p + 1) + SPrintFImpl(p + 2);
}

template <typename Arg, typename... Args>
std::string COLD_NOINLINE SPrintFImpl( // NOLINT(runtime/string)
const char* format, Arg&& arg, Args&&... args) {
const char* p = strchr(format, '%');
CHECK_NOT_NULL(p); // If you hit this, you passed in too many arguments.
std::string ret(format, p);
// Ignore long / size_t modifiers
while (strchr("lz", *++p) != nullptr) {}
switch (*p) {
case '%': {
return ret + '%' + SPrintFImpl(p + 1,
std::forward<Arg>(arg),
std::forward<Args>(args)...);
}
default: {
return ret + '%' + SPrintFImpl(p,
std::forward<Arg>(arg),
std::forward<Args>(args)...);
}
case 'd':
case 'i':
case 'u':
case 's': ret += ToString(arg); break;
case 'p': {
CHECK(std::is_pointer<typename std::remove_reference<Arg>::type>::value);
char out[20];
int n = snprintf(out,
sizeof(out),
"%p",
*reinterpret_cast<const void* const*>(&arg));
CHECK_GE(n, 0);
ret += out;
break;
}
}
return ret + SPrintFImpl(p + 1, std::forward<Args>(args)...);
}

template <typename... Args>
std::string COLD_NOINLINE SPrintF( // NOLINT(runtime/string)
const char* format, Args&&... args) {
return SPrintFImpl(format, std::forward<Args>(args)...);
}

template <typename... Args>
void COLD_NOINLINE FPrintF(FILE* file, const char* format, Args&&... args) {
FWrite(file, SPrintF(format, std::forward<Args>(args)...));
}

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_DEBUG_UTILS_INL_H_
45 changes: 44 additions & 1 deletion src/debug_utils.cc
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#include "debug_utils.h"
#include "debug_utils-inl.h" // NOLINT(build/include)
#include "env-inl.h"

#ifdef __POSIX__
#if defined(__linux__)
#include <features.h>
#endif

#ifdef __ANDROID__
#include <android/log.h>
#endif

#if defined(__linux__) && !defined(__GLIBC__) || \
defined(__UCLIBC__) || \
defined(_AIX)
Expand Down Expand Up @@ -437,6 +441,45 @@ std::vector<std::string> NativeSymbolDebuggingContext::GetLoadedLibraries() {
return list;
}

void FWrite(FILE* file, const std::string& str) {
auto simple_fwrite = [&]() {
// The return value is ignored because there's no good way to handle it.
fwrite(str.data(), str.size(), 1, file);
addaleax marked this conversation as resolved.
Show resolved Hide resolved
};

if (file != stderr && file != stdout) {
simple_fwrite();
return;
}
#ifdef _WIN32
HANDLE handle =
GetStdHandle(file == stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);

// Check if stderr is something other than a tty/console
if (handle == INVALID_HANDLE_VALUE || handle == nullptr ||
uv_guess_handle(_fileno(file)) != UV_TTY) {
simple_fwrite();
return;
}

// Get required wide buffer size
int n = MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), nullptr, 0);

std::vector<wchar_t> wbuf(n);
MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), wbuf.data(), n);

// Don't include the final null character in the output
CHECK_GT(n, 0);
WriteConsoleW(handle, wbuf.data(), n - 1, nullptr, nullptr);
return;
#elif defined(__ANDROID__)
if (file == stderr) {
__android_log_print(ANDROID_LOG_ERROR, "nodejs", "%s", str.data());
return;
}
#endif
simple_fwrite();
}

} // namespace node

Expand Down
18 changes: 16 additions & 2 deletions src/debug_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,36 @@

namespace node {

template <typename T>
inline std::string ToString(const T& value);

// C++-style variant of sprintf()/fprintf() that:
// - Returns an std::string
// - Handles \0 bytes correctly
// - Supports %p and %s. %d, %i and %u are aliases for %s.
// - Accepts any class that has a ToString() method for stringification.
template <typename... Args>
inline std::string SPrintF(const char* format, Args&&... args);
template <typename... Args>
inline void FPrintF(FILE* file, const char* format, Args&&... args);
void FWrite(FILE* file, const std::string& str);

template <typename... Args>
inline void FORCE_INLINE Debug(Environment* env,
DebugCategory cat,
const char* format,
Args&&... args) {
if (!UNLIKELY(env->debug_enabled(cat)))
return;
fprintf(stderr, format, std::forward<Args>(args)...);
FPrintF(stderr, format, std::forward<Args>(args)...);
}

inline void FORCE_INLINE Debug(Environment* env,
DebugCategory cat,
const char* message) {
if (!UNLIKELY(env->debug_enabled(cat)))
return;
fprintf(stderr, "%s", message);
FPrintF(stderr, "%s", message);
}

template <typename... Args>
Expand Down
2 changes: 1 addition & 1 deletion src/inspector_io.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "inspector/main_thread_interface.h"
#include "inspector/node_string.h"
#include "base_object-inl.h"
#include "debug_utils.h"
#include "debug_utils-inl.h"
#include "node.h"
#include "node_crypto.h"
#include "node_internals.h"
Expand Down
2 changes: 1 addition & 1 deletion src/inspector_profiler.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "inspector_profiler.h"
#include "base_object-inl.h"
#include "debug_utils.h"
#include "debug_utils-inl.h"
#include "diagnosticfilename-inl.h"
#include "memory_tracker-inl.h"
#include "node_file.h"
Expand Down
2 changes: 1 addition & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

// ========== local headers ==========

#include "debug_utils.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_binding.h"
Expand Down
Loading