From 2e60743c6710799af53be1164fbd842a1f980dce Mon Sep 17 00:00:00 2001 From: Marvel Harisson Date: Mon, 26 Oct 2026 01:30:45 +0900 Subject: [PATCH] Fix formatting of nullptr with FMT_COMPILE and to_string (#4993) * Fix formatting of nullptr with FMT_COMPILE and to_string std::nullptr_t is implicitly convertible to string_view and const char*, so compiled format strings and fmt::to_string treated nullptr as a null C string: FMT_COMPILE("{}!") crashed and FMT_COMPILE("{}") and to_string threw "string pointer is null". Format it as a pointer ("0x0") like the runtime API does. * Write nullptr as a pointer in detail::write detail::write(out, nullptr) picked the const Char* overload, so fmt::to_string(nullptr) and FMT_COMPILE("{}") threw "string pointer is null". Add a nullptr_t overload that forwards to the const void* one. --- include/fmt/compile.h | 3 ++- include/fmt/format.h | 5 +++++ test/compile-test.cc | 2 ++ test/format-test.cc | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 2a0be049..50b929aa 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -175,7 +175,8 @@ template struct field { template constexpr auto format(OutputIt out, const T&... args) const -> OutputIt { const V& arg = get_arg_checked(args...); - if constexpr (std::is_convertible>::value) { + if constexpr (std::is_convertible>::value && + !std::is_same::value) { auto s = basic_string_view(arg); return copy(s.begin(), s.end(), out); } else if constexpr (use_format_as::value) { diff --git a/include/fmt/format.h b/include/fmt/format.h index 25a8bcf8..7720371d 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3920,6 +3920,11 @@ auto write(OutputIt out, const T* value, const format_specs& specs = {}, return write_ptr(out, bit_cast(value), &specs); } +template +auto write(OutputIt out, nullptr_t) -> OutputIt { + return write(out, static_cast(nullptr)); +} + template ::value == type::custom_type && diff --git a/test/compile-test.cc b/test/compile-test.cc index 12a7f33c..a022da36 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -76,6 +76,7 @@ TEST(compile_test, format_default) { EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo")); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo"))); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable())); + EXPECT_EQ("0x0", fmt::format(FMT_COMPILE("{}"), nullptr)); auto t = std::chrono::system_clock::now(); EXPECT_EQ(fmt::format("{}", t), fmt::format(FMT_COMPILE("{}"), t)); } @@ -314,6 +315,7 @@ TEST(compile_test, static_format_high_precision) { TEST(compile_test, text_and_arg) { EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42)); + EXPECT_EQ("0x0!", fmt::format(FMT_COMPILE("{}!"), nullptr)); } TEST(compile_test, unknown_format_fallback) { diff --git a/test/format-test.cc b/test/format-test.cc index f4b4b830..34b0dfe1 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2339,6 +2339,7 @@ FMT_END_NAMESPACE TEST(format_test, to_string) { EXPECT_EQ(fmt::to_string(42), "42"); EXPECT_EQ(fmt::to_string(reinterpret_cast(0x1234)), "0x1234"); + EXPECT_EQ(fmt::to_string(nullptr), "0x0"); EXPECT_EQ(fmt::to_string(adl_test::fmt::detail::foo()), "foo"); EXPECT_EQ(fmt::to_string(foo), "0");