toString() for parcelable/union types for C++/NDK
Note that enum types already have toString(enum) function.
This is for the C++/NDK backends.
Bug: 134658111
Test: aidl_integration_test / aidl_unittests
Change-Id: I9ca2dcc1a508d9dac2b7dad8f735bbbfe3f67717
diff --git a/aidl_to_cpp_common.cpp b/aidl_to_cpp_common.cpp
index fac699a..58dafdb 100644
--- a/aidl_to_cpp_common.cpp
+++ b/aidl_to_cpp_common.cpp
@@ -32,6 +32,14 @@
namespace aidl {
namespace cpp {
+namespace {
+constexpr char kToStringHelper[] =
+ R"(template <typename _T, ::std::enable_if_t<::std::is_same_v<::std::string, decltype(std::declval<_T>().toString())>, int> = 0>
+static inline ::std::string _call_toString(const _T& _t) { return _t.toString(); }
+static inline ::std::string _call_toString(...) { return "{no toString() implemented}"; }
+)";
+}
+
string ClassName(const AidlDefinedType& defined_type, ClassNames type) {
string base_name = defined_type.GetName();
if (base_name.length() >= 2 && base_name[0] == 'I' && isupper(base_name[1])) {
@@ -94,6 +102,96 @@
return prefix + a.GetName();
}
+string ToString(const AidlTypeSpecifier& type, const string& expr);
+string ToStringNullable(const AidlTypeSpecifier& type, const string& expr);
+string ToStringNullableVector(const AidlTypeSpecifier& element_type, const string& expr);
+string ToStringVector(const AidlTypeSpecifier& element_type, const string& expr);
+string ToStringRaw(const AidlTypeSpecifier& type, const string& expr);
+
+string ToStringNullable(const AidlTypeSpecifier& type, const string& expr) {
+ if (AidlTypenames::IsPrimitiveTypename(type.GetName())) {
+ // we don't allow @nullable for primitives
+ return ToStringRaw(type, expr);
+ }
+ return "((" + expr + ") ? " + ToStringRaw(type, "*" + expr) + ": \"(null)\")";
+}
+
+string ToStringVector(const AidlTypeSpecifier& element_type, const string& expr) {
+ return "[&](){ std::ostringstream o; o << \"[\"; bool first = true; for (const auto& v: " + expr +
+ ") { (void)v; if (first) first = false; else o << \", \"; o << " +
+ ToStringRaw(element_type, "v") + "; }; o << \"]\"; return o.str(); }()";
+}
+
+string ToStringNullableVector(const AidlTypeSpecifier& element_type, const string& expr) {
+ return "[&](){ if (!(" + expr +
+ ")) return std::string(\"(null)\"); std::ostringstream o; o << \"[\"; bool first = true; "
+ "for (const auto& v: *(" +
+ expr + ")) { (void)v; if (first) first = false; else o << \", \"; o << " +
+ ToStringNullable(element_type, "v") + "; }; o << \"]\"; return o.str(); }()";
+}
+
+string ToStringRaw(const AidlTypeSpecifier& type, const string& expr) {
+ if (AidlTypenames::IsBuiltinTypename(type.GetName())) {
+ if (AidlTypenames::IsPrimitiveTypename(type.GetName())) {
+ if (type.GetName() == "boolean") {
+ return "(" + expr + "?\"true\":\"false\")";
+ }
+ if (type.GetName() == "char") {
+ return "std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>().to_bytes(" +
+ expr + ")";
+ }
+ return "std::to_string(" + expr + ")";
+ }
+ if (type.GetName() == "String") {
+ return "(std::ostringstream() << " + expr + ").str()";
+ }
+ // ""(empty string) for unsupported types
+ return "\"\"";
+ }
+
+ const AidlDefinedType* defined_type = type.GetDefinedType();
+ AIDL_FATAL_IF(defined_type == nullptr, type);
+
+ if (defined_type->AsInterface()) {
+ // ""(empty string) for unsupported types
+ return "\"\"";
+ }
+ if (defined_type->AsEnumDeclaration()) {
+ const auto ns = Join(defined_type->GetSplitPackage(), "::");
+ return ns + "::toString(" + expr + ")";
+ }
+ return "_call_toString(" + expr + ")";
+}
+
+string ToString(const AidlTypeSpecifier& type, const string& expr) {
+ static const std::set<string> kNotSupported = {"Map", "IBinder", "ParcelFileDescriptor",
+ "ParcelableHolder"};
+ if (kNotSupported.find(type.GetName()) != kNotSupported.end()) {
+ // ""(empty string) for unsupported types
+ return "\"\"";
+ }
+ if (type.IsArray() && type.IsNullable()) {
+ const auto& element_type = type.ArrayBase();
+ return ToStringNullableVector(element_type, expr);
+ }
+ if (type.GetName() == "List" && type.IsNullable()) {
+ const auto& element_type = *type.GetTypeParameters()[0];
+ return ToStringNullableVector(element_type, expr);
+ }
+ if (type.IsArray()) {
+ const auto& element_type = type.ArrayBase();
+ return ToStringVector(element_type, expr);
+ }
+ if (type.GetName() == "List") {
+ const auto& element_type = *type.GetTypeParameters()[0];
+ return ToStringVector(element_type, expr);
+ }
+ if (type.IsNullable()) {
+ return ToStringNullable(type, expr);
+ }
+ return ToStringRaw(type, expr);
+}
+
struct TypeInfo {
// name of the type in C++ output
std::string cpp_name;
@@ -423,6 +521,68 @@
out << "\n";
}
+// Output may look like:
+// inline std::string toString() const {
+// std::ostringstream os;
+// os << "MyData{";
+// os << "field1: " << field1;
+// os << ", field2: " << v.field2;
+// ...
+// os << "}";
+// return os.str();
+// }
+void GenerateToString(CodeWriter& out, const AidlStructuredParcelable& parcelable) {
+ out << kToStringHelper;
+ out << "inline std::string toString() const {\n";
+ out.Indent();
+ out << "std::ostringstream os;\n";
+ out << "os << \"" << parcelable.GetName() << "{\";\n";
+ bool first = true;
+ for (const auto& f : parcelable.GetFields()) {
+ if (first) {
+ out << "os << \"";
+ first = false;
+ } else {
+ out << "os << \", ";
+ }
+ out << f->GetName() << ": \" << " << ToString(f->GetType(), f->GetName()) << ";\n";
+ }
+ out << "os << \"}\";\n";
+ out << "return os.str();\n";
+ out.Dedent();
+ out << "}\n";
+}
+
+// Output may look like:
+// inline std::string toString() const {
+// std::ostringstream os;
+// os << "MyData{";
+// switch (v.getTag()) {
+// case MyData::field: os << "field: " << v.get<MyData::field>(); break;
+// ...
+// }
+// os << "}";
+// return os.str();
+// }
+void GenerateToString(CodeWriter& out, const AidlUnionDecl& parcelable) {
+ out << kToStringHelper;
+ out << "inline std::string toString() const {\n";
+ out.Indent();
+ out << "std::ostringstream os;\n";
+ out << "os << \"" + parcelable.GetName() + "{\";\n";
+ out << "switch (getTag()) {\n";
+ for (const auto& f : parcelable.GetFields()) {
+ const string tag = f->GetName();
+ out << "case " << tag << ": os << \"" << tag << ": \" << "
+ << ToString(f->GetType(), "get<" + tag + ">()") << "; break;\n";
+ }
+ out << "}\n";
+ out << "os << \"}\";\n";
+ out << "return os.str();\n";
+ out.Dedent();
+ out << "}\n";
+}
+
const vector<string> UnionWriter::headers{
"type_traits", // std::is_same_v
"utility", // std::mode/forward for value
diff --git a/aidl_to_cpp_common.h b/aidl_to_cpp_common.h
index 3c6996e..d11b067 100644
--- a/aidl_to_cpp_common.h
+++ b/aidl_to_cpp_common.h
@@ -77,6 +77,9 @@
void GenerateParcelableComparisonOperators(CodeWriter& out, const AidlParcelable& parcelable);
+void GenerateToString(CodeWriter& out, const AidlStructuredParcelable& parcelable);
+void GenerateToString(CodeWriter& out, const AidlUnionDecl& parcelable);
+
struct ParcelWriterContext {
string status_type;
string status_ok;
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 1bcde1c..df29b8c 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -2973,7 +2973,10 @@
#include <binder/Parcel.h>
#include <binder/ParcelFileDescriptor.h>
#include <binder/Status.h>
+#include <codecvt>
#include <cstdint>
+#include <locale>
+#include <sstream>
#include <type_traits>
#include <utility>
#include <variant>
@@ -3062,6 +3065,20 @@
static const std::string DESCIPTOR = "a.Foo";
return DESCIPTOR;
}
+ template <typename _T, ::std::enable_if_t<::std::is_same_v<::std::string, decltype(std::declval<_T>().toString())>, int> = 0>
+ static inline ::std::string _call_toString(const _T& _t) { return _t.toString(); }
+ static inline ::std::string _call_toString(...) { return "{no toString() implemented}"; }
+ inline std::string toString() const {
+ std::ostringstream os;
+ os << "Foo{";
+ switch (getTag()) {
+ case ns: os << "ns: " << [&](){ std::ostringstream o; o << "["; bool first = true; for (const auto& v: get<ns>()) { (void)v; if (first) first = false; else o << ", "; o << std::to_string(v); }; o << "]"; return o.str(); }(); break;
+ case e: os << "e: " << a::toString(get<e>()); break;
+ case pfd: os << "pfd: " << ""; break;
+ }
+ os << "}";
+ return os.str();
+ }
private:
std::variant<::std::vector<int32_t>, ::a::ByteEnum, ::android::os::ParcelFileDescriptor> _value;
}; // class Foo
@@ -3114,6 +3131,9 @@
const char kUnionExampleExpectedOutputNdkHeader[] = R"(#pragma once
#include <android/binder_interface_utils.h>
#include <android/binder_parcelable_utils.h>
+#include <codecvt>
+#include <locale>
+#include <sstream>
#include <type_traits>
#include <utility>
@@ -3191,6 +3211,20 @@
binder_status_t readFromParcel(const AParcel* _parcel);
binder_status_t writeToParcel(AParcel* _parcel) const;
static const ::ndk::parcelable_stability_t _aidl_stability = ::ndk::STABILITY_LOCAL;
+ template <typename _T, ::std::enable_if_t<::std::is_same_v<::std::string, decltype(std::declval<_T>().toString())>, int> = 0>
+ static inline ::std::string _call_toString(const _T& _t) { return _t.toString(); }
+ static inline ::std::string _call_toString(...) { return "{no toString() implemented}"; }
+ inline std::string toString() const {
+ std::ostringstream os;
+ os << "Foo{";
+ switch (getTag()) {
+ case ns: os << "ns: " << [&](){ std::ostringstream o; o << "["; bool first = true; for (const auto& v: get<ns>()) { (void)v; if (first) first = false; else o << ", "; o << std::to_string(v); }; o << "]"; return o.str(); }(); break;
+ case e: os << "e: " << a::toString(get<e>()); break;
+ case pfd: os << "pfd: " << ""; break;
+ }
+ os << "}";
+ return os.str();
+ }
private:
std::variant<std::vector<int32_t>, ::aidl::a::ByteEnum, ::ndk::ScopedFileDescriptor> _value;
};
diff --git a/generate_cpp.cpp b/generate_cpp.cpp
index 616d472..770c3c7 100644
--- a/generate_cpp.cpp
+++ b/generate_cpp.cpp
@@ -1234,6 +1234,14 @@
" return DESCIPTOR;\n"
"}\n",
parcel.GetCanonicalName().c_str()))));
+
+ // toString() method
+ includes.insert("codecvt"); // std::codecvt_utf8_utf16
+ includes.insert("locale"); // std::wstrinig_convert
+ includes.insert("sstream"); // std::ostringstream
+ const string code = CodeWriter::RunWith(&GenerateToString, parcel);
+ parcel_class->AddPublic(std::make_unique<LiteralDecl>(code));
+
return unique_ptr<Document>{
new CppHeader{vector<string>(includes.begin(), includes.end()),
NestInNamespaces(std::move(parcel_class), parcel.GetSplitPackage())}};
diff --git a/generate_ndk.cpp b/generate_ndk.cpp
index 8718561..717f60c 100644
--- a/generate_ndk.cpp
+++ b/generate_ndk.cpp
@@ -982,7 +982,11 @@
out << "#pragma once\n";
out << "#include <android/binder_interface_utils.h>\n";
out << "#include <android/binder_parcelable_utils.h>\n";
- out << "\n";
+
+ // used by toString()
+ out << "#include <codecvt>\n";
+ out << "#include <locale>\n";
+ out << "#include <sstream>\n";
GenerateHeaderIncludes(out, types, defined_type);
@@ -1021,6 +1025,9 @@
out << "static const ::ndk::parcelable_stability_t _aidl_stability = ::ndk::"
<< (defined_type.IsVintfStability() ? "STABILITY_VINTF" : "STABILITY_LOCAL") << ";\n";
+
+ cpp::GenerateToString(out, defined_type);
+
out.Dedent();
out << "};\n";
LeaveNdkNamespace(out, defined_type);
@@ -1112,6 +1119,12 @@
out << "#pragma once\n";
out << "#include <android/binder_interface_utils.h>\n";
out << "#include <android/binder_parcelable_utils.h>\n";
+
+ // used by toString()
+ out << "#include <codecvt>\n";
+ out << "#include <locale>\n";
+ out << "#include <sstream>\n";
+
out << "\n";
for (const auto& header : cpp::UnionWriter::headers) {
@@ -1138,6 +1151,7 @@
out << "static const ::ndk::parcelable_stability_t _aidl_stability = ::ndk::"
<< (defined_type.IsVintfStability() ? "STABILITY_VINTF" : "STABILITY_LOCAL") << ";\n";
+ cpp::GenerateToString(out, defined_type);
out.Dedent();
out << "private:\n";
out.Indent();
diff --git a/tests/aidl_test_client_parcelables.cpp b/tests/aidl_test_client_parcelables.cpp
index 696261e..1b3a51e 100644
--- a/tests/aidl_test_client_parcelables.cpp
+++ b/tests/aidl_test_client_parcelables.cpp
@@ -14,11 +14,13 @@
* limitations under the License.
*/
+#include <android/aidl/tests/ParcelableForToString.h>
#include <android/aidl/tests/extension/MyExt.h>
#include <android/aidl/tests/extension/MyExt2.h>
#include <android/aidl/tests/extension/MyExtLike.h>
#include "aidl_test_client.h"
+#include <string>
#include <vector>
using android::IInterface;
@@ -29,6 +31,8 @@
using android::aidl::tests::INamedCallback;
using android::aidl::tests::IntEnum;
using android::aidl::tests::ITestService;
+using android::aidl::tests::OtherParcelableForToString;
+using android::aidl::tests::ParcelableForToString;
using android::aidl::tests::SimpleParcelable;
using android::aidl::tests::StructuredParcelable;
using android::aidl::tests::Union;
@@ -38,6 +42,7 @@
using android::aidl::tests::extension::MyExtLike;
using android::binder::Status;
using android::os::PersistableBundle;
+using std::string;
using std::vector;
TEST_F(AidlTest, RepeatSimpleParcelable) {
@@ -378,3 +383,66 @@
EXPECT_EQ(ext2, *actualExt2);
}
}
+
+TEST_F(AidlTest, ParcelableToString) {
+ ParcelableForToString p;
+ p.intValue = 10;
+ p.intArray = {20, 30};
+ p.longValue = 100L;
+ p.longArray = {200L, 300L};
+ p.doubleValue = 3.14;
+ p.doubleArray = {1.1, 1.2};
+ p.floatValue = 3.14f;
+ p.floatArray = {1.1f, 1.2f};
+ p.byteValue = 3;
+ p.byteArray = {5, 6};
+ p.booleanValue = true;
+ p.booleanArray = {true, false};
+ p.stringValue = String16("this is a string");
+ p.stringArray = {String16("hello"), String16("world")};
+ p.stringList = {String16("alice"), String16("bob")};
+ OtherParcelableForToString op;
+ op.field = String16("other");
+ p.parcelableValue = op;
+ p.parcelableArray = {op, op};
+ p.enumValue = IntEnum::FOO;
+ p.enumArray = {IntEnum::FOO, IntEnum::BAR};
+ // p.nullArray = null;
+ // p.nullList = null;
+ GenericStructuredParcelable<int32_t, StructuredParcelable, IntEnum> gen;
+ gen.a = 1;
+ gen.b = 2;
+ p.parcelableGeneric = gen;
+ p.unionValue = Union(std::vector<std::string>{"union", "value"});
+
+ const string expected =
+ "ParcelableForToString{"
+ "intValue: 10, "
+ "intArray: [20, 30], "
+ "longValue: 100, "
+ "longArray: [200, 300], "
+ "doubleValue: 3.140000, "
+ "doubleArray: [1.100000, 1.200000], "
+ "floatValue: 3.140000, "
+ "floatArray: [1.100000, 1.200000], "
+ "byteValue: 3, "
+ "byteArray: [5, 6], "
+ "booleanValue: true, "
+ "booleanArray: [true, false], "
+ "stringValue: this is a string, "
+ "stringArray: [hello, world], "
+ "stringList: [alice, bob], "
+ "parcelableValue: OtherParcelableForToString{field: other}, "
+ "parcelableArray: ["
+ "OtherParcelableForToString{field: other}, "
+ "OtherParcelableForToString{field: other}], "
+ "enumValue: FOO, "
+ "enumArray: [FOO, BAR], "
+ "nullArray: [], "
+ "nullList: [], "
+ "parcelableGeneric: GenericStructuredParcelable{a: 1, b: 2}, "
+ "unionValue: Union{ss: [union, value]}"
+ "}";
+
+ EXPECT_EQ(expected, p.toString());
+}