Check cpp_header for C++/NDK

Unstructured parcelables should have cpp_header for compilation.

Bug: 194846019
Test: atest aidl_unittests
Change-Id: I76c56d3976272f4b401ddc9d8d54fa0e37527400
diff --git a/aidl.cpp b/aidl.cpp
index 5703d3d..38b8220 100644
--- a/aidl.cpp
+++ b/aidl.cpp
@@ -354,6 +354,34 @@
   return validator.success;
 }
 
+bool ValidateCppHeader(const AidlDocument& doc) {
+  struct CppHeaderVisitor : AidlVisitor {
+    bool success = true;
+    void Visit(const AidlParcelable& p) override {
+      if (p.GetCppHeader().empty()) {
+        AIDL_ERROR(p) << "Unstructured parcelable \"" << p.GetName()
+                      << "\" must have C++ header defined.";
+        success = false;
+      }
+    }
+    void Visit(const AidlTypeSpecifier& m) override {
+      auto type = m.GetDefinedType();
+      if (type) {
+        auto unstructured = type->AsUnstructuredParcelable();
+        if (unstructured && unstructured->GetCppHeader().empty()) {
+          AIDL_ERROR(m) << "Unstructured parcelable \"" << m.GetUnresolvedName()
+                        << "\" must have C++ header defined.";
+          success = false;
+        }
+      }
+    }
+  };
+
+  CppHeaderVisitor validator;
+  VisitTopDown(validator, doc);
+  return validator.success;
+}
+
 }  // namespace
 
 namespace internals {
@@ -505,8 +533,8 @@
       bool isStable = unstructured_parcelable->IsStableApiParcelable(options.TargetLanguage());
       if (options.IsStructured() && !isStable) {
         AIDL_ERROR(unstructured_parcelable)
-            << "Cannot declared parcelable in a --structured interface. Parcelable must be defined "
-               "in AIDL directly.";
+            << "Cannot declare unstructured parcelable in a --structured interface. Parcelable "
+               "must be defined in AIDL directly.";
         return AidlError::NOT_STRUCTURED;
       }
       if (options.FailOnParcelable()) {
@@ -579,6 +607,12 @@
     return AidlError::BAD_TYPE;
   }
 
+  if ((options.TargetLanguage() == Options::Language::CPP ||
+       options.TargetLanguage() == Options::Language::NDK) &&
+      !ValidateCppHeader(*document)) {
+    return AidlError::BAD_TYPE;
+  }
+
   if (!Diagnose(*document, options.GetDiagnosticMapping())) {
     return AidlError::BAD_TYPE;
   }
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 7ea1bb8..76277fd 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -1215,27 +1215,8 @@
 }
 
 // TODO: we should treat every backend all the same in future.
-bool AidlParcelable::LanguageSpecificCheckValid(const AidlTypenames& /*typenames*/,
+bool AidlParcelable::LanguageSpecificCheckValid(const AidlTypenames& typenames,
                                                 Options::Language lang) const {
-  if (lang == Options::Language::CPP || lang == Options::Language::NDK) {
-    const AidlParcelable* unstructured_parcelable = this->AsUnstructuredParcelable();
-    if (unstructured_parcelable != nullptr) {
-      if (unstructured_parcelable->GetCppHeader().empty()) {
-        AIDL_ERROR(unstructured_parcelable)
-            << "Unstructured parcelable must have C++ header defined.";
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-// TODO: we should treat every backend all the same in future.
-bool AidlStructuredParcelable::LanguageSpecificCheckValid(const AidlTypenames& typenames,
-                                                          Options::Language lang) const {
-  if (!AidlParcelable::LanguageSpecificCheckValid(typenames, lang)) {
-    return false;
-  }
   for (const auto& v : this->GetFields()) {
     if (!v->GetType().LanguageSpecificCheckValid(typenames, lang)) {
       return false;
@@ -1400,20 +1381,6 @@
 }
 
 // TODO: we should treat every backend all the same in future.
-bool AidlUnionDecl::LanguageSpecificCheckValid(const AidlTypenames& typenames,
-                                               Options::Language lang) const {
-  if (!AidlParcelable::LanguageSpecificCheckValid(typenames, lang)) {
-    return false;
-  }
-  for (const auto& v : this->GetFields()) {
-    if (!v->GetType().LanguageSpecificCheckValid(typenames, lang)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-// TODO: we should treat every backend all the same in future.
 bool AidlInterface::LanguageSpecificCheckValid(const AidlTypenames& typenames,
                                                Options::Language lang) const {
   for (const auto& m : this->GetMethods()) {
diff --git a/aidl_language.h b/aidl_language.h
index 2dc1d91..c4a335e 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -1057,9 +1057,6 @@
   std::string GetPreprocessDeclarationName() const override { return "structured_parcelable"; }
 
   bool CheckValid(const AidlTypenames& typenames) const override;
-  bool LanguageSpecificCheckValid(const AidlTypenames& typenames,
-                                  Options::Language lang) const override;
-
   void DispatchVisit(AidlVisitor& v) const override { v.Visit(*this); }
 };
 
@@ -1154,8 +1151,6 @@
 
   const AidlNode& AsAidlNode() const override { return *this; }
   bool CheckValid(const AidlTypenames& typenames) const override;
-  bool LanguageSpecificCheckValid(const AidlTypenames& typenames,
-                                  Options::Language lang) const override;
   std::string GetPreprocessDeclarationName() const override { return "union"; }
 
   const AidlUnionDecl* AsUnionDeclaration() const override { return this; }
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index e63df71..b2e6eb4 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -435,16 +435,14 @@
 
   EXPECT_EQ(0, ::android::aidl::compile_aidl(java_options, io_delegate_));
   EXPECT_EQ(0, ::android::aidl::compile_aidl(cpp_options, io_delegate_));
-  const string expected_stderr =
-      "ERROR: a/Foo.aidl:1.48-52: Cannot declared parcelable in a --structured interface. "
-      "Parcelable must be defined in AIDL directly.\n";
+
   CaptureStderr();
   EXPECT_NE(0, ::android::aidl::compile_aidl(cpp_structured_options, io_delegate_));
-  EXPECT_EQ(expected_stderr, GetCapturedStderr());
+  EXPECT_THAT(GetCapturedStderr(), HasSubstr("Cannot declare unstructured"));
 
   CaptureStderr();
   EXPECT_NE(0, ::android::aidl::compile_aidl(rust_options, io_delegate_));
-  EXPECT_EQ(expected_stderr, GetCapturedStderr());
+  EXPECT_THAT(GetCapturedStderr(), HasSubstr("Cannot declare unstructured"));
 }
 
 TEST_F(AidlTest, ParcelableSupportJavaDeriveToString) {
@@ -672,6 +670,23 @@
   EXPECT_EQ((Comments{{"// get bar\n"}}), interface->GetMethods()[0]->GetComments());
 }
 
+TEST_F(AidlTest, RejectsIfCppHeaderIsMissing) {
+  io_delegate_.SetFileContents("Foo.aidl", "parcelable Foo;");
+  Options options = Options::From("aidl --lang cpp -h h -o o Foo.aidl");
+  CaptureStderr();
+  EXPECT_EQ(1, ::android::aidl::compile_aidl(options, io_delegate_));
+  EXPECT_THAT(GetCapturedStderr(), HasSubstr("must have C++ header defined"));
+}
+
+TEST_F(AidlTest, RejectsIfTypeRefsCppHeaderIsMissing) {
+  io_delegate_.SetFileContents("Foo.aidl", "parcelable Foo;");
+  io_delegate_.SetFileContents("IBar.aidl", "interface IBar { void bar(in Foo foo); }");
+  Options options = Options::From("aidl -I . --lang cpp -h h -o o IBar.aidl");
+  CaptureStderr();
+  EXPECT_EQ(1, ::android::aidl::compile_aidl(options, io_delegate_));
+  EXPECT_THAT(GetCapturedStderr(), HasSubstr("must have C++ header defined"));
+}
+
 TEST_F(AidlTest, ParsesPreprocessedFile) {
   string simple_content = "parcelable a.Foo;\ninterface b.IBar;";
   io_delegate_.SetFileContents("path", simple_content);