blob: b0c7f6d4a9ebc6e01733e5e41195d610d286087b [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*/
/**
* @addtogroup NdkBinder
* @{
*/
/**
* @file binder_to_string.h
* @brief Helper for parcelable.
*/
#pragma once
#include <codecvt>
#include <locale>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <type_traits>
#if __has_include(<utils/StrongPointer.h>)
#include <utils/StrongPointer.h>
#define HAS_STRONG_POINTER
#endif
#if __has_include(<utils/String16.h>)
#include <utils/String16.h>
#define HAS_STRING16
#endif
#if __has_include(<android/binder_ibinder.h>)
#include <android/binder_auto_utils.h>
#include <android/binder_interface_utils.h>
#include <android/binder_parcelable_utils.h>
#define HAS_NDK_INTERFACE
#endif
// TODO: some things include libbinder without having access to libbase. This is
// due to frameworks/native/include, which symlinks to libbinder headers, so even
// though we don't use it here, we detect a different header, so that we are more
// confident libbase will be included
#if __has_include(<binder/RpcSession.h>)
#include <binder/IBinder.h>
#include <binder/IInterface.h>
#define HAS_CPP_INTERFACE
#endif
namespace android {
namespace internal {
// ToString is a utility to generate string representation for various AIDL-supported types.
template <typename _T>
std::string ToString(const _T& t);
namespace details {
// Truthy if _T has toString() method.
template <typename _T>
class HasToStringMethod {
template <typename _U>
static auto _test(int) -> decltype(std::declval<_U>().toString(), std::true_type());
template <typename _U>
static std::false_type _test(...);
public:
enum { value = decltype(_test<_T>(0))::value };
};
// Truthy if _T has a overloaded toString(T)
template <typename _T>
class HasToStringFunction {
template <typename _U>
static auto _test(int) -> decltype(toString(std::declval<_U>()), std::true_type());
template <typename _U>
static std::false_type _test(...);
public:
enum { value = decltype(_test<_T>(0))::value };
};
template <typename T, template <typename...> typename U>
struct IsInstantiationOf : std::false_type {};
template <template <typename...> typename U, typename... Args>
struct IsInstantiationOf<U<Args...>, U> : std::true_type {};
// Truthy if _T is like a pointer: one of sp/optional/shared_ptr
template <typename _T>
class IsPointerLike {
template <typename _U>
static std::enable_if_t<
#ifdef HAS_STRONG_POINTER
IsInstantiationOf<_U, sp>::value || // for IBinder and interface types in the C++
// backend
#endif
IsInstantiationOf<_U, std::optional>::value || // for @nullable types in the
// C++/NDK backends
IsInstantiationOf<_U, std::unique_ptr>::value || // for @nullable(heap=true)
// in C++/NDK backends
IsInstantiationOf<_U, std::shared_ptr>::value, // for interface types in the
// NDK backends
std::true_type>
_test(int);
template <typename _U>
static std::false_type _test(...);
public:
enum { value = decltype(_test<_T>(0))::value };
};
// Truthy if _T is like a container
template <typename _T>
class IsIterable {
template <typename _U>
static auto _test(int)
-> decltype(begin(std::declval<_U>()), end(std::declval<_U>()), std::true_type());
template <typename _U>
static std::false_type _test(...);
public:
enum { value = decltype(_test<_T>(0))::value };
};
template <typename _T>
struct TypeDependentFalse {
enum { value = false };
};
} // namespace details
template <typename _T>
std::string ToString(const _T& t) {
if constexpr (std::is_same_v<bool, _T>) {
return t ? "true" : "false";
} else if constexpr (std::is_same_v<char16_t, _T>) {
// TODO(b/244494451): codecvt is deprecated in C++17 -- suppress the
// warnings. There's no replacement in the standard library yet.
_Pragma("clang diagnostic push")
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"");
return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>().to_bytes(t);
_Pragma("clang diagnostic pop");
} else if constexpr (std::is_arithmetic_v<_T>) {
return std::to_string(t);
} else if constexpr (std::is_same_v<std::string, _T>) {
return t;
#ifdef HAS_NDK_INTERFACE
} else if constexpr (std::is_same_v<::ndk::SpAIBinder, _T>) {
std::stringstream ss;
ss << "binder:" << std::hex << t.get();
return ss.str();
} else if constexpr (std::is_same_v<::ndk::ScopedFileDescriptor, _T>) {
return "fd:" + std::to_string(t.get());
} else if constexpr (std::is_base_of_v<::ndk::ICInterface, _T>) {
// TODO(b/266248339): this format is to make it easy to handle resolv_integration_test
// freezing the output format. We would like to print more info.
return "<interface>";
#if __ANDROID_API__ >= 31
} else if constexpr (std::is_same_v<::ndk::AParcelableHolder, _T>) {
return "AParcelableHolder";
#endif
#endif // HAS_NDK_INTERFACE
#ifdef HAS_CPP_INTERFACE
} else if constexpr (std::is_base_of_v<IInterface, _T>) {
std::stringstream ss;
ss << "interface:" << std::hex << &t;
return ss.str();
} else if constexpr (std::is_same_v<IBinder, _T>) {
std::stringstream ss;
ss << "binder:" << std::hex << &t;
return ss.str();
#endif
#ifdef HAS_STRING16
} else if constexpr (std::is_same_v<String16, _T>) {
std::stringstream out;
out << t;
return out.str();
#endif
} else if constexpr (details::IsPointerLike<_T>::value || std::is_pointer_v<_T>) {
if (!t) return "(null)";
std::stringstream out;
out << ToString(*t);
return out.str();
} else if constexpr (details::HasToStringMethod<_T>::value) {
return t.toString();
} else if constexpr (details::HasToStringFunction<_T>::value) {
return toString(t);
} else if constexpr (details::IsIterable<_T>::value) {
std::stringstream out;
bool first = true;
out << "[";
for (const auto& e : t) {
if (first) {
first = false;
} else {
out << ", ";
}
// Use explicit type parameter in case deref of iterator has different type
// e.g. vector<bool>
out << ToString<typename _T::value_type>(e);
}
out << "]";
return out.str();
} else {
static_assert(details::TypeDependentFalse<_T>::value, "no toString implemented, huh?");
}
}
} // namespace internal
} // namespace android
#ifdef HAS_STRONG_POINTER
#undef HAS_STRONG_POINTER
#endif
#ifdef HAS_STRING16
#undef HAS_STRING16
#endif
#ifdef HAS_NDK_INTERFACE
#undef HAS_NDK_INTERFACE
#endif
#ifdef HAS_CPP_INTERFACE
#undef HAS_CPP_INTERFACE
#endif
/** @} */