[committed] libstdc++: Use strerror_r in std::generic_category()::message(int) [PR110133]

Message ID 20231104084442.3016638-1-jwakely@redhat.com
State Accepted
Headers
Series [committed] libstdc++: Use strerror_r in std::generic_category()::message(int) [PR110133] |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

Jonathan Wakely Nov. 4, 2023, 8:44 a.m. UTC
  Tested x86_64-linux. Pushed to trunk.

Probably worth backporting after some time on trunk.

-- >8 --

Use strerror_r instead of strerror when available, due to the latter not
being thread-safe. This is complicated by Glibc providing a GNU-specific
strerror_r which is not compatible with POSIX strerror_r, so we need to
dispatch on the return type.

We can use the recently-added std::string::__resize_and_overwrite to
write directly into the string buffer when possible. Because we estimate
the initial buffer size we might end up with excess capacity in the
returned std::string. We can slightly tweak the std::system_error
constructors to make use of that excess capacity, so that in some cases
we require fewer allocations to construct the std::system_error::what()
string.

libstdc++-v3/ChangeLog:

	PR libstdc++/110133
	* include/std/system_error (system_error::system_error): Group
	arguments so that concatenation can reuse rvalue's capacity.
	* src/c++11/system_error.cc (strerror_string): New function.
	[_GLIBCXX_HAVE_STRERROR_R] (use_strerror_result): New functions.
	(generic_error_category::message): Use strerror_string.
	(system_error_category::message): Likewise.
---
 libstdc++-v3/include/std/system_error  |  4 +-
 libstdc++-v3/src/c++11/system_error.cc | 78 +++++++++++++++++++++++---
 2 files changed, 72 insertions(+), 10 deletions(-)
  

Patch

diff --git a/libstdc++-v3/include/std/system_error b/libstdc++-v3/include/std/system_error
index d71abc9766b..e26472bb0bf 100644
--- a/libstdc++-v3/include/std/system_error
+++ b/libstdc++-v3/include/std/system_error
@@ -563,7 +563,7 @@  namespace __adl_only
     : runtime_error(__ec.message()), _M_code(__ec) { }
 
     system_error(error_code __ec, const string& __what)
-    : runtime_error(__what + ": " + __ec.message()), _M_code(__ec) { }
+    : runtime_error(__what + (": " + __ec.message())), _M_code(__ec) { }
 
     system_error(error_code __ec, const char* __what)
     : runtime_error(__what + (": " + __ec.message())), _M_code(__ec) { }
@@ -576,7 +576,7 @@  namespace __adl_only
       _M_code(__v, __ecat) { }
 
     system_error(int __v, const error_category& __ecat, const string& __what)
-    : runtime_error(__what + ": " + error_code(__v, __ecat).message()),
+    : runtime_error(__what + (": " + error_code(__v, __ecat).message())),
       _M_code(__v, __ecat) { }
 
 #if __cplusplus >= 201103L
diff --git a/libstdc++-v3/src/c++11/system_error.cc b/libstdc++-v3/src/c++11/system_error.cc
index 748eee94168..876e2bb44fc 100644
--- a/libstdc++-v3/src/c++11/system_error.cc
+++ b/libstdc++-v3/src/c++11/system_error.cc
@@ -46,6 +46,74 @@  namespace
 {
   using std::string;
 
+#if _GLIBCXX_HAVE_STRERROR_R
+  // Handle the result of POSIX strerror_r.
+  inline std::size_t
+  use_strerror_result(int res, char* buf, std::size_t bufsz,
+		      std::size_t& nextbufsz)
+  {
+    if (res == 0) // Success.
+      return std::strlen(buf);
+
+    if (res == ERANGE) // Buffer too small to hold result string.
+      {
+	nextbufsz = 2 * bufsz;
+	return 0;
+      }
+    // else res == EINVAL, unknown error.
+    if (*buf == '\0') // No result string written to buffer.
+      {
+	const char msg[] = "Unknown error";
+	std::memcpy(buf, msg, sizeof(msg) - 1);
+	return sizeof(msg) - 1;
+      }
+    else // An "unknown error" string was already written to the buffer.
+      return std::strlen(buf);
+  }
+
+  // Handle the result of GNU strerror_r.
+  inline std::size_t
+  use_strerror_result(char* res, char* buf, std::size_t bufsz,
+		      std::size_t& nextbufsz)
+  {
+    if (res == buf) // Result string written to the buffer.
+      return std::strlen(res);
+
+    // Static result string returned, must be copied to the buffer.
+    std::size_t len = std::strlen(res);
+    if (len <= bufsz)
+      {
+	std::strcpy(buf, res);
+	return len;
+      }
+
+    // Reallocate and try again:
+    nextbufsz = len;
+    return 0;
+  }
+
+  string strerror_string(int err)
+  {
+    // Estimate maximum length of strerror strings (including "Unknown error").
+    // Any excess capacity here can be used by std::system_error constructors
+    // when concatenating strings.
+    std::size_t len = 60;
+    string s;
+    do
+      s.__resize_and_overwrite(len, [err, &len](char* p, std::size_t n) {
+	*p = '\0';
+	return use_strerror_result(strerror_r(err, p, n), p, n, len);
+      });
+    while (s.empty());
+    return s;
+  }
+#else
+  string strerror_string(int err)
+  {
+    return strerror(err); // XXX Not thread-safe.
+  }
+#endif
+
   template<typename T>
     struct constant_init
     {
@@ -66,11 +134,7 @@  namespace
     _GLIBCXX_DEFAULT_ABI_TAG
     string
     message(int i) const final
-    {
-      // XXX locale issues: how does one get or set loc.
-      // _GLIBCXX_HAVE_STRERROR_L, strerror_l(i, cloc)
-      return string(strerror(i));
-    }
+    { return strerror_string(i); }
 
     // Override this to avoid a virtual call to default_error_condition(i).
     bool
@@ -113,9 +177,7 @@  namespace
       }
       return string("Unknown error code");
 #else
-      // XXX locale issues: how does one get or set loc.
-      // _GLIBCXX_HAVE_STRERROR_L, strerror_l(i, cloc)
-      return string(strerror(i));
+      return strerror_string(i);
 #endif
     }