Fix handling for invalid or missing app images

Avoid crashing when there is an invalid app image path passed to
oatdump. Added regression test.

Bug: 137724009
Test: test-art-host-gtest-oatdump_app_test

Change-Id: I27470d0c1d844de5b9f3f3bf960e925cd8977d50
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 090e271..ea64708 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2782,12 +2782,14 @@
     if (space == nullptr) {
       LOG(ERROR) << "Failed to open app image " << options->app_image_ << " with error "
                  << error_msg;
+      return EXIT_FAILURE;
     }
     // Open dex files for the image.
     std::vector<std::unique_ptr<const DexFile>> dex_files;
     if (!runtime->GetClassLinker()->OpenImageDexFiles(space.get(), &dex_files, &error_msg)) {
       LOG(ERROR) << "Failed to open app image dex files " << options->app_image_ << " with error "
                  << error_msg;
+      return EXIT_FAILURE;
     }
     // Dump the actual image.
     int result = DumpImage(space.get(), options, os);
diff --git a/oatdump/oatdump_app_test.cc b/oatdump/oatdump_app_test.cc
index 4490647..b4997ba 100644
--- a/oatdump/oatdump_app_test.cc
+++ b/oatdump/oatdump_app_test.cc
@@ -42,4 +42,13 @@
   ASSERT_TRUE(Exec(kStatic, kModeAppImage, {}, kListAndCode));
 }
 
+TEST_F(OatDumpTest, TestAppImageInvalidPath) {
+  TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS();  // GC bug, b/126305867
+  TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
+  const std::string app_image_arg = "--app-image-file=" + GetAppImageName();
+  ASSERT_TRUE(GenerateAppOdexFile(kStatic, {"--runtime-arg", "-Xmx64M", app_image_arg}));
+  SetAppImageName("missing_app_image.art");
+  ASSERT_TRUE(Exec(kStatic, kModeAppImage, {}, kListAndCode, /*expect_failure=*/true));
+}
+
 }  // namespace art
diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h
index 359b060..7c5149d 100644
--- a/oatdump/oatdump_test.h
+++ b/oatdump/oatdump_test.h
@@ -107,8 +107,15 @@
     return "ProfileTestMultiDex";
   }
 
+  void SetAppImageName(const std::string& name) {
+    app_image_name_ = name;
+  }
+
   std::string GetAppImageName() {
-    return tmp_dir_ + "/" + GetAppBaseName() + ".art";
+    if (app_image_name_.empty()) {
+      app_image_name_ =  tmp_dir_ + "/" + GetAppBaseName() + ".art";
+    }
+    return app_image_name_;
   }
 
   std::string GetAppOdexName() {
@@ -158,7 +165,8 @@
   ::testing::AssertionResult Exec(Flavor flavor,
                                   Mode mode,
                                   const std::vector<std::string>& args,
-                                  Display display) {
+                                  Display display,
+                                  bool expect_failure = false) {
     std::string file_path = GetExecutableFilePath(flavor, "oatdump");
 
     if (!OS::FileExists(file_path.c_str())) {
@@ -322,8 +330,17 @@
     if (res.stage != ForkAndExecResult::kFinished) {
       return ::testing::AssertionFailure() << strerror(errno);
     }
+    error_buf.push_back(0);  // Make data a C string.
+
     if (!res.StandardSuccess()) {
-      return ::testing::AssertionFailure() << "Did not terminate successfully: " << res.status_code;
+      if (expect_failure && WIFEXITED(res.status_code)) {
+        // Avoid crash as valid exit.
+        return ::testing::AssertionSuccess();
+      }
+      return ::testing::AssertionFailure() << "Did not terminate successfully: " << res.status_code
+          << " " << error_buf.data();
+    } else if (expect_failure) {
+      return ::testing::AssertionFailure() << "Expected failure";
     }
 
     if (mode == kModeSymbolize) {
@@ -342,7 +359,6 @@
     }
     if (!result) {
       oss << "Processed bytes " << total << ":" << std::endl;
-      error_buf.push_back(0);  // Make data a C string.
     }
 
     return result ? ::testing::AssertionSuccess()
@@ -350,6 +366,7 @@
   }
 
   std::string tmp_dir_;
+  std::string app_image_name_;
 
  private:
   std::string core_art_location_;