Avoid growing boot class path for --single-image compiled images

Calculation of the number of components in Runtime::Init did not
account for images compiled with --single-image where the number of
images does not equal the number of components.

Bug: 160683548
Test: Treehugger
Change-Id: I1db7a4bbbc8bb2f48d54f5048bd1b8407d19cf02
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index f3c58d3..0bed8eb 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1382,6 +1382,11 @@
     std::vector<std::unique_ptr<const DexFile>>&& additional_dex_files) {
   for (std::unique_ptr<const DexFile>& dex_file : additional_dex_files) {
     AppendToBootClassPath(self, dex_file.get());
+    if (kIsDebugBuild) {
+      for (const auto& boot_dex_file : boot_dex_files_) {
+        DCHECK_NE(boot_dex_file->GetLocation(), dex_file->GetLocation());
+      }
+    }
     boot_dex_files_.push_back(std::move(dex_file));
   }
 }
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 0cb9713..a718b5c 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -3534,6 +3534,14 @@
   return boot_image_checksum;
 }
 
+size_t ImageSpace::GetNumberOfComponents(ArrayRef<ImageSpace* const> image_spaces) {
+  size_t n = 0;
+  for (auto&& is : image_spaces) {
+    n += is->GetComponentCount();
+  }
+  return n;
+}
+
 static size_t CheckAndCountBCPComponents(std::string_view oat_boot_class_path,
                                          ArrayRef<const std::string> boot_class_path,
                                          /*out*/std::string* error_msg) {
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 36889fe..157c9ac 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -189,6 +189,11 @@
     return &live_bitmap_;
   }
 
+  // Compute the number of components in the image (contributing jar files).
+  size_t GetComponentCount() const {
+    return GetImageHeader().GetComponentCount();
+  }
+
   void Dump(std::ostream& os) const override;
 
   // Sweeping image spaces is a NOP.
@@ -225,6 +230,9 @@
   static std::string GetBootClassPathChecksums(ArrayRef<ImageSpace* const> image_spaces,
                                                ArrayRef<const DexFile* const> boot_class_path);
 
+  // Returns the total number of components (jar files) associated with the image spaces.
+  static size_t GetNumberOfComponents(ArrayRef<gc::space::ImageSpace* const> image_spaces);
+
   // Returns whether the checksums are valid for the given boot class path,
   // image location and ISA (may differ from the ISA of an initialized Runtime).
   // The boot image and dex files do not need to be loaded in memory.
diff --git a/runtime/image.h b/runtime/image.h
index 61db627..c8e5948 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -437,10 +437,12 @@
   // their oat files are mmapped independently.
   uint32_t image_reservation_size_ = 0u;
 
-  // The number of components.
+  // The number of components (jar files contributing to the image).
   // For boot image or boot image extension, the primary image stores the total number
-  // of images, secondary images have this set to 0.
-  // App images have 1 component.
+  // of components, secondary images have this set to 0. App images have 1 component.
+  // The component count usually matches the total number of images (one image per component), but
+  // if multiple components are compiled with --single-image there will only be 1 image associated
+  // with those components.
   uint32_t component_count_ = 0u;
 
   // Required base address for mapping the image.
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index ac3c392..2012a04 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1594,10 +1594,13 @@
         GetInternTable()->AddImageStringsToTable(image_space, VoidFunctor());
       }
     }
-    if (heap_->GetBootImageSpaces().size() != GetBootClassPath().size()) {
+
+    const size_t total_components = gc::space::ImageSpace::GetNumberOfComponents(
+        ArrayRef<gc::space::ImageSpace* const>(heap_->GetBootImageSpaces()));
+    if (total_components != GetBootClassPath().size()) {
       // The boot image did not contain all boot class path components. Load the rest.
-      DCHECK_LT(heap_->GetBootImageSpaces().size(), GetBootClassPath().size());
-      size_t start = heap_->GetBootImageSpaces().size();
+      CHECK_LT(total_components, GetBootClassPath().size());
+      size_t start = total_components;
       DCHECK_LT(start, GetBootClassPath().size());
       std::vector<std::unique_ptr<const DexFile>> extra_boot_class_path;
       if (runtime_options.Exists(Opt::BootClassPathDexList)) {