ART: Be more robust with multidex uncompressed-dex odex files

Reject odex/vdex files that mix embedded and uncompressed dex files.
This simplifies the code and allows early detection of mismatches
and corruption.

Bug: 73086368
Test: m test-art-host
Test: m test-art-host-gtest-oat_file_test
Test: manual (with bug's environment)
Change-Id: Icccb3ca29b6c5f5adefe3735c91b8b6eac113af6
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 894b33b..65b691f 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -83,6 +83,11 @@
 ART_TEST_HOST_GTEST_EmptyUncompressed_DEX := $(basename $(ART_TEST_HOST_GTEST_Main_DEX))EmptyUncompressed$(suffix $(ART_TEST_HOST_GTEST_Main_DEX))
 ART_TEST_TARGET_GTEST_EmptyUncompressed_DEX := $(basename $(ART_TEST_TARGET_GTEST_Main_DEX))EmptyUncompressed$(suffix $(ART_TEST_TARGET_GTEST_Main_DEX))
 
+# Create rules for MultiDexUncompressed, a copy of MultiDex with the classes.dex uncompressed
+# for the OatFile tests.
+ART_TEST_HOST_GTEST_MultiDexUncompressed_DEX := $(basename $(ART_TEST_HOST_GTEST_MultiDex_DEX))Uncompressed$(suffix $(ART_TEST_HOST_GTEST_MultiDex_DEX))
+ART_TEST_TARGET_GTEST_MultiDexUncompressed_DEX := $(basename $(ART_TEST_TARGET_GTEST_MultiDex_DEX))Uncompressed$(suffix $(ART_TEST_TARGET_GTEST_MultiDex_DEX))
+
 $(ART_TEST_HOST_GTEST_MainStripped_DEX): $(ART_TEST_HOST_GTEST_Main_DEX)
 	cp $< $@
 	$(call dexpreopt-remove-classes.dex,$@)
@@ -111,6 +116,16 @@
 	zip -j -qD -X -0 $@ $(dir $@)classes.dex
 	rm $(dir $@)classes.dex
 
+$(ART_TEST_HOST_GTEST_MultiDexUncompressed_DEX): $(ART_TEST_HOST_GTEST_MultiDex_DEX) $(ZIPALIGN)
+	cp $< $@
+	$(call uncompress-dexs, $@)
+	$(call align-package, $@)
+
+$(ART_TEST_TARGET_GTEST_MultiDexUncompressed_DEX): $(ART_TEST_TARGET_GTEST_MultiDex_DEX) $(ZIPALIGN)
+	cp $< $@
+	$(call uncompress-dexs, $@)
+	$(call align-package, $@)
+
 ART_TEST_GTEST_VerifierDeps_SRC := $(abspath $(wildcard $(LOCAL_PATH)/VerifierDeps/*.smali))
 ART_TEST_GTEST_VerifierDepsMulti_SRC := $(abspath $(wildcard $(LOCAL_PATH)/VerifierDepsMulti/*.smali))
 ART_TEST_HOST_GTEST_VerifierDeps_DEX := $(dir $(ART_TEST_HOST_GTEST_Main_DEX))$(subst Main,VerifierDeps,$(basename $(notdir $(ART_TEST_HOST_GTEST_Main_DEX))))$(suffix $(ART_TEST_HOST_GTEST_Main_DEX))
@@ -153,7 +168,7 @@
 ART_GTEST_oat_file_assistant_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
 ART_GTEST_dexoptanalyzer_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
 ART_GTEST_image_space_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
-ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex
+ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex MainUncompressed MultiDexUncompressed
 ART_GTEST_oat_test_DEX_DEPS := Main
 ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY
 ART_GTEST_patchoat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index dc4bae3..600449c 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -633,6 +633,15 @@
     const uint8_t* dex_file_pointer = nullptr;
     if (UNLIKELY(dex_file_offset == 0U)) {
       if (uncompressed_dex_files_ == nullptr) {
+        // Do not support mixed-mode oat files.
+        if (i > 0) {
+          *error_msg = StringPrintf("In oat file '%s', unsupported uncompressed-dex-file for dex "
+                                        "file %zu (%s)",
+                                    GetLocation().c_str(),
+                                    i,
+                                    dex_file_location.c_str());
+          return false;
+        }
         uncompressed_dex_files_.reset(new std::vector<std::unique_ptr<const DexFile>>());
         // No dex files, load it from location.
         const ArtDexFileLoader dex_file_loader;
@@ -652,9 +661,31 @@
             return false;
           }
         }
+        // The oat file may be out of date wrt/ the dex-file location. We need to be defensive
+        // here and ensure that at least the number of dex files still matches.
+        // Note: actual checksum comparisons are the duty of the OatFileAssistant and will be
+        //       done after loading the OatFile.
+        if (uncompressed_dex_files_->size() != dex_file_count) {
+          *error_msg = StringPrintf("In oat file '%s', expected %u uncompressed dex files, but "
+                                        "found %zu in '%s'",
+                                    GetLocation().c_str(),
+                                    dex_file_count,
+                                    uncompressed_dex_files_->size(),
+                                    dex_file_location.c_str());
+          return false;
+        }
       }
       dex_file_pointer = uncompressed_dex_files_.get()->at(i)->Begin();
     } else {
+      // Do not support mixed-mode oat files.
+      if (uncompressed_dex_files_ != nullptr) {
+        *error_msg = StringPrintf("In oat file '%s', unsupported embedded dex-file for dex file "
+                                      "%zu (%s)",
+                                  GetLocation().c_str(),
+                                  i,
+                                  dex_file_location.c_str());
+        return false;
+      }
       if (UNLIKELY(DexSize() - dex_file_offset < sizeof(DexFile::Header))) {
         *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file "
                                       "offset %u of %zu but the size of dex file header is %zu",
diff --git a/runtime/oat_file_test.cc b/runtime/oat_file_test.cc
index 8d86401..89812f3 100644
--- a/runtime/oat_file_test.cc
+++ b/runtime/oat_file_test.cc
@@ -88,4 +88,46 @@
   EXPECT_EQ(odex_file->GetVdexFile()->Begin(), odex_file->VdexBegin());
 }
 
+TEST_F(OatFileTest, ChangingMultiDexUncompressed) {
+  std::string dex_location = GetScratchDir() + "/MultiDexUncompressed.jar";
+
+  Copy(GetTestDexFileName("MultiDexUncompressed"), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kQuicken);
+
+  std::string oat_location;
+  std::string error_msg;
+  ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
+        dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
+
+  // Ensure we can load that file. Just a precondition.
+  {
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(),
+                                                     oat_location.c_str(),
+                                                     nullptr,
+                                                     nullptr,
+                                                     false,
+                                                     /*low_4gb*/false,
+                                                     dex_location.c_str(),
+                                                     &error_msg));
+    ASSERT_TRUE(odex_file != nullptr);
+    ASSERT_EQ(2u, odex_file->GetOatDexFiles().size());
+  }
+
+  // Now replace the source.
+  Copy(GetTestDexFileName("MainUncompressed"), dex_location);
+
+  // And try to load again.
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(),
+                                                   oat_location.c_str(),
+                                                   nullptr,
+                                                   nullptr,
+                                                   false,
+                                                   /*low_4gb*/false,
+                                                   dex_location.c_str(),
+                                                   &error_msg));
+  EXPECT_TRUE(odex_file == nullptr);
+  EXPECT_NE(std::string::npos, error_msg.find("expected 2 uncompressed dex files, but found 1"))
+      << error_msg;
+}
+
 }  // namespace art