gen_log: prints parcelable/union/enum types

User defined types are printed using toString().

Bug: 161439795
Test: aidl_integration_test
Change-Id: I10fbac5f268d19791899129e339dd7c64638be99
diff --git a/Android.bp b/Android.bp
index dd88a5d..df214b8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -394,6 +394,9 @@
     local_include_dir: "tests",
     srcs: [
         "tests/android/aidl/loggable/ILoggableInterface.aidl",
+        "tests/android/aidl/loggable/Data.aidl",
+        "tests/android/aidl/loggable/Enum.aidl",
+        "tests/android/aidl/loggable/Union.aidl",
     ],
     gen_trace: true,
     backend: {
diff --git a/aidl_to_cpp_common.cpp b/aidl_to_cpp_common.cpp
index 58dafdb..5e0ef1f 100644
--- a/aidl_to_cpp_common.cpp
+++ b/aidl_to_cpp_common.cpp
@@ -32,13 +32,16 @@
 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}"; }
-)";
+char kToStringHelper[] = R"(template <typename _T> class _has_toString {
+  template <typename _U> static std::true_type __has_toString(decltype(&_U::toString));
+  template <typename _U> static std::false_type __has_toString(...);
+  public: enum { value = decltype(__has_toString<_T>(nullptr))::value };
+};
+template <typename _T> inline static std::string _call_toString(const _T& t) {
+  if constexpr (_has_toString<_T>::value) return t.toString();
+  return "{no toString() implemented}";
 }
+)";
 
 string ClassName(const AidlDefinedType& defined_type, ClassNames type) {
   string base_name = defined_type.GetName();
@@ -198,7 +201,9 @@
 
   // function that writes an expression to convert a variable to a Json::Value
   // object
-  std::function<void(CodeWriter& w, const string& var_name, bool isNdk)> toJsonValueExpr;
+  std::function<void(CodeWriter& w, const AidlTypeSpecifier& type, const string& var_name,
+                     bool isNdk)>
+      toJsonValueExpr;
 };
 
 const static std::unordered_map<std::string, TypeInfo> kTypeInfoMap = {
@@ -206,21 +211,21 @@
     {"boolean",
      {
          "bool",
-         [](CodeWriter& c, const string& var_name, bool) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool) {
            c << "Json::Value(" << var_name << "? \"true\" : \"false\")";
          },
      }},
     {"byte",
      {
          "int8_t",
-         [](CodeWriter& c, const string& var_name, bool) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool) {
            c << "Json::Value(" << var_name << ")";
          },
      }},
     {"char",
      {
          "char16_t",
-         [](CodeWriter& c, const string& var_name, bool isNdk) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool isNdk) {
            if (isNdk) {
              c << "Json::Value(" << var_name << ")";
            } else {
@@ -231,54 +236,68 @@
     {"int",
      {
          "int32_t",
-         [](CodeWriter& c, const string& var_name, bool) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool) {
            c << "Json::Value(" << var_name << ")";
          },
      }},
     {"long",
      {
          "int64_t",
-         [](CodeWriter& c, const string& var_name, bool) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool) {
            c << "Json::Value(static_cast<Json::Int64>(" << var_name << "))";
          },
      }},
     {"float",
      {
          "float",
-         [](CodeWriter& c, const string& var_name, bool) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool) {
            c << "Json::Value(" << var_name << ")";
          },
      }},
     {"double",
      {
          "double",
-         [](CodeWriter& c, const string& var_name, bool) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool) {
            c << "Json::Value(" << var_name << ")";
          },
      }},
     {"String",
      {
          "std::string",
-         [](CodeWriter& c, const string& var_name, bool) {
+         [](CodeWriter& c, const AidlTypeSpecifier&, const string& var_name, bool) {
            c << "Json::Value(" << var_name << ")";
          },
      }}
     // missing List, Map, ParcelFileDescriptor, IBinder
 };
 
+const static TypeInfo kTypeInfoForDefinedType{
+    "<<parcelable>>",  // pseudo-name for parcelable types
+    [](CodeWriter& c, const AidlTypeSpecifier& type, const string& var_name, bool) {
+      c << ToString(type, var_name);
+    }};
+
 TypeInfo GetTypeInfo(const AidlTypeSpecifier& aidl) {
   AIDL_FATAL_IF(!aidl.IsResolved(), aidl) << aidl.ToString();
   const string& aidl_name = aidl.GetName();
 
-  TypeInfo info;
   if (AidlTypenames::IsBuiltinTypename(aidl_name)) {
     auto it = kTypeInfoMap.find(aidl_name);
     if (it != kTypeInfoMap.end()) {
-      info = it->second;
+      return it->second;
     }
+    return {};
   }
-  // Missing interface and parcelable type
-  return info;
+
+  const AidlDefinedType* defined_type = aidl.GetDefinedType();
+  AIDL_FATAL_IF(defined_type == NULL, aidl) << aidl.ToString();
+  if (defined_type->AsStructuredParcelable() || defined_type->AsEnumDeclaration() ||
+      defined_type->AsUnionDeclaration()) {
+    return kTypeInfoForDefinedType;
+  }
+
+  // skip interface types
+  return {};
 }
 
 inline bool CanWriteLog(const TypeInfo& t) {
@@ -298,13 +317,14 @@
 
   const string var_object_expr = ((isPointer ? "*" : "")) + name;
   if (type.IsArray()) {
+    const AidlTypeSpecifier& base_type = type.ArrayBase();
     writer << log << " = Json::Value(Json::arrayValue);\n";
     writer << "for (const auto& v: " << var_object_expr << ") " << log << ".append(";
-    info.toJsonValueExpr(writer, "v", isNdk);
+    info.toJsonValueExpr(writer, base_type, "v", isNdk);
     writer << ");";
   } else {
     writer << log << " = ";
-    info.toJsonValueExpr(writer, var_object_expr, isNdk);
+    info.toJsonValueExpr(writer, type, var_object_expr, isNdk);
     writer << ";";
   }
   writer << "\n";
diff --git a/aidl_to_cpp_common.h b/aidl_to_cpp_common.h
index d11b067..2b2db4a 100644
--- a/aidl_to_cpp_common.h
+++ b/aidl_to_cpp_common.h
@@ -29,6 +29,9 @@
 namespace aidl {
 namespace cpp {
 
+// provides _call_toString(expr) which call expr.toString() if possible
+extern char kToStringHelper[];
+
 // These roughly correspond to the various class names in the C++ hierarchy:
 enum class ClassNames {
   BASE,          // Foo (not a real class, but useful in some circumstances).
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index df29b8c..6c4b2f0 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -3065,9 +3065,15 @@
     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}"; }
+  template <typename _T> class _has_toString {
+    template <typename _U> static std::true_type __has_toString(decltype(&_U::toString));
+    template <typename _U> static std::false_type __has_toString(...);
+    public: enum { value = decltype(__has_toString<_T>(nullptr))::value };
+  };
+  template <typename _T> inline static std::string _call_toString(const _T& t) {
+    if constexpr (_has_toString<_T>::value) return t.toString();
+    return "{no toString() implemented}";
+  }
   inline std::string toString() const {
     std::ostringstream os;
     os << "Foo{";
@@ -3211,9 +3217,15 @@
   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}"; }
+  template <typename _T> class _has_toString {
+    template <typename _U> static std::true_type __has_toString(decltype(&_U::toString));
+    template <typename _U> static std::false_type __has_toString(...);
+    public: enum { value = decltype(__has_toString<_T>(nullptr))::value };
+  };
+  template <typename _T> inline static std::string _call_toString(const _T& t) {
+    if constexpr (_has_toString<_T>::value) return t.toString();
+    return "{no toString() implemented}";
+  }
   inline std::string toString() const {
     std::ostringstream os;
     os << "Foo{";
diff --git a/generate_cpp.cpp b/generate_cpp.cpp
index 770c3c7..7784a97 100644
--- a/generate_cpp.cpp
+++ b/generate_cpp.cpp
@@ -815,6 +815,8 @@
       ConstructorDecl::IS_VIRTUAL | ConstructorDecl::IS_DEFAULT}};
 
   vector<unique_ptr<Declaration>> publics;
+  vector<unique_ptr<Declaration>> privates;
+
   publics.push_back(std::move(constructor));
   publics.push_back(std::move(destructor));
 
@@ -832,10 +834,9 @@
     includes.emplace_back("json/value.h");
     publics.emplace_back(
         new LiteralDecl{"static std::function<void(const Json::Value&)> logFunc;\n"});
+    privates.emplace_back(new LiteralDecl{kToStringHelper});
   }
 
-  vector<unique_ptr<Declaration>> privates;
-
   if (options.Version() > 0) {
     privates.emplace_back(new LiteralDecl("int32_t cached_version_ = -1;\n"));
   }
@@ -876,6 +877,8 @@
   vector<string> includes = {"binder/IInterface.h", HeaderFile(interface, ClassNames::RAW, false)};
 
   vector<unique_ptr<Declaration>> publics;
+  vector<unique_ptr<Declaration>> privates;
+
   publics.push_back(std::move(constructor));
   publics.push_back(std::move(on_transact));
 
@@ -896,9 +899,13 @@
     includes.emplace_back("json/value.h");
     publics.emplace_back(
         new LiteralDecl{"static std::function<void(const Json::Value&)> logFunc;\n"});
+    privates.emplace_back(new LiteralDecl{kToStringHelper});
   }
-  unique_ptr<ClassDecl> bn_class{
-      new ClassDecl{bn_name, "::android::BnInterface<" + i_name + ">", {}, std::move(publics), {}}};
+  unique_ptr<ClassDecl> bn_class{new ClassDecl{bn_name,
+                                               "::android::BnInterface<" + i_name + ">",
+                                               {},
+                                               std::move(publics),
+                                               std::move(privates)}};
 
   return unique_ptr<Document>{
       new CppHeader{includes, NestInNamespaces(std::move(bn_class), interface.GetSplitPackage())}};
diff --git a/generate_ndk.cpp b/generate_ndk.cpp
index 717f60c..8ef2a67 100644
--- a/generate_ndk.cpp
+++ b/generate_ndk.cpp
@@ -365,6 +365,9 @@
   out << "\n";
 
   EnterNdkNamespace(out, defined_type);
+  if (options.GenLog()) {
+    out << cpp::kToStringHelper;
+  }
   GenerateClassSource(out, types, defined_type, options);
   GenerateClientSource(out, types, defined_type, options);
   GenerateServerSource(out, types, defined_type, options);
diff --git a/tests/aidl_test_client_loggable_interface.cpp b/tests/aidl_test_client_loggable_interface.cpp
index ba99d4c..50202dd 100644
--- a/tests/aidl_test_client_loggable_interface.cpp
+++ b/tests/aidl_test_client_loggable_interface.cpp
@@ -29,7 +29,10 @@
 using android::sp;
 using android::String16;
 using android::aidl::loggable::BpLoggableInterface;
+using android::aidl::loggable::Data;
+using android::aidl::loggable::Enum;
 using android::aidl::loggable::ILoggableInterface;
+using android::aidl::loggable::Union;
 using android::aidl::tests::BackendType;
 using android::os::ParcelFileDescriptor;
 using std::optional;
@@ -71,6 +74,11 @@
   String16 stringValue("def");
   vector<String16> stringArray{String16("ghi"), String16("jkl")};
   vector<String16> listValue{String16("mno")};
+  Data dataValue;
+  dataValue.num = 42;
+  dataValue.str = "abc";
+  dataValue.nestedUnion = "def";
+  dataValue.nestedEnum = Enum::FOO;
   sp<IBinder> binderValue;
   optional<ParcelFileDescriptor> pfdValue;
   vector<ParcelFileDescriptor> pfdArray;
@@ -79,7 +87,7 @@
   status = loggable->LogThis(boolValue, &boolArray, byteValue, &byteArray, charValue, &charArray,
                              intValue, &intArray, longValue, &longArray, floatValue, &floatArray,
                              doubleValue, &doubleArray, stringValue, &stringArray, &listValue,
-                             binderValue, &pfdValue, &pfdArray, &_aidl_return);
+                             dataValue, binderValue, &pfdValue, &pfdArray, &_aidl_return);
   EXPECT_TRUE(status.isOk());
   EXPECT_EQ(vector<String16>{String16("loggable")}, _aidl_return);
 
@@ -155,6 +163,10 @@
       {
          "name" : "stringArray",
          "value" : [ true, true ]
+      },
+      {
+         "name" : "dataValue",
+         "value" : "Data{num: 42, str: abc, nestedUnion: Union{str: def}, nestedEnum: FOO}"
       }
    ],
    "interface_name" : "android.aidl.loggable.ILoggableInterface",
diff --git a/tests/aidl_test_service.cpp b/tests/aidl_test_service.cpp
index 0b1674e..26a9718 100644
--- a/tests/aidl_test_service.cpp
+++ b/tests/aidl_test_service.cpp
@@ -55,6 +55,7 @@
 #include "android/aidl/tests/extension/MyExt2.h"
 
 #include "android/aidl/loggable/BnLoggableInterface.h"
+#include "android/aidl/loggable/Data.h"
 
 // Used implicitly.
 #undef LOG_TAG
@@ -636,9 +637,9 @@
   virtual Status LogThis(bool, vector<bool>*, int8_t, vector<uint8_t>*, char16_t, vector<char16_t>*,
                          int32_t, vector<int32_t>*, int64_t, vector<int64_t>*, float,
                          vector<float>*, double, vector<double>*, const String16&,
-                         vector<String16>*, vector<String16>*, const sp<IBinder>&,
-                         optional<ParcelFileDescriptor>*, vector<ParcelFileDescriptor>*,
-                         vector<String16>* _aidl_return) override {
+                         vector<String16>*, vector<String16>*, const android::aidl::loggable::Data&,
+                         const sp<IBinder>&, optional<ParcelFileDescriptor>*,
+                         vector<ParcelFileDescriptor>*, vector<String16>* _aidl_return) override {
     *_aidl_return = vector<String16>{String16("loggable")};
     return Status::ok();
   }
diff --git a/tests/android/aidl/loggable/Data.aidl b/tests/android/aidl/loggable/Data.aidl
new file mode 100644
index 0000000..cd7f4ae
--- /dev/null
+++ b/tests/android/aidl/loggable/Data.aidl
@@ -0,0 +1,11 @@
+package android.aidl.loggable;
+
+import android.aidl.loggable.Enum;
+import android.aidl.loggable.Union;
+
+parcelable Data {
+    int num;
+    @utf8InCpp String str;
+    Union nestedUnion;
+    Enum nestedEnum;
+}
\ No newline at end of file
diff --git a/tests/android/aidl/loggable/Enum.aidl b/tests/android/aidl/loggable/Enum.aidl
new file mode 100644
index 0000000..bb6a180
--- /dev/null
+++ b/tests/android/aidl/loggable/Enum.aidl
@@ -0,0 +1,3 @@
+package android.aidl.loggable;
+
+enum Enum { FOO = 42 }
\ No newline at end of file
diff --git a/tests/android/aidl/loggable/ILoggableInterface.aidl b/tests/android/aidl/loggable/ILoggableInterface.aidl
index 8944fb0..a19efc1 100644
--- a/tests/android/aidl/loggable/ILoggableInterface.aidl
+++ b/tests/android/aidl/loggable/ILoggableInterface.aidl
@@ -1,4 +1,5 @@
 package android.aidl.loggable;
+import  android.aidl.loggable.Data;
 
 interface ILoggableInterface {
     String[] LogThis(boolean boolValue, inout boolean[] boolArray,
@@ -10,6 +11,7 @@
                    double doubleValue, inout double[] doubleArray,
                    String stringValue, inout String[] stringArray,
                    inout List<String> listValue,
+                   in Data dataValue,
                    @nullable IBinder binderValue,
                    inout @nullable ParcelFileDescriptor pfdValue, inout ParcelFileDescriptor[] pfdArray);
 }
diff --git a/tests/android/aidl/loggable/Union.aidl b/tests/android/aidl/loggable/Union.aidl
new file mode 100644
index 0000000..4e116ae
--- /dev/null
+++ b/tests/android/aidl/loggable/Union.aidl
@@ -0,0 +1,6 @@
+package android.aidl.loggable;
+
+union Union {
+    int num = 43;
+    @utf8InCpp String str;
+}
\ No newline at end of file