Error out on invalid input.

In this CL we make sure that:

* we do not visit AST if it has errors
* we return nonzero exit code if the input headers had errors
* we delete output files if the input headers had errors (this is the behavior of Clang as well)

We do not need to be able to process partially incorrect headers, we assume all input it valid C++. In case of errors Clang will print errors to stderr, so the user will be informed about the cause of the failure.

PiperOrigin-RevId: 391482623
diff --git a/rs_bindings_from_cc/ast_consumer.cc b/rs_bindings_from_cc/ast_consumer.cc
index 9fc3c1b..d383004 100644
--- a/rs_bindings_from_cc/ast_consumer.cc
+++ b/rs_bindings_from_cc/ast_consumer.cc
@@ -11,6 +11,13 @@
 namespace rs_bindings_from_cc {
 
 void AstConsumer::HandleTranslationUnit(clang::ASTContext &ast_context) {
+  if (ast_context.getDiagnostics().hasErrorOccurred()) {
+    // We do not need to process partially incorrect headers, we assume all
+    // input is valid C++. If there is an error Clang already printed it to
+    // stderr; the user will be informed about the cause of the failure.
+    // There is nothing more for us to do here.
+    return;
+  }
   ast_visitor_.TraverseDecl(ast_context.getTranslationUnitDecl());
 }
 
diff --git a/rs_bindings_from_cc/ast_visitor_test.cc b/rs_bindings_from_cc/ast_visitor_test.cc
index d9f95ba..336e4ac 100644
--- a/rs_bindings_from_cc/ast_visitor_test.cc
+++ b/rs_bindings_from_cc/ast_visitor_test.cc
@@ -35,6 +35,11 @@
   EXPECT_THAT(ir.Functions(), IsEmpty());
 }
 
+TEST(AstVisitorTest, TestIREmptyOnInvalidInput) {
+  IR ir = ImportCode("int foo(); But this is not C++", {});
+  EXPECT_THAT(ir.Functions(), IsEmpty());
+}
+
 TEST(AstVisitorTest, TestImportFuncWithVoidReturnType) {
   IR ir = ImportCode("void Foo();", {});
   ASSERT_THAT(ir.Functions(), SizeIs(1));
diff --git a/rs_bindings_from_cc/rs_bindings_from_cc.cc b/rs_bindings_from_cc/rs_bindings_from_cc.cc
index fc8fcdc..fd5c21d 100644
--- a/rs_bindings_from_cc/rs_bindings_from_cc.cc
+++ b/rs_bindings_from_cc/rs_bindings_from_cc.cc
@@ -16,6 +16,7 @@
 #include "rs_bindings_from_cc/frontend_action.h"
 #include "rs_bindings_from_cc/ir.h"
 #include "rs_bindings_from_cc/rs_src_code_gen.h"
+#include "file/base/filesystem.h"
 #include "file/base/helpers.h"
 #include "file/base/options.h"
 #include "third_party/absl/container/flat_hash_map.h"
@@ -69,6 +70,8 @@
     CHECK_OK(file::SetContents(cc_out, rs_api_impl, file::Defaults()));
     return 0;
   } else {
+    CHECK_OK(file::Delete(rs_out, file::Defaults()));
+    CHECK_OK(file::Delete(cc_out, file::Defaults()));
     return 1;
   }
 }
diff --git a/rs_bindings_from_cc/test/rs_bindings_from_cc_test.sh b/rs_bindings_from_cc/test/rs_bindings_from_cc_test.sh
index 51af475..76b1621 100755
--- a/rs_bindings_from_cc/test/rs_bindings_from_cc_test.sh
+++ b/rs_bindings_from_cc/test/rs_bindings_from_cc_test.sh
@@ -10,7 +10,7 @@
 # Find input files
 readonly RS_BINDINGS_FROM_CC="${RUNFILES}/rs_bindings_from_cc/rs_bindings_from_cc"
 
-function test::rs_bindings_from_cc_cmd_line_api() {
+function test::cmd_line_api() {
   EXPECT_FAIL "${RS_BINDINGS_FROM_CC}" "generator should return non-zero with no arguments"
   EXPECT_SUCCEED \
     "${RS_BINDINGS_FROM_CC} 2>&1 | grep 'please specify --rs_out' > /dev/null" \
@@ -42,4 +42,23 @@
   EXPECT_FILE_NOT_EMPTY "${cc_out}"
 }
 
+function test::tool_returns_nonzero_on_invalid_input() {
+  local rs_out="${TEST_TMPDIR}/rs_api.rs"
+  local cc_out="${TEST_TMPDIR}/rs_api_impl.cc"
+
+  # Creating outputs so we can observe if the tool deletes them.
+  touch "${rs_out}" "${cc_out}"
+
+  local hdr="${TEST_TMPDIR}/hello_world.h"
+  echo "int foo(); But this is not C++;" > "${hdr}"
+  EXPECT_FAIL \
+    "\"${RS_BINDINGS_FROM_CC}\" \
+      --rs_out=\"${rs_out}\" \
+      --cc_out=\"${cc_out}\" \
+      --public_headers=\"${hdr}\" 1>&2"
+
+  CHECK_FILE_NOT_EXISTS "${rs_out}"
+  CHECK_FILE_NOT_EXISTS "${cc_out}"
+}
+
 gbash::unit::main "$@"
\ No newline at end of file