List<T> supported types

Added a parameterized test to reflect the current status:

This revealed some weird cases like "List<IBinder>" is not supported
in the NDK backends.

Note that added checks in aidl_language.cpp are to avoid crashes. We
need to fix if the current status is not what we intended.

Bug: 174779997
Bug: 171932530
Test: aidl_unittests
Change-Id: Id5fa4c55b9c36206b25d6408d16ddfc224704f95
diff --git a/aidl.cpp b/aidl.cpp
index 0f894c2..344649d 100644
--- a/aidl.cpp
+++ b/aidl.cpp
@@ -522,6 +522,7 @@
     return true;
   };
   const bool is_check_api = options.GetTask() == Options::Task::CHECK_API;
+  const bool is_dump_api = options.GetTask() == Options::Task::DUMP_API;
 
   // Resolve the unresolved type references found from the input file
   if (!is_check_api && !main_parser->Resolve(resolver)) {
@@ -592,8 +593,10 @@
         }
       }
 
-      if (!defined_type->LanguageSpecificCheckValid(*typenames, options.TargetLanguage())) {
-        valid_type = false;
+      if (!is_dump_api && !is_check_api) {
+        if (!defined_type->LanguageSpecificCheckValid(*typenames, options.TargetLanguage())) {
+          valid_type = false;
+        }
       }
 
       if (!valid_type) {
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 4ab9fad..ad6aa8b 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -522,8 +522,9 @@
     auto& types = GetTypeParameters();
     // TODO(b/136048684) Disallow to use primitive types only if it is List or Map.
     if (type_name == "List" || type_name == "Map") {
-      if (std::any_of(types.begin(), types.end(), [](auto& type_ptr) {
-            return AidlTypenames::IsPrimitiveTypename(type_ptr->GetName());
+      if (std::any_of(types.begin(), types.end(), [&](auto& type_ptr) {
+            return (typenames.GetEnumDeclaration(*type_ptr)) ||
+                   AidlTypenames::IsPrimitiveTypename(type_ptr->GetName());
           })) {
         AIDL_ERROR(this) << "A generic type cannot have any primitive type parameters.";
         return false;
@@ -1069,23 +1070,41 @@
   }
   if (this->IsGeneric()) {
     if (this->GetName() == "List") {
+      const AidlTypeSpecifier& contained_type = *GetTypeParameters()[0];
+      const string& contained_type_name = contained_type.GetName();
       if (lang == Options::Language::CPP) {
-        const string& contained_type = this->GetTypeParameters()[0]->GetName();
-        if (!(contained_type == "String" || contained_type == "IBinder")) {
-          AIDL_ERROR(this) << "List<" << contained_type
+        if (!(contained_type_name == "String" || contained_type_name == "IBinder")) {
+          AIDL_ERROR(this) << "List<" << contained_type_name
                            << "> is not supported. List in cpp supports only String and IBinder.";
           return false;
         }
       } else if (lang == Options::Language::JAVA) {
-        const string& contained_type = this->GetTypeParameters()[0]->GetName();
-        if (AidlTypenames::IsBuiltinTypename(contained_type)) {
-          if (contained_type != "String" && contained_type != "IBinder" &&
-              contained_type != "ParcelFileDescriptor") {
-            AIDL_ERROR(this) << "List<" << contained_type
+        if (AidlTypenames::IsBuiltinTypename(contained_type_name)) {
+          if (contained_type_name != "String" && contained_type_name != "IBinder" &&
+              contained_type_name != "ParcelFileDescriptor") {
+            AIDL_ERROR(this) << "List<" << contained_type_name
                              << "> is not supported. List in Java supports only String, IBinder, "
                                 "and ParcelFileDescriptor.";
             return false;
           }
+        } else {  // Defined types
+          if (typenames.GetInterface(contained_type)) {
+            AIDL_ERROR(this) << "List<" << contained_type_name
+                             << "> is not supported. List in Java supports only String, IBinder, "
+                                "and ParcelFileDescriptor.";
+            return false;
+          }
+        }
+      } else if (lang == Options::Language::NDK) {
+        if (typenames.GetInterface(contained_type)) {
+          AIDL_ERROR(this) << "List<" << contained_type_name
+                           << "> is not supported. List in NDK doesn't support interface.";
+          return false;
+        }
+        if (contained_type_name == "IBinder") {
+          AIDL_ERROR(this) << "List<" << contained_type_name
+                           << "> is not supported. List in NDK doesn't support IBinder.";
+          return false;
         }
       }
     }
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index c4651c8..4d6ee2e 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -16,6 +16,7 @@
 
 #include "aidl.h"
 
+#include <android-base/format.h>
 #include <android-base/stringprintf.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -4274,5 +4275,103 @@
   EXPECT_NE(nullptr, Parse("IFoo.aidl", contents, typenames_, GetLanguage()));
 }
 
+struct ListTypeParam {
+  string kind;
+  string literal;
+};
+
+const ListTypeParam kListTypeParams[] = {
+    {"primitive", "int"},   {"String", "String"},
+    {"IBinder", "IBinder"}, {"ParcelFileDescriptor", "ParcelFileDescriptor"},
+    {"parcelable", "Foo"},  {"enum", "a.Enum"},
+    {"union", "a.Union"},   {"interface", "a.IBar"},
+};
+
+const std::map<std::string, std::string> kListSupportExpectations = {
+    {"cpp_primitive", "A generic type cannot have any primitive type parameters."},
+    {"java_primitive", "A generic type cannot have any primitive type parameters."},
+    {"ndk_primitive", "A generic type cannot have any primitive type parameters."},
+    {"rust_primitive", "A generic type cannot have any primitive type parameters."},
+    {"cpp_String", ""},
+    {"java_String", ""},
+    {"ndk_String", ""},
+    {"rust_String", ""},
+    {"cpp_IBinder", ""},
+    {"java_IBinder", ""},
+    {"ndk_IBinder", "List<IBinder> is not supported. List in NDK doesn't support IBinder."},
+    {"rust_IBinder", ""},
+    {"cpp_ParcelFileDescriptor", "List<ParcelFileDescriptor> is not supported."},
+    {"java_ParcelFileDescriptor", ""},
+    {"ndk_ParcelFileDescriptor", ""},
+    {"rust_ParcelFileDescriptor", ""},
+    {"cpp_interface",
+     "List<a.IBar> is not supported. List in cpp supports only String and IBinder."},
+    {"java_interface",
+     "List<a.IBar> is not supported. List in Java supports only String, IBinder, and "
+     "ParcelFileDescriptor."},
+    {"ndk_interface", "List<a.IBar> is not supported. List in NDK doesn't support interface."},
+    {"rust_interface", ""},
+    {"cpp_parcelable",
+     "List<a.Foo> is not supported. List in cpp supports only String and IBinder."},
+    {"java_parcelable", ""},
+    {"ndk_parcelable", ""},
+    {"rust_parcelable", ""},
+    {"cpp_enum", "A generic type cannot have any primitive type parameters."},
+    {"java_enum", "A generic type cannot have any primitive type parameters."},
+    {"ndk_enum", "A generic type cannot have any primitive type parameters."},
+    {"rust_enum", "A generic type cannot have any primitive type parameters."},
+    {"cpp_union", "List<a.Union> is not supported. List in cpp supports only String and IBinder."},
+    {"java_union", ""},
+    {"ndk_union", ""},
+    {"rust_union", ""},
+};
+
+using AidlListTestParam = std::tuple<Options::Language, ListTypeParam>;
+
+class AidlListTest : public testing::TestWithParam<AidlListTestParam> {
+ public:
+  void SetUp() override {
+    const auto& param = GetParam();
+    const auto& lang = Options::LanguageToString(std::get<0>(param));
+    const auto& kind = std::get<1>(param).kind;
+
+    FakeIoDelegate io;
+    io.SetFileContents("a/IBar.aidl", "package a; interface IBar { }");
+    io.SetFileContents("a/Enum.aidl", "package a; enum Enum { A }");
+    io.SetFileContents("a/Union.aidl", "package a; union Union { int a; }");
+    io.SetFileContents("a/Foo.aidl", fmt::format(R"(
+      package a;
+      parcelable Foo {{
+        List<{}> list;
+      }})",
+                                                 std::get<1>(param).literal));
+
+    const auto options =
+        Options::From(fmt::format("aidl -I . --lang={} a/Foo.aidl -o out -h out", lang));
+    CaptureStderr();
+    compile_aidl(options, io);
+    auto it = kListSupportExpectations.find(lang + "_" + kind);
+    EXPECT_TRUE(it != kListSupportExpectations.end());
+    const string err = GetCapturedStderr();
+    if (it->second.empty()) {
+      EXPECT_EQ("", err);
+    } else {
+      EXPECT_THAT(err, testing::HasSubstr(it->second));
+    }
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    AidlTestSuite, AidlListTest,
+    testing::Combine(testing::Values(Options::Language::CPP, Options::Language::JAVA,
+                                     Options::Language::NDK, Options::Language::RUST),
+                     testing::ValuesIn(kListTypeParams)),
+    [](const testing::TestParamInfo<AidlListTestParam>& info) {
+      return Options::LanguageToString(std::get<0>(info.param)) + "_" +
+             std::get<1>(info.param).kind;
+    });
+
+TEST_P(AidlListTest, SupportedTypes) {}
+
 }  // namespace aidl
 }  // namespace android