diff --git a/base/.gitignore b/base/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/base/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/base/build.gradle.kts b/base/build.gradle.kts new file mode 100644 index 000000000..1801fd353 --- /dev/null +++ b/base/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("ndksamples.android.library") +} + +android { + namespace = "com.android.ndk.samples.base" + + defaultConfig { + externalNativeBuild { + cmake { + arguments.add("-DANDROID_WEAK_API_DEFS=ON") + } + } + } + + externalNativeBuild { + cmake { + path = file("src/main/cpp/CMakeLists.txt") + } + } + + buildFeatures { + prefab = true + prefabPublishing = true + } + + prefab { + create("base") { + headers = "src/main/cpp/include" + } + } +} diff --git a/base/src/main/AndroidManifest.xml b/base/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a5918e68a --- /dev/null +++ b/base/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/base/src/main/cpp/CMakeLists.txt b/base/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..c98d205d5 --- /dev/null +++ b/base/src/main/cpp/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.22.1) +project(base LANGUAGES CXX) + +add_compile_options(-Wall -Werror -Wextra) + +add_library(base + STATIC + logging.cpp + stringprintf.cpp + strings.cpp +) + +target_include_directories(base + PUBLIC + include +) diff --git a/base/src/main/cpp/include/base/errno_restorer.h b/base/src/main/cpp/include/base/errno_restorer.h new file mode 100644 index 000000000..ceb7f88d0 --- /dev/null +++ b/base/src/main/cpp/include/base/errno_restorer.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include + +#include "base/macros.h" + +namespace android { +namespace base { + +class ErrnoRestorer { + public: + ErrnoRestorer() : saved_errno_(errno) {} + + ~ErrnoRestorer() { errno = saved_errno_; } + + // Allow this object to be used as part of && operation. + explicit operator bool() const { return true; } + + private: + const int saved_errno_; + + DISALLOW_COPY_AND_ASSIGN(ErrnoRestorer); +}; + +} // namespace base +} // namespace android diff --git a/base/src/main/cpp/include/base/logging.h b/base/src/main/cpp/include/base/logging.h new file mode 100644 index 000000000..51edbb557 --- /dev/null +++ b/base/src/main/cpp/include/base/logging.h @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2015 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. + */ + +#pragma once + +// +// Google-style C++ logging. +// + +// This header provides a C++ stream interface to logging. +// +// To log: +// +// LOG(INFO) << "Some text; " << some_value; +// +// Replace `INFO` with any severity from `enum LogSeverity`. +// Most devices filter out VERBOSE logs by default, run +// `adb shell setprop log.tag. V` to see them in adb logcat. +// +// To log the result of a failed function and include the string +// representation of `errno` at the end: +// +// PLOG(ERROR) << "Write failed"; +// +// The output will be something like `Write failed: I/O error`. +// Remember this as 'P' as in perror(3). +// +// To output your own types, simply implement operator<< as normal. +// +// By default, output goes to logcat on Android and stderr on the host. +// A process can use `SetLogger` to decide where all logging goes. +// Implementations are provided for logcat, stderr, and dmesg. +// +// By default, the process' name is used as the log tag. +// Code can choose a specific log tag by defining LOG_TAG +// before including this header. + +// This header also provides assertions: +// +// CHECK(must_be_true); +// CHECK_EQ(a, b) << z_is_interesting_too; + + +#include +#include +#include + +#include "base/errno_restorer.h" +#include "base/macros.h" + +// Note: DO NOT USE DIRECTLY. Use LOG_TAG instead. +#ifdef _LOG_TAG_INTERNAL +#error "_LOG_TAG_INTERNAL must not be defined" +#endif +#ifdef LOG_TAG +#define _LOG_TAG_INTERNAL LOG_TAG +#else +#define _LOG_TAG_INTERNAL nullptr +#endif + +namespace android { +namespace base { + +enum LogSeverity { + VERBOSE, + DEBUG, + INFO, + WARNING, + ERROR, + FATAL_WITHOUT_ABORT, // For loggability tests, this is considered identical to FATAL. + FATAL, +}; + +enum LogId { + DEFAULT, + MAIN, + SYSTEM, + RADIO, + CRASH, +}; + +using LogFunction = std::function; +using AbortFunction = std::function; + +// Loggers for use with InitLogging/SetLogger. + +LogFunction TeeLogger(LogFunction&& l1, LogFunction&& l2); + +void DefaultAborter(const char* abort_message); + +void SetDefaultTag(const std::string& tag); + +// The LogdLogger sends chunks of up to ~4000 bytes at a time to logd. It does not prevent other +// threads from writing to logd between sending each chunk, so other threads may interleave their +// messages. If preventing interleaving is required, then a custom logger that takes a lock before +// calling this logger should be provided. +class LogdLogger { + public: + explicit LogdLogger(LogId default_log_id = android::base::MAIN); + + void operator()(LogId, LogSeverity, const char* tag, const char* file, + unsigned int line, const char* message); + + private: + LogId default_log_id_; +}; + +// Configure logging based on ANDROID_LOG_TAGS environment variable. +// We need to parse a string that looks like +// +// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i +// +// The tag (or '*' for the global level) comes first, followed by a colon and a +// letter indicating the minimum priority level we're expected to log. This can +// be used to reveal or conceal logs with specific tags. +#ifdef __ANDROID__ +#define INIT_LOGGING_DEFAULT_LOGGER LogdLogger() +#else +#define INIT_LOGGING_DEFAULT_LOGGER StderrLogger +#endif +void InitLogging(char* argv[], + LogFunction&& logger = INIT_LOGGING_DEFAULT_LOGGER, + AbortFunction&& aborter = DefaultAborter); +#undef INIT_LOGGING_DEFAULT_LOGGER + +// Replace the current logger and return the old one. +LogFunction SetLogger(LogFunction&& logger); + +// Replace the current aborter and return the old one. +AbortFunction SetAborter(AbortFunction&& aborter); + +// A helper macro that produces an expression that accepts both a qualified name and an +// unqualified name for a LogSeverity, and returns a LogSeverity value. +// Note: DO NOT USE DIRECTLY. This is an implementation detail. +#define SEVERITY_LAMBDA(severity) ([&]() { \ + using ::android::base::VERBOSE; \ + using ::android::base::DEBUG; \ + using ::android::base::INFO; \ + using ::android::base::WARNING; \ + using ::android::base::ERROR; \ + using ::android::base::FATAL_WITHOUT_ABORT; \ + using ::android::base::FATAL; \ + return (severity); }()) + +#ifdef __clang_analyzer__ +// Clang's static analyzer does not see the conditional statement inside +// LogMessage's destructor that will abort on FATAL severity. +#define ABORT_AFTER_LOG_FATAL for (;; abort()) + +struct LogAbortAfterFullExpr { + ~LogAbortAfterFullExpr() __attribute__((noreturn)) { abort(); } + explicit operator bool() const { return false; } +}; +// Provides an expression that evaluates to the truthiness of `x`, automatically +// aborting if `c` is true. +#define ABORT_AFTER_LOG_EXPR_IF(c, x) (((c) && ::android::base::LogAbortAfterFullExpr()) || (x)) +// Note to the static analyzer that we always execute FATAL logs in practice. +#define MUST_LOG_MESSAGE(severity) (SEVERITY_LAMBDA(severity) == ::android::base::FATAL) +#else +#define ABORT_AFTER_LOG_FATAL +#define ABORT_AFTER_LOG_EXPR_IF(c, x) (x) +#define MUST_LOG_MESSAGE(severity) false +#endif +#define ABORT_AFTER_LOG_FATAL_EXPR(x) ABORT_AFTER_LOG_EXPR_IF(true, x) + +// Defines whether the given severity will be logged or silently swallowed. +#define WOULD_LOG(severity) \ + (UNLIKELY(::android::base::ShouldLog(SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL)) || \ + MUST_LOG_MESSAGE(severity)) + +// Get an ostream that can be used for logging at the given severity and to the default +// destination. +// +// Notes: +// 1) This will not check whether the severity is high enough. One should use WOULD_LOG to filter +// usage manually. +// 2) This does not save and restore errno. +#define LOG_STREAM(severity) \ + ::android::base::LogMessage(__FILE__, __LINE__, SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, \ + -1) \ + .stream() + +// Logs a message to logcat on Android otherwise to stderr. If the severity is +// FATAL it also causes an abort. For example: +// +// LOG(FATAL) << "We didn't expect to reach here"; +#define LOG(severity) LOGGING_PREAMBLE(severity) && LOG_STREAM(severity) + +// Checks if we want to log something, and sets up appropriate RAII objects if +// so. +// Note: DO NOT USE DIRECTLY. This is an implementation detail. +#define LOGGING_PREAMBLE(severity) \ + (WOULD_LOG(severity) && \ + ABORT_AFTER_LOG_EXPR_IF((SEVERITY_LAMBDA(severity)) == ::android::base::FATAL, true) && \ + ::android::base::ErrnoRestorer()) + +// A variant of LOG that also logs the current errno value. To be used when +// library calls fail. +#define PLOG(severity) \ + LOGGING_PREAMBLE(severity) && \ + ::android::base::LogMessage(__FILE__, __LINE__, SEVERITY_LAMBDA(severity), \ + _LOG_TAG_INTERNAL, errno) \ + .stream() + +// Marker that code is yet to be implemented. +#define UNIMPLEMENTED(level) \ + LOG(level) << __PRETTY_FUNCTION__ << " unimplemented " + +// Check whether condition x holds and LOG(FATAL) if not. The value of the +// expression x is only evaluated once. Extra logging can be appended using << +// after. For example: +// +// CHECK(false == true) results in a log message of +// "Check failed: false == true". +#define CHECK(x) \ + LIKELY((x)) || ABORT_AFTER_LOG_FATAL_EXPR(false) || \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, _LOG_TAG_INTERNAL, \ + -1) \ + .stream() \ + << "Check failed: " #x << " " + +// clang-format off +// Helper for CHECK_xx(x,y) macros. +#define CHECK_OP(LHS, RHS, OP) \ + for (auto _values = ::android::base::MakeEagerEvaluator(LHS, RHS); \ + UNLIKELY(!(_values.lhs.v OP _values.rhs.v)); \ + /* empty */) \ + ABORT_AFTER_LOG_FATAL \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, _LOG_TAG_INTERNAL, -1) \ + .stream() \ + << "Check failed: " << #LHS << " " << #OP << " " << #RHS << " (" #LHS "=" \ + << ::android::base::LogNullGuard::Guard(_values.lhs.v) \ + << ", " #RHS "=" \ + << ::android::base::LogNullGuard::Guard(_values.rhs.v) \ + << ") " +// clang-format on + +// Check whether a condition holds between x and y, LOG(FATAL) if not. The value +// of the expressions x and y is evaluated once. Extra logging can be appended +// using << after. For example: +// +// CHECK_NE(0 == 1, false) results in +// "Check failed: false != false (0==1=false, false=false) ". +#define CHECK_EQ(x, y) CHECK_OP(x, y, == ) +#define CHECK_NE(x, y) CHECK_OP(x, y, != ) +#define CHECK_LE(x, y) CHECK_OP(x, y, <= ) +#define CHECK_LT(x, y) CHECK_OP(x, y, < ) +#define CHECK_GE(x, y) CHECK_OP(x, y, >= ) +#define CHECK_GT(x, y) CHECK_OP(x, y, > ) + +// clang-format off +// Helper for CHECK_STRxx(s1,s2) macros. +#define CHECK_STROP(s1, s2, sense) \ + while (UNLIKELY((strcmp(s1, s2) == 0) != (sense))) \ + ABORT_AFTER_LOG_FATAL \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, \ + _LOG_TAG_INTERNAL, -1) \ + .stream() \ + << "Check failed: " << "\"" << (s1) << "\"" \ + << ((sense) ? " == " : " != ") << "\"" << (s2) << "\"" +// clang-format on + +// Check for string (const char*) equality between s1 and s2, LOG(FATAL) if not. +#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true) +#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false) + +// Perform the pthread function call(args), LOG(FATAL) on error. +#define CHECK_PTHREAD_CALL(call, args, what) \ + do { \ + int rc = call args; \ + if (rc != 0) { \ + errno = rc; \ + ABORT_AFTER_LOG_FATAL \ + PLOG(FATAL) << #call << " failed for " << (what); \ + } \ + } while (false) + +// DCHECKs are debug variants of CHECKs only enabled in debug builds. Generally +// CHECK should be used unless profiling identifies a CHECK as being in +// performance critical code. +#if defined(NDEBUG) && !defined(__clang_analyzer__) +static constexpr bool kEnableDChecks = false; +#else +static constexpr bool kEnableDChecks = true; +#endif + +#define DCHECK(x) \ + if (::android::base::kEnableDChecks) CHECK(x) +#define DCHECK_EQ(x, y) \ + if (::android::base::kEnableDChecks) CHECK_EQ(x, y) +#define DCHECK_NE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_NE(x, y) +#define DCHECK_LE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_LE(x, y) +#define DCHECK_LT(x, y) \ + if (::android::base::kEnableDChecks) CHECK_LT(x, y) +#define DCHECK_GE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_GE(x, y) +#define DCHECK_GT(x, y) \ + if (::android::base::kEnableDChecks) CHECK_GT(x, y) +#define DCHECK_STREQ(s1, s2) \ + if (::android::base::kEnableDChecks) CHECK_STREQ(s1, s2) +#define DCHECK_STRNE(s1, s2) \ + if (::android::base::kEnableDChecks) CHECK_STRNE(s1, s2) + +namespace log_detail { + +// Temporary storage for a single eagerly evaluated check expression operand. +template struct Storage { + template explicit constexpr Storage(U&& u) : v(std::forward(u)) {} + explicit Storage(const Storage& t) = delete; + explicit Storage(Storage&& t) = delete; + T v; +}; + +// Partial specialization for smart pointers to avoid copying. +template struct Storage> { + explicit constexpr Storage(const std::unique_ptr& ptr) : v(ptr.get()) {} + const T* v; +}; +template struct Storage> { + explicit constexpr Storage(const std::shared_ptr& ptr) : v(ptr.get()) {} + const T* v; +}; + +// Type trait that checks if a type is a (potentially const) char pointer. +template struct IsCharPointer { + using Pointee = std::remove_cv_t>; + static constexpr bool value = std::is_pointer_v && + (std::is_same_v || std::is_same_v || + std::is_same_v); +}; + +// Counterpart to Storage that depends on both operands. This is used to prevent +// char pointers being treated as strings in the log output - they might point +// to buffers of unprintable binary data. +template struct StorageTypes { + static constexpr bool voidptr = IsCharPointer::value && IsCharPointer::value; + using LHSType = std::conditional_t; + using RHSType = std::conditional_t; +}; + +// Temporary class created to evaluate the LHS and RHS, used with +// MakeEagerEvaluator to infer the types of LHS and RHS. +template +struct EagerEvaluator { + template constexpr EagerEvaluator(A&& l, B&& r) + : lhs(std::forward(l)), rhs(std::forward(r)) {} + const Storage::LHSType> lhs; + const Storage::RHSType> rhs; +}; + +} // namespace log_detail + +// Converts std::nullptr_t and null char pointers to the string "null" +// when writing the failure message. +template struct LogNullGuard { + static const T& Guard(const T& v) { return v; } +}; +template <> struct LogNullGuard { + static const char* Guard(const std::nullptr_t&) { return "(null)"; } +}; +template <> struct LogNullGuard { + static const char* Guard(const char* v) { return v ? v : "(null)"; } +}; +template <> struct LogNullGuard { + static const char* Guard(const char* v) { return v ? v : "(null)"; } +}; + +// Helper function for CHECK_xx. +template +constexpr auto MakeEagerEvaluator(LHS&& lhs, RHS&& rhs) { + return log_detail::EagerEvaluator, std::decay_t>( + std::forward(lhs), std::forward(rhs)); +} + +// Data for the log message, not stored in LogMessage to avoid increasing the +// stack size. +class LogMessageData; + +// A LogMessage is a temporarily scoped object used by LOG and the unlikely part +// of a CHECK. The destructor will abort if the severity is FATAL. +class LogMessage { + public: + // LogId has been deprecated, but this constructor must exist for prebuilts. + LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity, const char* tag, + int error); + LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag, int error); + + ~LogMessage(); + + // Returns the stream associated with the message, the LogMessage performs + // output when it goes out of scope. + std::ostream& stream(); + + // The routine that performs the actual logging. + static void LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag, + const char* msg); + + private: + const std::unique_ptr data_; + + DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// Get the minimum severity level for logging. +LogSeverity GetMinimumLogSeverity(); + +// Set the minimum severity level for logging, returning the old severity. +LogSeverity SetMinimumLogSeverity(LogSeverity new_severity); + +// Return whether or not a log message with the associated tag should be logged. +bool ShouldLog(LogSeverity severity, const char* tag); + +// Allows to temporarily change the minimum severity level for logging. +class ScopedLogSeverity { + public: + explicit ScopedLogSeverity(LogSeverity level); + ~ScopedLogSeverity(); + + private: + LogSeverity old_; +}; + +} // namespace base +} // namespace android + +namespace std { // NOLINT(cert-dcl58-cpp) + +// Emit a warning of ostream<< with std::string*. The intention was most likely to print *string. +// +// Note: for this to work, we need to have this in a namespace. +// Note: using a pragma because "-Wgcc-compat" (included in "-Weverything") complains about +// diagnose_if. +// Note: to print the pointer, use "<< static_cast(string_pointer)" instead. +// Note: a not-recommended alternative is to let Clang ignore the warning by adding +// -Wno-user-defined-warnings to CPPFLAGS. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" +#define OSTREAM_STRING_POINTER_USAGE_WARNING \ + __attribute__((diagnose_if(true, "Unexpected logging of string pointer", "warning"))) +inline OSTREAM_STRING_POINTER_USAGE_WARNING +std::ostream& operator<<(std::ostream& stream, const std::string* string_pointer) { + return stream << static_cast(string_pointer); +} +#pragma clang diagnostic pop + +} // namespace std diff --git a/base/src/main/cpp/include/base/macros.h b/base/src/main/cpp/include/base/macros.h new file mode 100644 index 000000000..b9ee402f2 --- /dev/null +++ b/base/src/main/cpp/include/base/macros.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015 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. + */ + +#pragma once + +#include // for size_t +#include // for TEMP_FAILURE_RETRY + +#include + +// bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't. +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) \ + ({ \ + decltype(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ + }) +#endif + +// A macro to disallow the copy constructor and operator= functions +// This must be placed in the private: declarations for a class. +// +// For disallowing only assign or copy, delete the relevant operator or +// constructor, for example: +// void operator=(const TypeName&) = delete; +// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken +// semantically, one should either use disallow both or neither. Try to +// avoid these in new code. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char(&ArraySizeHelper(T(&array)[N]))[N]; // NOLINT(readability/casting) + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +#define SIZEOF_MEMBER(t, f) sizeof(std::declval().f) + +// Changing this definition will cause you a lot of pain. A majority of +// vendor code defines LIKELY and UNLIKELY this way, and includes +// this header through an indirect path. +#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) +#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) + +#define WARN_UNUSED __attribute__((warn_unused_result)) + +// A deprecated function to call to create a false use of the parameter, for +// example: +// int foo(int x) { UNUSED(x); return 10; } +// to avoid compiler warnings. Going forward we prefer ATTRIBUTE_UNUSED. +template +void UNUSED(const T&...) { +} + +// An attribute to place on a parameter to a function, for example: +// int foo(int x ATTRIBUTE_UNUSED) { return 10; } +// to avoid compiler warnings. +#define ATTRIBUTE_UNUSED __attribute__((__unused__)) + +// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through +// between switch labels: +// switch (x) { +// case 40: +// case 41: +// if (truth_is_out_there) { +// ++x; +// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in +// // comments. +// } else { +// return x; +// } +// case 42: +// ... +// +// As shown in the example above, the FALLTHROUGH_INTENDED macro should be +// followed by a semicolon. It is designed to mimic control-flow statements +// like 'break;', so it can be placed in most places where 'break;' can, but +// only if there are no statements on the execution path between it and the +// next switch label. +// +// When compiled with clang, the FALLTHROUGH_INTENDED macro is expanded to +// [[clang::fallthrough]] attribute, which is analysed when performing switch +// labels fall-through diagnostic ('-Wimplicit-fallthrough'). See clang +// documentation on language extensions for details: +// http://clang.llvm.org/docs/LanguageExtensions.html#clang__fallthrough +// +// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no +// effect on diagnostics. +// +// In either case this macro has no effect on runtime behavior and performance +// of code. +#ifndef FALLTHROUGH_INTENDED +#define FALLTHROUGH_INTENDED [[fallthrough]] // NOLINT +#endif + +// Current ABI string +#if defined(__arm__) +#define ABI_STRING "arm" +#elif defined(__aarch64__) +#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/base/src/main/cpp/logging.cpp b/base/src/main/cpp/logging.cpp new file mode 100644 index 000000000..b930dece0 --- /dev/null +++ b/base/src/main/cpp/logging.cpp @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2015 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. + */ + +#if defined(_WIN32) +#include +#endif + +#include "base/logging.h" + +#include +#include +#include +#include + +// For getprogname(3) or program_invocation_short_name. +#if defined(__ANDROID__) || defined(__APPLE__) +#include +#elif defined(__GLIBC__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __ANDROID__ +#include +#else +#include +#include +#endif + +#include "strings.h" +#include "logging_splitters.h" + +namespace android { +namespace base { + +// BSD-based systems like Android/macOS have getprogname(). Others need us to provide one. +#if !defined(__APPLE__) && !defined(__BIONIC__) +static const char* getprogname() { +#ifdef _WIN32 + static bool first = true; + static char progname[MAX_PATH] = {}; + + if (first) { + snprintf(progname, sizeof(progname), "%s", + android::base::Basename(android::base::GetExecutablePath()).c_str()); + first = false; + } + + return progname; +#else + return program_invocation_short_name; +#endif +} +#endif + +static const char* GetFileBasename(const char* file) { + // We can't use basename(3) even on Unix because the Mac doesn't + // have a non-modifying basename. + const char* last_slash = strrchr(file, '/'); + if (last_slash != nullptr) { + return last_slash + 1; + } +#if defined(_WIN32) + const char* last_backslash = strrchr(file, '\\'); + if (last_backslash != nullptr) { + return last_backslash + 1; + } +#endif + return file; +} + +static LogId log_id_tToLogId(int32_t buffer_id) { + switch (buffer_id) { + case LOG_ID_MAIN: + return MAIN; + case LOG_ID_SYSTEM: + return SYSTEM; + case LOG_ID_RADIO: + return RADIO; + case LOG_ID_CRASH: + return CRASH; + case LOG_ID_DEFAULT: + default: + return DEFAULT; + } +} + +static int32_t LogIdTolog_id_t(LogId log_id) { + switch (log_id) { + case MAIN: + return LOG_ID_MAIN; + case SYSTEM: + return LOG_ID_SYSTEM; + case RADIO: + return LOG_ID_RADIO; + case CRASH: + return LOG_ID_CRASH; + case DEFAULT: + default: + return LOG_ID_DEFAULT; + } +} + +static LogSeverity PriorityToLogSeverity(int priority) { + switch (priority) { + case ANDROID_LOG_DEFAULT: + return INFO; + case ANDROID_LOG_VERBOSE: + return VERBOSE; + case ANDROID_LOG_DEBUG: + return DEBUG; + case ANDROID_LOG_INFO: + return INFO; + case ANDROID_LOG_WARN: + return WARNING; + case ANDROID_LOG_ERROR: + return ERROR; + case ANDROID_LOG_FATAL: + return FATAL; + default: + return FATAL; + } +} + +static int32_t LogSeverityToPriority(LogSeverity severity) { + switch (severity) { + case VERBOSE: + return ANDROID_LOG_VERBOSE; + case DEBUG: + return ANDROID_LOG_DEBUG; + case INFO: + return ANDROID_LOG_INFO; + case WARNING: + return ANDROID_LOG_WARN; + case ERROR: + return ANDROID_LOG_ERROR; + case FATAL_WITHOUT_ABORT: + case FATAL: + default: + return ANDROID_LOG_FATAL; + } +} + +static LogFunction& Logger() { +#ifdef __ANDROID__ + static auto& logger = *new LogFunction(LogdLogger()); +#else + static auto& logger = *new LogFunction(StderrLogger); +#endif + return logger; +} + +static AbortFunction& Aborter() { + static auto& aborter = *new AbortFunction(DefaultAborter); + return aborter; +} + +// Only used for Q fallback. +static std::recursive_mutex& TagLock() { + static auto& tag_lock = *new std::recursive_mutex(); + return tag_lock; +} +// Only used for Q fallback. +static std::string* gDefaultTag; + +void SetDefaultTag(const std::string& tag) { + if (__builtin_available(android 30, *)) { + __android_log_set_default_tag(tag.c_str()); + } else { + std::lock_guard lock(TagLock()); + if (gDefaultTag != nullptr) { + delete gDefaultTag; + gDefaultTag = nullptr; + } + if (!tag.empty()) { + gDefaultTag = new std::string(tag); + } + } +} + +static bool gInitialized = false; + +// Only used for Q fallback. +static LogSeverity gMinimumLogSeverity = INFO; + +LogFunction TeeLogger(LogFunction&& l1, LogFunction&& l2) { + return [l1 = std::move(l1), l2 = std::move(l2)](LogId id, LogSeverity severity, const char* tag, + const char* file, unsigned int line, + const char* message) { + l1(id, severity, tag, file, line, message); + l2(id, severity, tag, file, line, message); + }; +} + +void DefaultAborter(const char* abort_message) { +#ifdef __ANDROID__ + android_set_abort_message(abort_message); +#else + UNUSED(abort_message); +#endif + abort(); +} + +static void LogdLogChunk(LogId id, LogSeverity severity, const char* tag, const char* message) { + int32_t lg_id = LogIdTolog_id_t(id); + int32_t priority = LogSeverityToPriority(severity); + + if (__builtin_available(android 30, *)) { + __android_log_message log_message = {sizeof(__android_log_message), lg_id, priority, tag, + static_cast(nullptr), 0, message}; + __android_log_logd_logger(&log_message); + } else { + __android_log_buf_print(lg_id, priority, tag, "%s", message); + } +} + +LogdLogger::LogdLogger(LogId default_log_id) : default_log_id_(default_log_id) {} + +void LogdLogger::operator()(LogId id, LogSeverity severity, const char* tag, const char* file, + unsigned int line, const char* message) { + if (id == DEFAULT) { + id = default_log_id_; + } + + SplitByLogdChunks(id, severity, tag, file, line, message, LogdLogChunk); +} + +void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) { + SetLogger(std::forward(logger)); + SetAborter(std::forward(aborter)); + + if (gInitialized) { + return; + } + + gInitialized = true; + + // Stash the command line for later use. We can use /proc/self/cmdline on + // Linux to recover this, but we don't have that luxury on the Mac/Windows, + // and there are a couple of argv[0] variants that are commonly used. + if (argv != nullptr) { + SetDefaultTag(basename(argv[0])); + } + + const char* tags = getenv("ANDROID_LOG_TAGS"); + if (tags == nullptr) { + return; + } + + std::vector specs = Split(tags, " "); + for (size_t i = 0; i < specs.size(); ++i) { + // "tag-pattern:[vdiwefs]" + std::string spec(specs[i]); + if (spec.size() == 3 && StartsWith(spec, "*:")) { + switch (spec[2]) { + case 'v': + SetMinimumLogSeverity(VERBOSE); + continue; + case 'd': + SetMinimumLogSeverity(DEBUG); + continue; + case 'i': + SetMinimumLogSeverity(INFO); + continue; + case 'w': + SetMinimumLogSeverity(WARNING); + continue; + case 'e': + SetMinimumLogSeverity(ERROR); + continue; + case 'f': + SetMinimumLogSeverity(FATAL_WITHOUT_ABORT); + continue; + // liblog will even suppress FATAL if you say 's' for silent, but fatal should + // never be suppressed. + case 's': + SetMinimumLogSeverity(FATAL_WITHOUT_ABORT); + continue; + } + } + LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags + << ")"; + } +} + +LogFunction SetLogger(LogFunction&& logger) { + LogFunction old_logger = std::move(Logger()); + Logger() = std::move(logger); + + if (__builtin_available(android 30, *)) { + __android_log_set_logger([](const struct __android_log_message* log_message) { + auto log_id = log_id_tToLogId(log_message->buffer_id); + auto severity = PriorityToLogSeverity(log_message->priority); + + Logger()(log_id, severity, log_message->tag, log_message->file, log_message->line, + log_message->message); + }); + } + return old_logger; +} + +AbortFunction SetAborter(AbortFunction&& aborter) { + AbortFunction old_aborter = std::move(Aborter()); + Aborter() = std::move(aborter); + + if (__builtin_available(android 30, *)) { + __android_log_set_aborter([](const char* abort_message) { Aborter()(abort_message); }); + } + return old_aborter; +} + +// This indirection greatly reduces the stack impact of having lots of +// checks/logging in a function. +class LogMessageData { + public: + LogMessageData(const char* file, unsigned int line, LogSeverity severity, const char* tag, + int error) + : file_(GetFileBasename(file)), + line_number_(line), + severity_(severity), + tag_(tag), + error_(error) {} + + const char* GetFile() const { + return file_; + } + + unsigned int GetLineNumber() const { + return line_number_; + } + + LogSeverity GetSeverity() const { + return severity_; + } + + const char* GetTag() const { return tag_; } + + int GetError() const { + return error_; + } + + std::ostream& GetBuffer() { + return buffer_; + } + + std::string ToString() const { + return buffer_.str(); + } + + private: + std::ostringstream buffer_; + const char* const file_; + const unsigned int line_number_; + const LogSeverity severity_; + const char* const tag_; + const int error_; + + DISALLOW_COPY_AND_ASSIGN(LogMessageData); +}; + +LogMessage::LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity, + const char* tag, int error) + : LogMessage(file, line, severity, tag, error) {} + +LogMessage::LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag, + int error) + : data_(new LogMessageData(file, line, severity, tag, error)) {} + +LogMessage::~LogMessage() { + // Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM. + if (!WOULD_LOG(data_->GetSeverity())) { + return; + } + + // Finish constructing the message. + if (data_->GetError() != -1) { + data_->GetBuffer() << ": " << strerror(data_->GetError()); + } + std::string msg(data_->ToString()); + + if (data_->GetSeverity() == FATAL) { +#ifdef __ANDROID__ + // Set the bionic abort message early to avoid liblog doing it + // with the individual lines, so that we get the whole message. + android_set_abort_message(msg.c_str()); +#endif + } + + LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), data_->GetTag(), + msg.c_str()); + + // Abort if necessary. + if (data_->GetSeverity() == FATAL) { + if (__builtin_available(android 30, *)) { + __android_log_call_aborter(msg.c_str()); + } else { + Aborter()(msg.c_str()); + } + } +} + +std::ostream& LogMessage::stream() { + return data_->GetBuffer(); +} + +void LogMessage::LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag, + const char* message) { + int32_t priority = LogSeverityToPriority(severity); + if (__builtin_available(android 30, *)) { + __android_log_message log_message = { + sizeof(__android_log_message), LOG_ID_DEFAULT, priority, tag, file, line, message}; + __android_log_write_log_message(&log_message); + } else { + if (tag == nullptr) { + std::lock_guard lock(TagLock()); + if (gDefaultTag == nullptr) { + gDefaultTag = new std::string(getprogname()); + } + + Logger()(DEFAULT, severity, gDefaultTag->c_str(), file, line, message); + } else { + Logger()(DEFAULT, severity, tag, file, line, message); + } + } +} + +LogSeverity GetMinimumLogSeverity() { + if (__builtin_available(android 30, *)) { + return PriorityToLogSeverity(__android_log_get_minimum_priority()); + } else { + return gMinimumLogSeverity; + } +} + +bool ShouldLog(LogSeverity severity, const char* tag) { + // Even though we're not using the R liblog functions in this function, if we're running on Q, + // we need to fall back to using gMinimumLogSeverity, since __android_log_is_loggable() will not + // take into consideration the value from SetMinimumLogSeverity(). + if (__builtin_available(android 30, *)) { + int32_t priority = LogSeverityToPriority(severity); + return __android_log_is_loggable(priority, tag, ANDROID_LOG_INFO); + } else { + return severity >= gMinimumLogSeverity; + } +} + +LogSeverity SetMinimumLogSeverity(LogSeverity new_severity) { + if (__builtin_available(android 30, *)) { + int32_t priority = LogSeverityToPriority(new_severity); + return PriorityToLogSeverity(__android_log_set_minimum_priority(priority)); + } else { + LogSeverity old_severity = gMinimumLogSeverity; + gMinimumLogSeverity = new_severity; + return old_severity; + } +} + +ScopedLogSeverity::ScopedLogSeverity(LogSeverity new_severity) { + old_ = SetMinimumLogSeverity(new_severity); +} + +ScopedLogSeverity::~ScopedLogSeverity() { + SetMinimumLogSeverity(old_); +} + +} // namespace base +} // namespace android diff --git a/base/src/main/cpp/logging_splitters.h b/base/src/main/cpp/logging_splitters.h new file mode 100644 index 000000000..01bbd8c9b --- /dev/null +++ b/base/src/main/cpp/logging_splitters.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include +#include +#include + +#include "stringprintf.h" + +#define LOGGER_ENTRY_MAX_PAYLOAD 4068 // This constant is not in the NDK. + +namespace android { +namespace base { + +// This splits the message up line by line, by calling log_function with a pointer to the start of +// each line and the size up to the newline character. It sends size = -1 for the final line. +template +static void SplitByLines(const char* msg, const F& log_function, Args&&... args) { + const char* newline = strchr(msg, '\n'); + while (newline != nullptr) { + log_function(msg, newline - msg, args...); + msg = newline + 1; + newline = strchr(msg, '\n'); + } + + log_function(msg, -1, args...); +} + +// This splits the message up into chunks that logs can process delimited by new lines. It calls +// log_function with the exact null terminated message that should be sent to logd. +// Note, despite the loops and snprintf's, if severity is not fatal and there are no new lines, +// this function simply calls log_function with msg without any extra overhead. +template +static void SplitByLogdChunks(LogId log_id, LogSeverity severity, const char* tag, const char* file, + unsigned int line, const char* msg, const F& log_function) { + // The maximum size of a payload, after the log header that logd will accept is + // LOGGER_ENTRY_MAX_PAYLOAD, so subtract the other elements in the payload to find the size of + // the string that we can log in each pass. + // The protocol is documented in liblog/README.protocol.md. + // Specifically we subtract a byte for the priority, the length of the tag + its null terminator, + // and an additional byte for the null terminator on the payload. We subtract an additional 32 + // bytes for slack, similar to java/android/util/Log.java. + ptrdiff_t max_size = LOGGER_ENTRY_MAX_PAYLOAD - strlen(tag) - 35; + if (max_size <= 0) { + abort(); + } + // If we're logging a fatal message, we'll append the file and line numbers. + bool add_file = file != nullptr && (severity == FATAL || severity == FATAL_WITHOUT_ABORT); + + std::string file_header; + if (add_file) { + file_header = StringPrintf("%s:%u] ", file, line); + } + int file_header_size = file_header.size(); + + __attribute__((uninitialized)) char logd_chunk[max_size + 1]; + ptrdiff_t chunk_position = 0; + + auto call_log_function = [&]() { + log_function(log_id, severity, tag, logd_chunk); + chunk_position = 0; + }; + + auto write_to_logd_chunk = [&](const char* message, int length) { + int size_written = 0; + const char* new_line = chunk_position > 0 ? "\n" : ""; + if (add_file) { + size_written = snprintf(logd_chunk + chunk_position, sizeof(logd_chunk) - chunk_position, + "%s%s%.*s", new_line, file_header.c_str(), length, message); + } else { + size_written = snprintf(logd_chunk + chunk_position, sizeof(logd_chunk) - chunk_position, + "%s%.*s", new_line, length, message); + } + + // This should never fail, if it does and we set size_written to 0, which will skip this line + // and move to the next one. + if (size_written < 0) { + size_written = 0; + } + chunk_position += size_written; + }; + + const char* newline = strchr(msg, '\n'); + while (newline != nullptr) { + // If we have data in the buffer and this next line doesn't fit, write the buffer. + if (chunk_position != 0 && chunk_position + (newline - msg) + 1 + file_header_size > max_size) { + call_log_function(); + } + + // Otherwise, either the next line fits or we have any empty buffer and too large of a line to + // ever fit, in both cases, we add it to the buffer and continue. + write_to_logd_chunk(msg, newline - msg); + + msg = newline + 1; + newline = strchr(msg, '\n'); + } + + // If we have left over data in the buffer and we can fit the rest of msg, add it to the buffer + // then write the buffer. + if (chunk_position != 0 && + chunk_position + static_cast(strlen(msg)) + 1 + file_header_size <= max_size) { + write_to_logd_chunk(msg, -1); + call_log_function(); + } else { + // If the buffer is not empty and we can't fit the rest of msg into it, write its contents. + if (chunk_position != 0) { + call_log_function(); + } + // Then write the rest of the msg. + if (add_file) { + snprintf(logd_chunk, sizeof(logd_chunk), "%s%s", file_header.c_str(), msg); + log_function(log_id, severity, tag, logd_chunk); + } else { + log_function(log_id, severity, tag, msg); + } + } +} + +} // namespace base +} // namespace android diff --git a/base/src/main/cpp/stringprintf.cpp b/base/src/main/cpp/stringprintf.cpp new file mode 100644 index 000000000..84b7b1372 --- /dev/null +++ b/base/src/main/cpp/stringprintf.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011 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 "stringprintf.h" + +#include + +#include + +namespace android { +namespace base { + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + // First try with a small fixed size buffer + char space[1024] __attribute__((__uninitialized__)); + + // It's possible for methods that use a va_list to invalidate + // the data in it upon use. The fix is to make a copy + // of the structure before using it and use that copy instead. + va_list backup_ap; + va_copy(backup_ap, ap); + int result = vsnprintf(space, sizeof(space), format, backup_ap); + va_end(backup_ap); + + if (result < static_cast(sizeof(space))) { + if (result >= 0) { + // Normal case -- everything fit. + dst->append(space, result); + return; + } + + if (result < 0) { + // Just an error. + return; + } + } + + // Increase the buffer size to the size requested by vsnprintf, + // plus one for the closing \0. + int length = result + 1; + char* buf = new char[length]; + + // Restore the va_list before we use it again + va_copy(backup_ap, ap); + result = vsnprintf(buf, length, format, backup_ap); + va_end(backup_ap); + + if (result >= 0 && result < length) { + // It fit + dst->append(buf, result); + } + delete[] buf; +} + +std::string StringPrintf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string result; + StringAppendV(&result, fmt, ap); + va_end(ap); + return result; +} + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +} // namespace base +} // namespace android diff --git a/base/src/main/cpp/stringprintf.h b/base/src/main/cpp/stringprintf.h new file mode 100644 index 000000000..93c56afd7 --- /dev/null +++ b/base/src/main/cpp/stringprintf.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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. + */ + +#pragma once + +#include +#include + +namespace android { +namespace base { + +// These printf-like functions are implemented in terms of vsnprintf, so they +// use the same attribute for compile-time format string checking. + +// Returns a string corresponding to printf-like formatting of the arguments. +std::string StringPrintf(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendF(std::string* dst, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendV(std::string* dst, const char* format, va_list ap) + __attribute__((__format__(__printf__, 2, 0))); + +} // namespace base +} // namespace android diff --git a/base/src/main/cpp/strings.cpp b/base/src/main/cpp/strings.cpp new file mode 100644 index 000000000..80bac65b4 --- /dev/null +++ b/base/src/main/cpp/strings.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2015 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 "strings.h" + +#include +#include + +#include +#include + +#include "stringprintf.h" + +namespace android { +namespace base { + +#define CHECK_NE(a, b) \ + if ((a) == (b)) abort(); + +std::vector Split(const std::string& s, + const std::string& delimiters) { + CHECK_NE(delimiters.size(), 0U); + + std::vector result; + + size_t base = 0; + size_t found; + while (true) { + found = s.find_first_of(delimiters, base); + result.push_back(s.substr(base, found - base)); + if (found == s.npos) break; + base = found + 1; + } + + return result; +} + +std::vector Tokenize(const std::string& s, const std::string& delimiters) { + CHECK_NE(delimiters.size(), 0U); + + std::vector result; + size_t end = 0; + + while (true) { + size_t base = s.find_first_not_of(delimiters, end); + if (base == s.npos) { + break; + } + end = s.find_first_of(delimiters, base); + result.push_back(s.substr(base, end - base)); + } + return result; +} + +[[deprecated("Retained only for binary compatibility (symbol name)")]] +std::string Trim(const std::string& s) { + 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&, char); +template std::string Join(const std::vector&, char); +template std::string Join(const std::vector&, const std::string&); +template std::string Join(const std::vector&, const std::string&); + +bool StartsWith(std::string_view s, std::string_view prefix) { + return s.substr(0, prefix.size()) == prefix; +} + +bool StartsWith(std::string_view s, char prefix) { + return !s.empty() && s.front() == prefix; +} + +bool StartsWithIgnoreCase(std::string_view s, std::string_view prefix) { + return s.size() >= prefix.size() && strncasecmp(s.data(), prefix.data(), prefix.size()) == 0; +} + +bool EndsWith(std::string_view s, std::string_view suffix) { + return s.size() >= suffix.size() && s.substr(s.size() - suffix.size(), suffix.size()) == suffix; +} + +bool EndsWith(std::string_view s, char suffix) { + return !s.empty() && s.back() == suffix; +} + +bool EndsWithIgnoreCase(std::string_view s, std::string_view suffix) { + return s.size() >= suffix.size() && + strncasecmp(s.data() + (s.size() - suffix.size()), suffix.data(), suffix.size()) == 0; +} + +bool EqualsIgnoreCase(std::string_view lhs, std::string_view rhs) { + return lhs.size() == rhs.size() && strncasecmp(lhs.data(), rhs.data(), lhs.size()) == 0; +} + +std::string StringReplace(std::string_view s, std::string_view from, std::string_view to, + bool all) { + if (from.empty()) return std::string(s); + + std::string result; + std::string_view::size_type start_pos = 0; + do { + std::string_view::size_type pos = s.find(from, start_pos); + if (pos == std::string_view::npos) break; + + result.append(s.data() + start_pos, pos - start_pos); + result.append(to.data(), to.size()); + + start_pos = pos + from.size(); + } while (all); + result.append(s.data() + start_pos, s.size() - start_pos); + return result; +} + +std::string ErrnoNumberAsString(int errnum) { + char buf[100]; + buf[0] = '\0'; + int strerror_err = strerror_r(errnum, buf, sizeof(buf)); + if (strerror_err < 0) { + return StringPrintf("Failed to convert errno %d to string: %d", errnum, strerror_err); + } + return buf; +} + +} // namespace base +} // namespace android diff --git a/base/src/main/cpp/strings.h b/base/src/main/cpp/strings.h new file mode 100644 index 000000000..9557fad61 --- /dev/null +++ b/base/src/main/cpp/strings.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 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. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace base { + +// Splits a string into a vector of strings. +// +// The string is split at each occurrence of a character in delimiters. +// +// The empty string is not a valid delimiter list. +std::vector Split(const std::string& s, + const std::string& delimiters); + +// Splits a string into a vector of string tokens. +// +// The string is split at each occurrence of a character in delimiters. +// Coalesce runs of delimiter bytes and ignore delimiter bytes at the start or +// end of string. In other words, return only nonempty string tokens. +// Use when you don't care about recovering the original string with Join(). +// +// Example: +// Tokenize(" foo bar ", " ") => {"foo", "bar"} +// Join(Tokenize(" foo bar", " "), " ") => "foo bar" +// +// The empty string is not a valid delimiter list. +std::vector Tokenize(const std::string& s, const std::string& delimiters); + +namespace internal { +template +constexpr bool always_false_v = false; +} + +template +std::string Trim(T&& t) { + std::string_view sv; + std::string s; + if constexpr (std::is_convertible_v) { + sv = std::forward(t); + } else if constexpr (std::is_convertible_v) { + // 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, + "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 +std::string Join(const ContainerT& things, SeparatorT separator) { + if (things.empty()) { + return ""; + } + + std::ostringstream result; + result << *things.begin(); + for (auto it = std::next(things.begin()); it != things.end(); ++it) { + result << separator << *it; + } + return result.str(); +} + +// We instantiate the common cases in strings.cpp. +extern template std::string Join(const std::vector&, char); +extern template std::string Join(const std::vector&, char); +extern template std::string Join(const std::vector&, const std::string&); +extern template std::string Join(const std::vector&, const std::string&); + +// Tests whether 's' starts with 'prefix'. +bool StartsWith(std::string_view s, std::string_view prefix); +bool StartsWith(std::string_view s, char prefix); +bool StartsWithIgnoreCase(std::string_view s, std::string_view prefix); + +// Tests whether 's' ends with 'suffix'. +bool EndsWith(std::string_view s, std::string_view suffix); +bool EndsWith(std::string_view s, char suffix); +bool EndsWithIgnoreCase(std::string_view s, std::string_view suffix); + +// Tests whether 'lhs' equals 'rhs', ignoring case. +bool EqualsIgnoreCase(std::string_view lhs, std::string_view rhs); + +// Removes `prefix` from the start of the given string and returns true (if +// it was present), false otherwise. +inline bool ConsumePrefix(std::string_view* s, std::string_view prefix) { + if (!StartsWith(*s, prefix)) return false; + s->remove_prefix(prefix.size()); + return true; +} + +// Removes `suffix` from the end of the given string and returns true (if +// it was present), false otherwise. +inline bool ConsumeSuffix(std::string_view* s, std::string_view suffix) { + if (!EndsWith(*s, suffix)) return false; + s->remove_suffix(suffix.size()); + return true; +} + +// Replaces `from` with `to` in `s`, once if `all == false`, or as many times as +// there are matches if `all == true`. +[[nodiscard]] std::string StringReplace(std::string_view s, std::string_view from, + std::string_view to, bool all); + +// Converts an errno number to its error message string. +std::string ErrnoNumberAsString(int errnum); + +} // namespace base +} // namespace android diff --git a/hello-gl2/app/build.gradle b/hello-gl2/app/build.gradle index a6a6f7a8b..456f045a2 100644 --- a/hello-gl2/app/build.gradle +++ b/hello-gl2/app/build.gradle @@ -20,4 +20,12 @@ android { path 'src/main/cpp/CMakeLists.txt' } } + + buildFeatures { + prefab true + } +} + +dependencies { + implementation(project(":base")) } diff --git a/hello-gl2/app/src/main/cpp/CMakeLists.txt b/hello-gl2/app/src/main/cpp/CMakeLists.txt index c023ce61f..ce4885a30 100644 --- a/hello-gl2/app/src/main/cpp/CMakeLists.txt +++ b/hello-gl2/app/src/main/cpp/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.22.1) +find_package(base REQUIRED CONFIG) + # now build app's shared lib set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") @@ -11,5 +13,7 @@ target_link_libraries(gl2jni android log EGL - GLESv2) + GLESv2 + base::base +) diff --git a/hello-gl2/app/src/main/cpp/gl_code.cpp b/hello-gl2/app/src/main/cpp/gl_code.cpp index 28a929d10..ad55bfdbb 100644 --- a/hello-gl2/app/src/main/cpp/gl_code.cpp +++ b/hello-gl2/app/src/main/cpp/gl_code.cpp @@ -24,13 +24,15 @@ #include #include +#include "base/logging.h" + #define LOG_TAG "libgl2jni" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) static void printGLString(const char* name, GLenum s) { const char* v = (const char*)glGetString(s); - LOGI("GL %s = %s\n", name, v); + LOG(INFO) << "GL " << name << " = " << v; } static void checkGlError(const char* op) { diff --git a/settings.gradle b/settings.gradle index 1bac1cdee..f0d4b009a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ dependencyResolutionManagement { rootProject.name = "NDK Samples" include(":audio-echo:app") +include(":base") include(":bitmap-plasma:app") include(":camera:basic") include(":camera:texture-view")