Skip to content

Commit 7bdd29f

Browse files
addaleaxcodebytere
authored andcommitted
src: add C++-style sprintf utility
Add an utility that handles C++-style strings and objects well. PR-URL: #31446 Fixes: #28761 Fixes: #31218 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 393b48e commit 7bdd29f

18 files changed

+165
-18
lines changed

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@
609609
'src/connect_wrap.h',
610610
'src/connection_wrap.h',
611611
'src/debug_utils.h',
612+
'src/debug_utils-inl.h',
612613
'src/env.h',
613614
'src/env-inl.h',
614615
'src/handle_wrap.h',

src/debug_utils-inl.h

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#ifndef SRC_DEBUG_UTILS_INL_H_
2+
#define SRC_DEBUG_UTILS_INL_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include "debug_utils.h"
7+
8+
#include <type_traits>
9+
10+
namespace node {
11+
12+
struct ToStringHelper {
13+
template <typename T>
14+
static std::string Convert(
15+
const T& value,
16+
std::string(T::* to_string)() const = &T::ToString) {
17+
return (value.*to_string)();
18+
}
19+
template <typename T,
20+
typename test_for_number = typename std::
21+
enable_if<std::is_arithmetic<T>::value, bool>::type,
22+
typename dummy = bool>
23+
static std::string Convert(const T& value) { return std::to_string(value); }
24+
static std::string Convert(const char* value) { return value; }
25+
static std::string Convert(const std::string& value) { return value; }
26+
static std::string Convert(bool value) { return value ? "true" : "false"; }
27+
};
28+
29+
template <typename T>
30+
std::string ToString(const T& value) {
31+
return ToStringHelper::Convert(value);
32+
}
33+
34+
inline std::string SPrintFImpl(const char* format) {
35+
const char* p = strchr(format, '%');
36+
if (LIKELY(p == nullptr)) return format;
37+
CHECK_EQ(p[1], '%'); // Only '%%' allowed when there are no arguments.
38+
39+
return std::string(format, p + 1) + SPrintFImpl(p + 2);
40+
}
41+
42+
template <typename Arg, typename... Args>
43+
std::string COLD_NOINLINE SPrintFImpl( // NOLINT(runtime/string)
44+
const char* format, Arg&& arg, Args&&... args) {
45+
const char* p = strchr(format, '%');
46+
CHECK_NOT_NULL(p); // If you hit this, you passed in too many arguments.
47+
std::string ret(format, p);
48+
// Ignore long / size_t modifiers
49+
while (strchr("lz", *++p) != nullptr) {}
50+
switch (*p) {
51+
case '%': {
52+
return ret + '%' + SPrintFImpl(p + 1,
53+
std::forward<Arg>(arg),
54+
std::forward<Args>(args)...);
55+
}
56+
default: {
57+
return ret + '%' + SPrintFImpl(p,
58+
std::forward<Arg>(arg),
59+
std::forward<Args>(args)...);
60+
}
61+
case 'd':
62+
case 'i':
63+
case 'u':
64+
case 's': ret += ToString(arg); break;
65+
case 'p': {
66+
CHECK(std::is_pointer<typename std::remove_reference<Arg>::type>::value);
67+
char out[20];
68+
int n = snprintf(out,
69+
sizeof(out),
70+
"%p",
71+
*reinterpret_cast<const void* const*>(&arg));
72+
CHECK_GE(n, 0);
73+
ret += out;
74+
break;
75+
}
76+
}
77+
return ret + SPrintFImpl(p + 1, std::forward<Args>(args)...);
78+
}
79+
80+
template <typename... Args>
81+
std::string COLD_NOINLINE SPrintF( // NOLINT(runtime/string)
82+
const char* format, Args&&... args) {
83+
return SPrintFImpl(format, std::forward<Args>(args)...);
84+
}
85+
86+
} // namespace node
87+
88+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
89+
90+
#endif // SRC_DEBUG_UTILS_INL_H_

src/debug_utils.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include "debug_utils.h"
1+
#include "debug_utils-inl.h" // NOLINT(build/include)
22
#include "env-inl.h"
33

44
#ifdef __POSIX__

src/debug_utils.h

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,26 @@
2222

2323
namespace node {
2424

25+
template <typename T>
26+
inline std::string ToString(const T& value);
27+
28+
// C++-style variant of sprintf() that:
29+
// - Returns an std::string
30+
// - Handles \0 bytes correctly
31+
// - Supports %p and %s. %d, %i and %u are aliases for %s.
32+
// - Accepts any class that has a ToString() method for stringification.
33+
template <typename... Args>
34+
inline std::string SPrintF(const char* format, Args&&... args);
35+
2536
template <typename... Args>
2637
inline void FORCE_INLINE Debug(Environment* env,
2738
DebugCategory cat,
2839
const char* format,
2940
Args&&... args) {
3041
if (!UNLIKELY(env->debug_enabled(cat)))
3142
return;
32-
fprintf(stderr, format, std::forward<Args>(args)...);
43+
std::string out = SPrintF(format, std::forward<Args>(args)...);
44+
fwrite(out.data(), out.size(), 1, stderr);
3345
}
3446

3547
inline void FORCE_INLINE Debug(Environment* env,

src/inspector_io.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include "inspector/main_thread_interface.h"
55
#include "inspector/node_string.h"
66
#include "base_object-inl.h"
7-
#include "debug_utils.h"
7+
#include "debug_utils-inl.h"
88
#include "node.h"
99
#include "node_crypto.h"
1010
#include "node_internals.h"

src/inspector_profiler.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "inspector_profiler.h"
22
#include "base_object-inl.h"
3-
#include "debug_utils.h"
3+
#include "debug_utils-inl.h"
44
#include "diagnosticfilename-inl.h"
55
#include "memory_tracker-inl.h"
66
#include "node_file.h"

src/node.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
// ========== local headers ==========
2525

26-
#include "debug_utils.h"
26+
#include "debug_utils-inl.h"
2727
#include "env-inl.h"
2828
#include "memory_tracker-inl.h"
2929
#include "node_binding.h"

src/node_http2.cc

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include "aliased_buffer.h"
2-
#include "debug_utils.h"
2+
#include "debug_utils-inl.h"
33
#include "memory_tracker-inl.h"
44
#include "node.h"
55
#include "node_buffer.h"
@@ -1967,7 +1967,7 @@ std::string Http2Stream::diagnostic_name() const {
19671967

19681968
// Notify the Http2Stream that a new block of HEADERS is being processed.
19691969
void Http2Stream::StartHeaders(nghttp2_headers_category category) {
1970-
Debug(this, "starting headers, category: %d", id_, category);
1970+
Debug(this, "starting headers, category: %d", category);
19711971
CHECK(!this->IsDestroyed());
19721972
session_->DecrementCurrentSessionMemory(current_headers_length_);
19731973
current_headers_length_ = 0;
@@ -2220,7 +2220,7 @@ int Http2Stream::DoWrite(WriteWrap* req_wrap,
22202220
req_wrap->Done(UV_EOF);
22212221
return 0;
22222222
}
2223-
Debug(this, "queuing %d buffers to send", id_, nbufs);
2223+
Debug(this, "queuing %d buffers to send", nbufs);
22242224
for (size_t i = 0; i < nbufs; ++i) {
22252225
// Store the req_wrap on the last write info in the queue, so that it is
22262226
// only marked as finished once all buffers associated with it are finished.

src/node_messaging.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "node_messaging.h"
22

33
#include "async_wrap-inl.h"
4-
#include "debug_utils.h"
4+
#include "debug_utils-inl.h"
55
#include "memory_tracker-inl.h"
66
#include "node_contextify.h"
77
#include "node_buffer.h"

src/node_platform.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include "node_internals.h"
33

44
#include "env-inl.h"
5-
#include "debug_utils.h"
5+
#include "debug_utils-inl.h"
66
#include <algorithm> // find_if(), find(), move()
77
#include <cmath> // llround()
88
#include <memory> // unique_ptr(), shared_ptr(), make_shared()

src/node_report.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "env-inl.h"
22
#include "node_report.h"
3-
#include "debug_utils.h"
3+
#include "debug_utils-inl.h"
44
#include "diagnosticfilename-inl.h"
55
#include "node_internals.h"
66
#include "node_metadata.h"

src/node_wasi.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "env-inl.h"
22
#include "base_object-inl.h"
3-
#include "debug_utils.h"
3+
#include "debug_utils-inl.h"
44
#include "memory_tracker-inl.h"
55
#include "node_mem-inl.h"
66
#include "util-inl.h"
@@ -1062,7 +1062,7 @@ void WASI::PathFilestatGet(const FunctionCallbackInfo<Value>& args) {
10621062
CHECK_TO_TYPE_OR_RETURN(args, args[4], Uint32, buf_ptr);
10631063
ASSIGN_OR_RETURN_UNWRAP(&wasi, args.This());
10641064
Debug(wasi,
1065-
"path_filestat_get(%d, %d, %d, %d, %d)\n",
1065+
"path_filestat_get(%d, %d, %d)\n",
10661066
fd,
10671067
path_ptr,
10681068
path_len);

src/node_watchdog.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
#include <algorithm>
2323

24-
#include "debug_utils.h"
24+
#include "debug_utils-inl.h"
2525
#include "env-inl.h"
2626
#include "node_errors.h"
2727
#include "node_internals.h"

src/node_worker.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include "node_worker.h"
2-
#include "debug_utils.h"
2+
#include "debug_utils-inl.h"
33
#include "memory_tracker-inl.h"
44
#include "node_errors.h"
55
#include "node_buffer.h"

src/spawn_sync.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

2222
#include "spawn_sync.h"
23-
#include "debug_utils.h"
23+
#include "debug_utils-inl.h"
2424
#include "env-inl.h"
2525
#include "node_internals.h"
2626
#include "string_bytes.h"

src/tls_wrap.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
#include "tls_wrap.h"
2323
#include "async_wrap-inl.h"
24-
#include "debug_utils.h"
24+
#include "debug_utils-inl.h"
2525
#include "memory_tracker-inl.h"
2626
#include "node_buffer.h" // Buffer
2727
#include "node_crypto.h" // SecureContext

src/tracing/agent.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include <string>
44
#include "trace_event.h"
55
#include "tracing/node_trace_buffer.h"
6-
#include "debug_utils.h"
6+
#include "debug_utils-inl.h"
77
#include "env-inl.h"
88

99
namespace node {

test/cctest/test_util.cc

+44
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#include "util-inl.h"
2+
#include "debug_utils-inl.h"
3+
#include "env-inl.h"
24
#include "gtest/gtest.h"
35

46
TEST(UtilTest, ListHead) {
@@ -250,3 +252,45 @@ TEST(UtilTest, MaybeStackBuffer) {
250252
EXPECT_TRUE(buf.IsInvalidated());
251253
}
252254
}
255+
256+
TEST(UtilTest, SPrintF) {
257+
using node::SPrintF;
258+
259+
// %d, %u and %s all do the same thing. The actual C++ type is used to infer
260+
// the right representation.
261+
EXPECT_EQ(SPrintF("%s", false), "false");
262+
EXPECT_EQ(SPrintF("%s", true), "true");
263+
EXPECT_EQ(SPrintF("%d", true), "true");
264+
EXPECT_EQ(SPrintF("%u", true), "true");
265+
EXPECT_EQ(SPrintF("%d", 10000000000LL), "10000000000");
266+
EXPECT_EQ(SPrintF("%d", -10000000000LL), "-10000000000");
267+
EXPECT_EQ(SPrintF("%u", 10000000000LL), "10000000000");
268+
EXPECT_EQ(SPrintF("%u", -10000000000LL), "-10000000000");
269+
EXPECT_EQ(SPrintF("%i", 10), "10");
270+
EXPECT_EQ(SPrintF("%d", 10), "10");
271+
272+
EXPECT_EQ(atof(SPrintF("%s", 0.5).c_str()), 0.5);
273+
EXPECT_EQ(atof(SPrintF("%s", -0.5).c_str()), -0.5);
274+
275+
void (*fn)() = []() {};
276+
void* p = reinterpret_cast<void*>(&fn);
277+
EXPECT_GE(SPrintF("%p", fn).size(), 4u);
278+
EXPECT_GE(SPrintF("%p", p).size(), 4u);
279+
280+
const std::string foo = "foo";
281+
const char* bar = "bar";
282+
EXPECT_EQ(SPrintF("%s %s", foo, "bar"), "foo bar");
283+
EXPECT_EQ(SPrintF("%s %s", foo, bar), "foo bar");
284+
285+
EXPECT_EQ(SPrintF("[%% %s %%]", foo), "[% foo %]");
286+
287+
struct HasToString {
288+
std::string ToString() const {
289+
return "meow";
290+
}
291+
};
292+
EXPECT_EQ(SPrintF("%s", HasToString{}), "meow");
293+
294+
const std::string with_zero = std::string("a") + '\0' + 'b';
295+
EXPECT_EQ(SPrintF("%s", with_zero), with_zero);
296+
}

0 commit comments

Comments
 (0)