diff options
Diffstat (limited to 'absl/base')
-rw-r--r-- | absl/base/BUILD.bazel | 45 | ||||
-rw-r--r-- | absl/base/CMakeLists.txt | 32 | ||||
-rw-r--r-- | absl/base/internal/errno_saver_test.cc | 3 | ||||
-rw-r--r-- | absl/base/internal/strerror.cc | 75 | ||||
-rw-r--r-- | absl/base/internal/strerror.h | 39 | ||||
-rw-r--r-- | absl/base/internal/strerror_benchmark.cc | 38 | ||||
-rw-r--r-- | absl/base/internal/strerror_test.cc | 86 |
7 files changed, 317 insertions, 1 deletions
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index bae79427ac4e..24dab79102f0 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -307,6 +307,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":errno_saver", + ":strerror", "@com_google_googletest//:gtest_main", ], ) @@ -451,6 +452,7 @@ cc_binary( testonly = 1, copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], visibility = ["//visibility:private"], deps = [ ":spinlock_benchmark_common", @@ -705,3 +707,46 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_library( + name = "strerror", + srcs = ["internal/strerror.cc"], + hdrs = ["internal/strerror.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + ":config", + ":core_headers", + ":errno_saver", + ], +) + +cc_test( + name = "strerror_test", + size = "small", + srcs = ["internal/strerror_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":strerror", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_binary( + name = "strerror_benchmark", + testonly = 1, + srcs = ["internal/strerror_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":strerror", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 14c52eabdfb3..4230d2e73ee1 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -326,6 +326,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::errno_saver + absl::strerror gmock gtest_main ) @@ -642,3 +643,34 @@ absl_cc_test( gmock gtest_main ) + +absl_cc_library( + NAME + strerror + SRCS + "internal/strerror.cc" + HDRS + "internal/strerror.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::errno_saver +) + +absl_cc_test( + NAME + strerror_test + SRCS + "internal/strerror_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strerror + absl::strings + gmock + gtest_main +) diff --git a/absl/base/internal/errno_saver_test.cc b/absl/base/internal/errno_saver_test.cc index b845e2dd1a0f..e9b742c588b0 100644 --- a/absl/base/internal/errno_saver_test.cc +++ b/absl/base/internal/errno_saver_test.cc @@ -18,6 +18,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/internal/strerror.h" namespace { using ::testing::Eq; @@ -26,7 +27,7 @@ struct ErrnoPrinter { int no; }; std::ostream &operator<<(std::ostream &os, ErrnoPrinter ep) { - return os << strerror(ep.no) << " [" << ep.no << "]"; + return os << absl::base_internal::StrError(ep.no) << " [" << ep.no << "]"; } bool operator==(ErrnoPrinter one, ErrnoPrinter two) { return one.no == two.no; } diff --git a/absl/base/internal/strerror.cc b/absl/base/internal/strerror.cc new file mode 100644 index 000000000000..af181513cde9 --- /dev/null +++ b/absl/base/internal/strerror.cc @@ -0,0 +1,75 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/base/internal/strerror.h" + +#include <cerrno> +#include <cstddef> +#include <cstdio> +#include <cstring> +#include <string> +#include <type_traits> + +#include "absl/base/attributes.h" +#include "absl/base/internal/errno_saver.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { +namespace { +const char* StrErrorAdaptor(int errnum, char* buf, size_t buflen) { +#if defined(_WIN32) + int rc = strerror_s(buf, buflen, errnum); + buf[buflen - 1] = '\0'; // guarantee NUL termination + if (rc == 0 && strncmp(buf, "Unknown error", buflen) == 0) *buf = '\0'; + return buf; +#else +#if defined(__GLIBC__) || defined(__APPLE__) + // Use the BSD sys_errlist API provided by GNU glibc and others to + // avoid any need to copy the message into the local buffer first. + if (0 <= errnum && errnum < sys_nerr) { + if (const char* p = sys_errlist[errnum]) { + return p; + } + } +#endif + // The type of `ret` is platform-specific; both of these branches must compile + // either way but only one will execute on any given platform: + auto ret = strerror_r(errnum, buf, buflen); + if (std::is_same<decltype(ret), int>::value) { + // XSI `strerror_r`; `ret` is `int`: + if (ret) *buf = '\0'; + return buf; + } else { + // GNU `strerror_r`; `ret` is `char *`: + return reinterpret_cast<const char*>(ret); + } +#endif +} +} // namespace + +std::string StrError(int errnum) { + absl::base_internal::ErrnoSaver errno_saver; + char buf[100]; + const char* str = StrErrorAdaptor(errnum, buf, sizeof buf); + if (*str == '\0') { + snprintf(buf, sizeof buf, "Unknown error %d", errnum); + str = buf; + } + return str; +} + +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/base/internal/strerror.h b/absl/base/internal/strerror.h new file mode 100644 index 000000000000..350097366eed --- /dev/null +++ b/absl/base/internal/strerror.h @@ -0,0 +1,39 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_BASE_INTERNAL_STRERROR_H_ +#define ABSL_BASE_INTERNAL_STRERROR_H_ + +#include <string> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { + +// A portable and thread-safe alternative to C89's `strerror`. +// +// The C89 specification of `strerror` is not suitable for use in a +// multi-threaded application as the returned string may be changed by calls to +// `strerror` from another thread. The many non-stdlib alternatives differ +// enough in their names, availability, and semantics to justify this wrapper +// around them. `errno` will not be modified by a call to `absl::StrError`. +std::string StrError(int errnum); + +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_STRERROR_H_ diff --git a/absl/base/internal/strerror_benchmark.cc b/absl/base/internal/strerror_benchmark.cc new file mode 100644 index 000000000000..d8ca86b95beb --- /dev/null +++ b/absl/base/internal/strerror_benchmark.cc @@ -0,0 +1,38 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cerrno> +#include <cstdio> +#include <string> + +#include "absl/base/internal/strerror.h" +#include "benchmark/benchmark.h" + +namespace { +#if defined(__GLIBC__) || defined(__APPLE__) +void BM_SysErrList(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::string(sys_errlist[ERANGE])); + } +} +BENCHMARK(BM_SysErrList); +#endif + +void BM_AbslStrError(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(absl::base_internal::StrError(ERANGE)); + } +} +BENCHMARK(BM_AbslStrError); +} // namespace diff --git a/absl/base/internal/strerror_test.cc b/absl/base/internal/strerror_test.cc new file mode 100644 index 000000000000..a53da97f92c9 --- /dev/null +++ b/absl/base/internal/strerror_test.cc @@ -0,0 +1,86 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/base/internal/strerror.h" + +#include <atomic> +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <string> +#include <thread> // NOLINT(build/c++11) +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/match.h" + +namespace { +using ::testing::AnyOf; +using ::testing::Eq; + +TEST(StrErrorTest, ValidErrorCode) { + errno = ERANGE; + EXPECT_THAT(absl::base_internal::StrError(EDOM), Eq(strerror(EDOM))); + EXPECT_THAT(errno, Eq(ERANGE)); +} + +TEST(StrErrorTest, InvalidErrorCode) { + errno = ERANGE; + EXPECT_THAT(absl::base_internal::StrError(-1), + AnyOf(Eq("No error information"), Eq("Unknown error -1"))); + EXPECT_THAT(errno, Eq(ERANGE)); +} + +TEST(StrErrorTest, MultipleThreads) { + // In this test, we will start up 2 threads and have each one call + // StrError 1000 times, each time with a different errnum. We + // expect that StrError(errnum) will return a string equal to the + // one returned by strerror(errnum), if the code is known. Since + // strerror is known to be thread-hostile, collect all the expected + // strings up front. + const int kNumCodes = 1000; + std::vector<std::string> expected_strings(kNumCodes); + for (int i = 0; i < kNumCodes; ++i) { + expected_strings[i] = strerror(i); + } + + std::atomic_int counter(0); + auto thread_fun = [&]() { + for (int i = 0; i < kNumCodes; ++i) { + ++counter; + errno = ERANGE; + const std::string value = absl::base_internal::StrError(i); + // Only the GNU implementation is guaranteed to provide the + // string "Unknown error nnn". POSIX doesn't say anything. + if (!absl::StartsWith(value, "Unknown error ")) { + EXPECT_THAT(absl::base_internal::StrError(i), Eq(expected_strings[i])); + } + EXPECT_THAT(errno, Eq(ERANGE)); + } + }; + + const int kNumThreads = 100; + std::vector<std::thread> threads; + for (int i = 0; i < kNumThreads; ++i) { + threads.push_back(std::thread(thread_fun)); + } + for (auto& thread : threads) { + thread.join(); + } + + EXPECT_THAT(counter, Eq(kNumThreads * kNumCodes)); +} + +} // namespace |