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

Printing of nullptr (char const * and void *) #226

Closed
LarsGullik opened this issue Nov 10, 2015 · 3 comments
Closed

Printing of nullptr (char const * and void *) #226

LarsGullik opened this issue Nov 10, 2015 · 3 comments

Comments

@LarsGullik
Copy link

When using cppformat as a drop-in replacement for printf on Linux with GLibc the behavior
of printing nullptr changes quite radically:

printf: Null-string prints: (null)
printf: Null-pointer prints: (nil)
fmt::printf:: Null-string prints: <throws>
fmt::printf Null-pointer prints: 0x0
iostream: Null-pointer prints: 0
iostream: Null-string prints: <makes cout invalid>

I would be really nice if the behaviour was kept. Esp. not throwing on Null-string. Perhaps it
would even be possible to retain the behaviour from whatever platform you were on.

I have done some of this in my cppformat copy, but perhaps a bit too broad since I also do this for fmt::format (not as you would want it most likely):

--- a/platform/cppformat/format.cpp
+++ b/platform/cppformat/format.cpp
@@ -410,6 +410,12 @@ class BasicArgFormatter : public ArgVisitor<Impl, void> {
   FMT_DISALLOW_COPY_AND_ASSIGN(BasicArgFormatter);

   void write_pointer(const void * p) {
+      if (!p) {
+          spec_.type_ = 's';
+          Arg::StringValue<char> str{"(nil)", 5};
+          writer_.write_str(str, spec_);
+          return;
+      }
     spec_.flags_ = HASH_FLAG;
     spec_.type_ = 'x';
     writer_.write_int(reinterpret_cast<uintptr_t>(p), spec_);
@@ -786,6 +792,13 @@ void fmt::internal::FixedBuffer<Char>::grow(std::size_t) {
 }

 template <typename Char>
+constexpr const Char * null_string();
+template <>
+constexpr const char * null_string<char>() { return "(null)"; }
+template <>
+constexpr const wchar_t * null_string<wchar_t>() { return L"(null)"; }
+
+template <typename Char>
 template <typename StrChar>
 void fmt::BasicWriter<Char>::write_str(
     const Arg::StringValue<StrChar> &s, const FormatSpec &spec) {
@@ -796,10 +809,8 @@ void fmt::BasicWriter<Char>::write_str(
   const StrChar *str_value = s.value;
   std::size_t str_size = s.size;
   if (str_size == 0) {
-    if (!str_value) {
-      FMT_THROW(FormatError("string pointer is null"));
-      return;
-    }
+    if (!str_value)
+      str_value = null_string<StrChar>();
     if (*str_value)
       str_size = std::char_traits<StrChar>::length(str_value);
   }
@vitaut
Copy link
Contributor

vitaut commented Nov 11, 2015

Passing a null string to printf with %s is an undefined behavior and can result in segfault:

  const char *s = 0;
  std::printf("%s\n", s); // segfaults in glibc

while the result of pointer formatting is implementation-defined.

That said, I think it's reasonable to expect fmt::*printf giving the same result as glibc in these cases (except for segfault), so I implemented a fix similar to the one you propose but limited to fmt::*printf in b5fda1c.

Thanks!

@vitaut vitaut closed this as completed Nov 11, 2015
@LarsGullik
Copy link
Author

Super.
Note that:

const char *s = 0;
std::printf("%p\n", s); // segfaults in glibc

is not glibc. Gcc has optimized away the whole printf and just does a puts
instead (or silmilar)

The code I used to create the small table in OP is:

#include <cstdio>
#include <iostream>
#include <format.h>

int main()
{
    void * p = nullptr;
    char const * s = nullptr;
    printf("printf: Null-string prints: %s\n", s);
    printf("printf: Null-pointer prints: %p\n", p);
    try {
        fmt::printf("fmt::printf: Null-string prints: %s\n", s);
    }
    catch (fmt::FormatError) {
        fmt::printf("fmt::printf: Null-string prints: <throws>\n");
    }
    fmt::printf("fmt::printf: Null-pointer prints: %p\n", p);
    std::cout << "iostream: Null-pointer prints: " << p << std::endl;
    //std::cout << "Null-string prints: " << s << std::endl;
    std::cout << "iostream: Null-string prints: " << "<makes cout invalid>" << std::endl;
}

@vitaut
Copy link
Contributor

vitaut commented Nov 11, 2015

Right, this is a GCC's "optimization" (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25609), not glibc's behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants