Snap for 10447354 from 2f003fc209a6552d59640a281830a73e75754bb7 to mainline-networking-release
Change-Id: I858fb2561368ba94e26bcb4d6f24854b4938e015
diff --git a/Android.bp b/Android.bp
index ef56692..d2cdadd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -283,7 +283,11 @@
name: "libbase_benchmark",
defaults: ["libbase_cflags_defaults"],
- srcs: ["format_benchmark.cpp"],
+ srcs: [
+ "file_benchmark.cpp",
+ "format_benchmark.cpp",
+ "function_ref_benchmark.cpp",
+ ],
shared_libs: ["libbase"],
compile_multilib: "both",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a3b2bfc..3724fa5 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "libbase_test"
}
],
- "hwasan-postsubmit": [
+ "hwasan-presubmit": [
{
"name": "libbase_test"
}
diff --git a/abi_compatibility.cpp b/abi_compatibility.cpp
index 06a7801..b5e4741 100644
--- a/abi_compatibility.cpp
+++ b/abi_compatibility.cpp
@@ -46,7 +46,11 @@
}
bool WriteStringToFd(const std::string& content, int fd) {
- return WriteStringToFd(content, borrowed_fd(fd));
+ return WriteStringToFd(std::string_view(content), borrowed_fd(fd));
+}
+
+bool WriteStringToFd(const std::string& content, borrowed_fd fd) {
+ return WriteStringToFd(std::string_view(content), fd);
}
bool ReadFully(int fd, void* data, size_t byte_count) {
@@ -61,6 +65,14 @@
return WriteFully(borrowed_fd(fd), data, byte_count);
}
+std::string Basename(const std::string& path) {
+ return Basename(std::string_view(path));
+}
+
+std::string Dirname(const std::string& path) {
+ return Dirname(std::string_view(path));
+}
+
#if defined(__LP64__)
#define MAPPEDFILE_FROMFD _ZN10MappedFile6FromFdEilmi
#else
diff --git a/file.cpp b/file.cpp
index a580dcc..69ee69f 100644
--- a/file.cpp
+++ b/file.cpp
@@ -23,6 +23,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -225,7 +226,7 @@
content->reserve(sb.st_size);
}
- char buf[BUFSIZ] __attribute__((__uninitialized__));
+ char buf[4096] __attribute__((__uninitialized__));
ssize_t n;
while ((n = TEMP_FAILURE_RETRY(read(fd.get(), &buf[0], sizeof(buf)))) > 0) {
content->append(buf, n);
@@ -244,7 +245,7 @@
return ReadFdToString(fd, content);
}
-bool WriteStringToFd(const std::string& content, borrowed_fd fd) {
+bool WriteStringToFd(std::string_view content, borrowed_fd fd) {
const char* p = content.data();
size_t left = content.size();
while (left > 0) {
@@ -336,6 +337,21 @@
}
return static_cast<ssize_t>(bytes_read);
}
+
+static ssize_t pwrite(borrowed_fd fd, const void* data, size_t byte_count, off64_t offset) {
+ DWORD bytes_written;
+ OVERLAPPED overlapped;
+ memset(&overlapped, 0, sizeof(OVERLAPPED));
+ overlapped.Offset = static_cast<DWORD>(offset);
+ overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
+ if (!WriteFile(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), data,
+ static_cast<DWORD>(byte_count), &bytes_written, &overlapped)) {
+ // In case someone tries to read errno (since this is masquerading as a POSIX call)
+ errno = EIO;
+ return -1;
+ }
+ return static_cast<ssize_t>(bytes_written);
+}
#endif
bool ReadFullyAtOffset(borrowed_fd fd, void* data, size_t byte_count, off64_t offset) {
@@ -350,6 +366,19 @@
return true;
}
+bool WriteFullyAtOffset(borrowed_fd fd, const void* data, size_t byte_count, off64_t offset) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
+ size_t remaining = byte_count;
+ while (remaining > 0) {
+ ssize_t n = TEMP_FAILURE_RETRY(pwrite(fd.get(), p, remaining, offset));
+ if (n == -1) return false;
+ p += n;
+ remaining -= n;
+ offset += n;
+ }
+ return true;
+}
+
bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count) {
const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
size_t remaining = byte_count;
@@ -469,7 +498,7 @@
}
#if defined(_WIN32)
-std::string Basename(const std::string& path) {
+std::string Basename(std::string_view path) {
// TODO: how much of this is actually necessary for mingw?
// Copy path because basename may modify the string passed in.
@@ -495,21 +524,21 @@
}
#else
// Copied from bionic so that Basename() below can be portable and thread-safe.
-static int __basename_r(const char* path, char* buffer, size_t buffer_size) {
+static int _basename_r(const char* path, size_t path_size, char* buffer, size_t buffer_size) {
const char* startp = nullptr;
const char* endp = nullptr;
int len;
int result;
// Empty or NULL string gets treated as ".".
- if (path == nullptr || *path == '\0') {
+ if (path == nullptr || path_size == 0) {
startp = ".";
len = 1;
goto Exit;
}
// Strip trailing slashes.
- endp = path + strlen(path) - 1;
+ endp = path + path_size - 1;
while (endp > path && *endp == '/') {
endp--;
}
@@ -546,25 +575,26 @@
}
return result;
}
-std::string Basename(const std::string& path) {
- char buf[PATH_MAX];
- __basename_r(path.c_str(), buf, sizeof(buf));
- return buf;
+std::string Basename(std::string_view path) {
+ char buf[PATH_MAX] __attribute__((__uninitialized__));
+ const auto size = _basename_r(path.data(), path.size(), buf, sizeof(buf));
+ return size > 0 ? std::string(buf, size) : std::string();
}
#endif
-std::string Dirname(const std::string& path) {
+#if defined(_WIN32)
+std::string Dirname(std::string_view path) {
+ // TODO: how much of this is actually necessary for mingw?
+
// Copy path because dirname may modify the string passed in.
std::string result(path);
-#if !defined(__BIONIC__)
// Use lock because dirname() may write to a process global and return a
// pointer to that. Note that this locking strategy only works if all other
// callers to dirname in the process also grab this same lock, but its
// better than nothing. Bionic's dirname returns a thread-local buffer.
static std::mutex& dirname_lock = *new std::mutex();
std::lock_guard<std::mutex> lock(dirname_lock);
-#endif
// Note that if std::string uses copy-on-write strings, &str[0] will cause
// the copy to be made, so there is no chance of us accidentally writing to
@@ -577,6 +607,72 @@
return result;
}
+#else
+// Copied from bionic so that Dirname() below can be portable and thread-safe.
+static int _dirname_r(const char* path, size_t path_size, char* buffer, size_t buffer_size) {
+ const char* endp = nullptr;
+ int len;
+ int result;
+
+ // Empty or NULL string gets treated as ".".
+ if (path == nullptr || path_size == 0) {
+ path = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ // Strip trailing slashes.
+ endp = path + path_size - 1;
+ while (endp > path && *endp == '/') {
+ endp--;
+ }
+
+ // Find the start of the dir.
+ while (endp > path && *endp != '/') {
+ endp--;
+ }
+
+ // Either the dir is "/" or there are no slashes.
+ if (endp == path) {
+ path = (*endp == '/') ? "/" : ".";
+ len = 1;
+ goto Exit;
+ }
+
+ do {
+ endp--;
+ } while (endp > path && *endp == '/');
+
+ len = endp - path + 1;
+
+ Exit:
+ result = len;
+ if (len + 1 > MAXPATHLEN) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if (buffer == nullptr) {
+ return result;
+ }
+
+ if (len > static_cast<int>(buffer_size) - 1) {
+ len = buffer_size - 1;
+ result = -1;
+ errno = ERANGE;
+ }
+
+ if (len >= 0) {
+ memcpy(buffer, path, len);
+ buffer[len] = 0;
+ }
+ return result;
+}
+std::string Dirname(std::string_view path) {
+ char buf[PATH_MAX] __attribute__((__uninitialized__));
+ const auto size = _dirname_r(path.data(), path.size(), buf, sizeof(buf));
+ return size > 0 ? std::string(buf, size) : std::string();
+}
+#endif
} // namespace base
} // namespace android
diff --git a/file_benchmark.cpp b/file_benchmark.cpp
new file mode 100644
index 0000000..86252ce
--- /dev/null
+++ b/file_benchmark.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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 <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <benchmark/benchmark.h>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+
+static void BenchmarkReadFdToString(benchmark::State& state) {
+ android::base::unique_fd fd(memfd_create("memfile", 0));
+ CHECK(fd.get() > 0);
+ CHECK_EQ(ftruncate(fd, state.range(0)), 0);
+ for (auto _ : state) {
+ CHECK_EQ(lseek(fd, 0, SEEK_SET), 0);
+ std::string str;
+ benchmark::DoNotOptimize(android::base::ReadFdToString(fd, &str));
+ }
+ state.SetBytesProcessed(state.iterations() * state.range(0));
+}
+
+BENCHMARK_RANGE(BenchmarkReadFdToString, 0, 1024 * 1024);
diff --git a/file_test.cpp b/file_test.cpp
index c739664..2474f7b 100644
--- a/file_test.cpp
+++ b/file_test.cpp
@@ -176,7 +176,7 @@
}
#endif
-TEST(file, WriteStringToFd) {
+TEST(file, WriteStringToFd_StringLiteral) {
TemporaryFile tf;
ASSERT_NE(tf.fd, -1) << tf.path;
ASSERT_TRUE(android::base::WriteStringToFd("abc", tf.fd));
@@ -188,6 +188,32 @@
EXPECT_EQ("abc", s);
}
+TEST(file, WriteStringToFd_String) {
+ std::string testStr = "def";
+ TemporaryFile tf;
+ ASSERT_NE(tf.fd, -1) << tf.path;
+ ASSERT_TRUE(android::base::WriteStringToFd(testStr, tf.fd));
+
+ ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
+
+ std::string s;
+ ASSERT_TRUE(android::base::ReadFdToString(tf.fd, &s)) << strerror(errno);
+ EXPECT_EQ(testStr, s);
+}
+
+TEST(file, WriteStringToFd_StringView) {
+ std::string_view testStrView = "ghi";
+ TemporaryFile tf;
+ ASSERT_NE(tf.fd, -1) << tf.path;
+ ASSERT_TRUE(android::base::WriteStringToFd(testStrView, tf.fd));
+
+ ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
+
+ std::string s;
+ ASSERT_TRUE(android::base::ReadFdToString(tf.fd, &s)) << strerror(errno);
+ EXPECT_EQ(testStrView, s);
+}
+
TEST(file, WriteFully) {
TemporaryFile tf;
ASSERT_NE(tf.fd, -1) << tf.path;
@@ -350,6 +376,15 @@
EXPECT_EQ("/system/bin", android::base::Dirname("/system/bin/sh"));
EXPECT_EQ(".", android::base::Dirname("sh"));
EXPECT_EQ("/system/bin", android::base::Dirname("/system/bin/sh/"));
+
+ // Since we've copy & pasted bionic's implementation, copy & paste the tests.
+ EXPECT_EQ(".", android::base::Dirname(""));
+ EXPECT_EQ("/usr", android::base::Dirname("/usr/lib"));
+ EXPECT_EQ("/", android::base::Dirname("/usr/"));
+ EXPECT_EQ(".", android::base::Dirname("usr"));
+ EXPECT_EQ(".", android::base::Dirname("."));
+ EXPECT_EQ(".", android::base::Dirname(".."));
+ EXPECT_EQ("/", android::base::Dirname("/"));
}
TEST(file, ReadFileToString_capacity) {
diff --git a/function_ref_benchmark.cpp b/function_ref_benchmark.cpp
new file mode 100644
index 0000000..404043e
--- /dev/null
+++ b/function_ref_benchmark.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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 "android-base/function_ref.h"
+
+#include <benchmark/benchmark.h>
+
+#include <functional>
+#include <utility>
+
+#include <time.h>
+
+using android::base::function_ref;
+
+template <class Callable, class... Args>
+[[clang::noinline]] auto call(Callable&& c, Args&&... args) {
+ return c(std::forward<Args>(args)...);
+}
+
+[[clang::noinline]] static int testFunc(int, const char*, char) {
+ return time(nullptr);
+}
+
+using Func = decltype(testFunc);
+
+static void BenchmarkFuncRaw(benchmark::State& state) {
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call(testFunc, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkFuncRaw);
+
+static void BenchmarkFuncPtr(benchmark::State& state) {
+ auto ptr = &testFunc;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call(ptr, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkFuncPtr);
+
+static void BenchmarkStdFunction(benchmark::State& state) {
+ std::function<Func> f(testFunc);
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call(f, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkStdFunction);
+
+static void BenchmarkFunctionRef(benchmark::State& state) {
+ function_ref<Func> f(testFunc);
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call(f, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkFunctionRef);
+
+namespace {
+struct BigFunc {
+ char big[128];
+ [[clang::noinline]] int operator()(int, const char*, char) const { return time(nullptr); }
+};
+
+static BigFunc bigFunc;
+} // namespace
+
+static void BenchmarkBigRaw(benchmark::State& state) {
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call(bigFunc, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkBigRaw);
+
+static void BenchmarkBigStdFunction(benchmark::State& state) {
+ std::function<Func> f(bigFunc);
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call(f, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkBigStdFunction);
+
+static void BenchmarkBigFunctionRef(benchmark::State& state) {
+ function_ref<Func> f(bigFunc);
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call(f, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkBigFunctionRef);
+
+static void BenchmarkMakeFunctionRef(benchmark::State& state) {
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call<function_ref<Func>>(bigFunc, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkMakeFunctionRef);
+
+static void BenchmarkMakeStdFunction(benchmark::State& state) {
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(call<std::function<Func>>(bigFunc, 1, "1", '1'));
+ }
+}
+BENCHMARK(BenchmarkMakeStdFunction);
diff --git a/include/android-base/file.h b/include/android-base/file.h
index c622562..b11b305 100644
--- a/include/android-base/file.h
+++ b/include/android-base/file.h
@@ -84,7 +84,7 @@
bool WriteStringToFile(const std::string& content, const std::string& path,
bool follow_symlinks = false);
-bool WriteStringToFd(const std::string& content, borrowed_fd fd);
+bool WriteStringToFd(std::string_view content, borrowed_fd fd);
#if !defined(_WIN32)
bool WriteStringToFile(const std::string& content, const std::string& path,
@@ -105,6 +105,7 @@
bool ReadFullyAtOffset(borrowed_fd fd, void* data, size_t byte_count, off64_t offset);
bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count);
+bool WriteFullyAtOffset(borrowed_fd fd, const void* data, size_t byte_count, off64_t offset);
bool RemoveFileIfExists(const std::string& path, std::string* err = nullptr);
@@ -118,8 +119,8 @@
// Like the regular basename and dirname, but thread-safe on all
// platforms and capable of correctly handling exotic Windows paths.
-std::string Basename(const std::string& path);
-std::string Dirname(const std::string& path);
+std::string Basename(std::string_view path);
+std::string Dirname(std::string_view path);
} // namespace base
} // namespace android
diff --git a/include/android-base/function_ref.h b/include/android-base/function_ref.h
index 2de14e3..42594cc 100644
--- a/include/android-base/function_ref.h
+++ b/include/android-base/function_ref.h
@@ -87,40 +87,47 @@
constexpr function_ref(const function_ref& other) noexcept = default;
constexpr function_ref& operator=(const function_ref&) noexcept = default;
- template <class Callable, class = std::enable_if_t<
- std::is_invocable_r<Ret, Callable, Args...>::value &&
- !std::is_same_v<function_ref, std::remove_reference_t<Callable>>>>
- function_ref(Callable&& c) noexcept
- : mTypeErasedFunction([](const function_ref* self, Args... args) -> Ret {
- // Generate a lambda that remembers the type of the passed
- // |Callable|.
- return (*reinterpret_cast<std::remove_reference_t<Callable>*>(self->mCallable))(
- std::forward<Args>(args)...);
- }),
- mCallable(reinterpret_cast<intptr_t>(&c)) {}
+ using RawFunc = Ret(Args...);
+
+ function_ref(RawFunc* funcptr) noexcept { *this = funcptr; }
template <class Callable, class = std::enable_if_t<
- std::is_invocable_r<Ret, Callable, Args...>::value &&
+ std::is_invocable_r_v<Ret, Callable, Args...> &&
+ !std::is_same_v<function_ref, std::remove_reference_t<Callable>>>>
+ function_ref(Callable&& c) noexcept {
+ *this = std::forward<Callable>(c);
+ }
+
+ function_ref& operator=(RawFunc* funcptr) noexcept {
+ mTypeErasedFunction = [](uintptr_t funcptr, Args... args) -> Ret {
+ return (reinterpret_cast<RawFunc*>(funcptr))(std::forward<Args>(args)...);
+ };
+ mCallable = reinterpret_cast<uintptr_t>(funcptr);
+ return *this;
+ }
+
+ template <class Callable, class = std::enable_if_t<
+ std::is_invocable_r_v<Ret, Callable, Args...> &&
!std::is_same_v<function_ref, std::remove_reference_t<Callable>>>>
function_ref& operator=(Callable&& c) noexcept {
- mTypeErasedFunction = [](const function_ref* self, Args... args) -> Ret {
+ mTypeErasedFunction = [](uintptr_t callable, Args... args) -> Ret {
// Generate a lambda that remembers the type of the passed
// |Callable|.
- return (*reinterpret_cast<std::remove_reference_t<Callable>*>(self->mCallable))(
+ return (*reinterpret_cast<std::remove_reference_t<Callable>*>(callable))(
std::forward<Args>(args)...);
};
- mCallable = reinterpret_cast<intptr_t>(&c);
+ mCallable = reinterpret_cast<uintptr_t>(&c);
return *this;
}
Ret operator()(Args... args) const {
- return mTypeErasedFunction(this, std::forward<Args>(args)...);
+ return mTypeErasedFunction(mCallable, std::forward<Args>(args)...);
}
private:
- using TypeErasedFunc = Ret(const function_ref*, Args...);
+ using TypeErasedFunc = Ret(uintptr_t, Args...);
TypeErasedFunc* mTypeErasedFunction;
- intptr_t mCallable;
+ uintptr_t mCallable;
};
} // namespace android::base
diff --git a/include/android-base/macros.h b/include/android-base/macros.h
index 546b2ec..f141f34 100644
--- a/include/android-base/macros.h
+++ b/include/android-base/macros.h
@@ -141,6 +141,8 @@
#define ABI_STRING "arm64"
#elif defined(__i386__)
#define ABI_STRING "x86"
+#elif defined(__riscv)
+#define ABI_STRING "riscv64"
#elif defined(__x86_64__)
#define ABI_STRING "x86_64"
#endif
diff --git a/include/android-base/parseint.h b/include/android-base/parseint.h
index be8b97b..c76d625 100644
--- a/include/android-base/parseint.h
+++ b/include/android-base/parseint.h
@@ -44,6 +44,9 @@
return false;
}
+ // This is never out of bounds. If string is zero-sized, s[0] == '\0'
+ // so the second condition is not checked. If string is "0",
+ // s[1] will compare against the '\0'.
int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10;
errno = 0;
char* end;
diff --git a/include/android-base/result.h b/include/android-base/result.h
index 9715a8c..3c325f2 100644
--- a/include/android-base/result.h
+++ b/include/android-base/result.h
@@ -369,8 +369,9 @@
};
#ifdef __cpp_concepts
-template<class U>
-concept Trivial = std::is_same_v<U, U>;
+template <class U>
+// Define a concept which **any** type matches to
+concept Universal = std::is_same_v<U, U>;
#endif
} // namespace impl
@@ -389,7 +390,14 @@
static bool IsOk(const V& val) { return val.ok(); }
// Turns V into a success value
- static T Unwrap(V&& val) { return std::move(val.value()); }
+ static T Unwrap(V&& val) {
+ if constexpr (std::is_same_v<T, void>) {
+ assert(IsOk(val));
+ return;
+ } else {
+ return std::move(val.value());
+ }
+ }
// Consumes V when it's a fail value
static const OkOrFail<V> Fail(V&& v) {
@@ -403,11 +411,16 @@
return unexpected(std::move(this->error_));
}
#ifdef __cpp_concepts
- template <impl::Trivial U>
+ // The idea here is to match this template method to any type (not simply trivial types).
+ // The reason for including a constraint is to take advantage of the fact that a constrained
+ // method always has strictly lower precedence than a non-constrained method in template
+ // specialization rules (thus avoiding ambiguity). So we use a universally matching constraint to
+ // mark this function as less preferable (but still accepting of all types).
+ template <impl::Universal U>
#else
template <typename U>
#endif
- operator const Result<U, E, include_message>() const && {
+ operator const Result<U, E, include_message>() const&& {
return unexpected(std::move(this->error_));
}
diff --git a/include/android-base/strings.h b/include/android-base/strings.h
index e794540..9557fad 100644
--- a/include/android-base/strings.h
+++ b/include/android-base/strings.h
@@ -16,9 +16,13 @@
#pragma once
+#include <ctype.h>
+
#include <sstream>
#include <string>
#include <string_view>
+#include <type_traits>
+#include <utility>
#include <vector>
namespace android {
@@ -46,8 +50,48 @@
// The empty string is not a valid delimiter list.
std::vector<std::string> Tokenize(const std::string& s, const std::string& delimiters);
-// Trims whitespace off both ends of the given string.
-std::string Trim(const std::string& s);
+namespace internal {
+template <typename>
+constexpr bool always_false_v = false;
+}
+
+template <typename T>
+std::string Trim(T&& t) {
+ std::string_view sv;
+ std::string s;
+ if constexpr (std::is_convertible_v<T, std::string_view>) {
+ sv = std::forward<T>(t);
+ } else if constexpr (std::is_convertible_v<T, std::string>) {
+ // The previous version of this function allowed for types which are implicitly convertible
+ // to std::string but not to std::string_view. For these types we go through std::string first
+ // here in order to retain source compatibility.
+ s = t;
+ sv = s;
+ } else {
+ static_assert(internal::always_false_v<T>,
+ "Implicit conversion to std::string or std::string_view not possible");
+ }
+
+ // Skip initial whitespace.
+ while (!sv.empty() && isspace(sv.front())) {
+ sv.remove_prefix(1);
+ }
+
+ // Skip terminating whitespace.
+ while (!sv.empty() && isspace(sv.back())) {
+ sv.remove_suffix(1);
+ }
+
+ return std::string(sv);
+}
+
+// We instantiate the common cases in strings.cpp.
+extern template std::string Trim(const char*&);
+extern template std::string Trim(const char*&&);
+extern template std::string Trim(const std::string&);
+extern template std::string Trim(const std::string&&);
+extern template std::string Trim(std::string_view&);
+extern template std::string Trim(std::string_view&&);
// Joins a container of things into a single string, using the given separator.
template <typename ContainerT, typename SeparatorT>
diff --git a/include/android-base/unique_fd.h b/include/android-base/unique_fd.h
index e929e4c..1ffe02f 100644
--- a/include/android-base/unique_fd.h
+++ b/include/android-base/unique_fd.h
@@ -32,7 +32,7 @@
#if defined(__BIONIC__)
#include <android/fdsan.h>
#endif
-#if !defined(_WIN32)
+#if !defined(_WIN32) && !defined(__TRUSTY__)
#include <sys/socket.h>
#endif
@@ -183,7 +183,7 @@
using unique_fd = unique_fd_impl<DefaultCloser>;
-#if !defined(_WIN32)
+#if !defined(_WIN32) && !defined(__TRUSTY__)
// Inline functions, so that they can be used header-only.
@@ -273,7 +273,7 @@
return dir;
}
-#endif // !defined(_WIN32)
+#endif // !defined(_WIN32) && !defined(__TRUSTY__)
// A wrapper type that can be implicitly constructed from either int or
// unique_fd. This supports cases where you don't actually own the file
diff --git a/result_test.cpp b/result_test.cpp
index 19b48c3..b2cd303 100644
--- a/result_test.cpp
+++ b/result_test.cpp
@@ -16,6 +16,7 @@
#include "android-base/result.h"
#include <utils/ErrorsMacros.h>
+#include "android-base/errors.h"
#include "errno.h"
#include <istream>
@@ -488,6 +489,17 @@
EXPECT_EQ(**result2, 3);
}
+TEST(result, void) {
+ using testing::Ok;
+
+ auto return_void = []() -> Result<void> {
+ OR_RETURN(Result<void>());
+ return {};
+ };
+
+ ASSERT_THAT(return_void(), Ok());
+}
+
struct ConstructorTracker {
static size_t constructor_called;
static size_t copy_constructor_called;
diff --git a/strings.cpp b/strings.cpp
index deb6e28..5ff2a52 100644
--- a/strings.cpp
+++ b/strings.cpp
@@ -69,40 +69,18 @@
return result;
}
+[[deprecated("Retained only for binary compatibility (symbol name)")]]
std::string Trim(const std::string& s) {
- std::string result;
-
- if (s.size() == 0) {
- return result;
- }
-
- size_t start_index = 0;
- size_t end_index = s.size() - 1;
-
- // Skip initial whitespace.
- while (start_index < s.size()) {
- if (!isspace(s[start_index])) {
- break;
- }
- start_index++;
- }
-
- // Skip terminating whitespace.
- while (end_index >= start_index) {
- if (!isspace(s[end_index])) {
- break;
- }
- end_index--;
- }
-
- // All spaces, no beef.
- if (end_index < start_index) {
- return "";
- }
- // Start_index is the first non-space, end_index is the last one.
- return s.substr(start_index, end_index - start_index + 1);
+ return Trim(std::string_view(s));
}
+template std::string Trim(const char*&);
+template std::string Trim(const char*&&);
+template std::string Trim(const std::string&);
+template std::string Trim(const std::string&&);
+template std::string Trim(std::string_view&);
+template std::string Trim(std::string_view&&);
+
// These cases are probably the norm, so we mark them extern in the header to
// aid compile time and binary size.
template std::string Join(const std::vector<std::string>&, char);
diff --git a/strings_test.cpp b/strings_test.cpp
index fb111b8..f92e39a 100644
--- a/strings_test.cpp
+++ b/strings_test.cpp
@@ -157,6 +157,22 @@
ASSERT_EQ("foo", android::base::Trim("\v\tfoo\n\f"));
}
+TEST(strings, trim_build_implicit_string_conversion) {
+ struct Foo {
+ operator std::string() { return " foo "; }
+ explicit operator std::string_view() { return " foo "; }
+ };
+ ASSERT_EQ("foo", android::base::Trim(Foo()));
+}
+
+TEST(strings, trim_build_implicit_string_view_conversion) {
+ struct Foo {
+ explicit operator std::string() { return " foo "; }
+ operator std::string_view() { return " foo "; }
+ };
+ ASSERT_EQ("foo", android::base::Trim(Foo()));
+}
+
TEST(strings, join_nothing) {
std::vector<std::string> list = {};
ASSERT_EQ("", android::base::Join(list, ','));