Avoid creating preresolved strings array for dex files that have no strings

Had exposed a bug in the image writer where having an empty non-null
array caused an assert failure.

Added test.

(cherry-picked from commit adbe2b10d3e9d1cba9c313043941ca591ed109f0)

Bug: 152809035
Test: test-art-host
Merged-In: I0671eef56ae7cbe67bb4ecdbcd72df4e26c8aaa3
Change-Id: I0671eef56ae7cbe67bb4ecdbcd72df4e26c8aaa3
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index db0eb8a..372ee35 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -2185,6 +2185,71 @@
                                   /*use_zip_fd=*/ true));
 }
 
+TEST_F(Dex2oatTest, AppImageEmptyDex) {
+  // Create a profile with the startup method marked.
+  ScratchFile profile_file;
+  ScratchFile temp_dex;
+  const std::string& dex_location = temp_dex.GetFilename();
+  std::vector<uint16_t> methods;
+  std::vector<dex::TypeIndex> classes;
+  {
+    MutateDexFile(temp_dex.GetFile(), GetTestDexFileName("StringLiterals"), [&] (DexFile* dex) {
+      // Modify the header to make the dex file valid but empty.
+      DexFile::Header* header = const_cast<DexFile::Header*>(&dex->GetHeader());
+      header->string_ids_size_ = 0;
+      header->string_ids_off_ = 0;
+      header->type_ids_size_ = 0;
+      header->type_ids_off_ = 0;
+      header->proto_ids_size_ = 0;
+      header->proto_ids_off_ = 0;
+      header->field_ids_size_ = 0;
+      header->field_ids_off_ = 0;
+      header->method_ids_size_ = 0;
+      header->method_ids_off_ = 0;
+      header->class_defs_size_ = 0;
+      header->class_defs_off_ = 0;
+      ASSERT_GT(header->file_size_,
+                sizeof(*header) + sizeof(dex::MapList) + sizeof(dex::MapItem) * 2);
+      // Move map list to be right after the header.
+      header->map_off_ = sizeof(DexFile::Header);
+      dex::MapList* map_list = const_cast<dex::MapList*>(dex->GetMapList());
+      map_list->list_[0].type_ = DexFile::kDexTypeHeaderItem;
+      map_list->list_[0].size_ = 1u;
+      map_list->list_[0].offset_ = 0u;
+      map_list->list_[1].type_ = DexFile::kDexTypeMapList;
+      map_list->list_[1].size_ = 1u;
+      map_list->list_[1].offset_ = header->map_off_;
+      map_list->size_ = 2;
+      header->data_off_ = header->map_off_;
+      header->data_size_ = map_list->Size();
+    });
+  }
+  std::unique_ptr<const DexFile> dex_file(OpenDexFile(temp_dex.GetFilename().c_str()));
+  const std::string out_dir = GetScratchDir();
+  const std::string odex_location = out_dir + "/base.odex";
+  const std::string app_image_location = out_dir + "/base.art";
+  ASSERT_TRUE(GenerateOdexForTest(dex_location,
+                                  odex_location,
+                                  CompilerFilter::Filter::kSpeedProfile,
+                                  { "--app-image-file=" + app_image_location,
+                                    "--resolve-startup-const-strings=true",
+                                    "--profile-file=" + profile_file.GetFilename()},
+                                  /*expect_success=*/ true,
+                                  /*use_fd=*/ false,
+                                  /*use_zip_fd=*/ false,
+                                  [](const OatFile&) {}));
+  // Open our generated oat file.
+  std::string error_msg;
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
+                                                   odex_location.c_str(),
+                                                   odex_location.c_str(),
+                                                   /*executable=*/ false,
+                                                   /*low_4gb=*/ false,
+                                                   &error_msg));
+  ASSERT_TRUE(odex_file != nullptr);
+}
+
+
 TEST_F(Dex2oatTest, AppImageResolveStrings) {
   using Hotness = ProfileCompilationInfo::MethodHotness;
   // Create a profile with the startup method marked.
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 9efcf16..b7776ff 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -682,7 +682,11 @@
         << "Dex cache should have been pruned " << dex_file->GetLocation()
         << "; possibly in class path";
     DexCacheArraysLayout layout(target_ptr_size_, dex_file);
-    DCHECK(layout.Valid());
+    // Empty dex files will not have a "valid" DexCacheArraysLayout.
+    if (dex_file->NumTypeIds() + dex_file->NumStringIds() + dex_file->NumMethodIds() +
+        dex_file->NumFieldIds() + dex_file->NumProtoIds() + dex_file->NumCallSiteIds() != 0) {
+      DCHECK(layout.Valid());
+    }
     size_t oat_index = GetOatIndexForDexFile(dex_file);
     ImageInfo& image_info = GetImageInfo(oat_index);
     uint32_t start = image_info.dex_cache_array_starts_.Get(dex_file);
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index eab26d8..40997f6 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -220,18 +220,20 @@
   Thread* const self = Thread::Current();
   LinearAlloc* linear_alloc = Runtime::Current()->GetLinearAlloc();
   const size_t num_strings = GetDexFile()->NumStringIds();
-  GcRoot<mirror::String>* strings =
-      linear_alloc->AllocArray<GcRoot<mirror::String>>(self, num_strings);
-  if (strings == nullptr) {
-    // Failed to allocate pre-resolved string array (probably due to address fragmentation), bail.
-    return false;
-  }
-  SetField32<false>(NumPreResolvedStringsOffset(), num_strings);
+  if (num_strings != 0) {
+    GcRoot<mirror::String>* strings =
+        linear_alloc->AllocArray<GcRoot<mirror::String>>(self, num_strings);
+    if (strings == nullptr) {
+      // Failed to allocate pre-resolved string array (probably due to address fragmentation), bail.
+      return false;
+    }
+    SetField32<false>(NumPreResolvedStringsOffset(), num_strings);
 
-  CHECK(strings != nullptr);
-  SetPreResolvedStrings(strings);
-  for (size_t i = 0; i < GetDexFile()->NumStringIds(); ++i) {
-    CHECK(GetPreResolvedStrings()[i].Read() == nullptr);
+    CHECK(strings != nullptr);
+    SetPreResolvedStrings(strings);
+    for (size_t i = 0; i < GetDexFile()->NumStringIds(); ++i) {
+      CHECK(GetPreResolvedStrings()[i].Read() == nullptr);
+    }
   }
   return true;
 }