Support array classes in runtime app image.

Test: 845-data-image
Bug: 260557058
Change-Id: Iebe377f655b7aa364bafcb10d4c8dcaa8f32521a
diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc
index 6739aad..f999569 100644
--- a/runtime/runtime_image.cc
+++ b/runtime/runtime_image.cc
@@ -477,12 +477,23 @@
       if (cls == nullptr) {
         return true;
       }
-      const dex::ClassDef* class_def = cls->GetClassDef();
-      if (class_def == nullptr) {
-        // Covers array classes and proxy classes.
-        // TODO: Handle these differently.
+      if (cls->IsProxyClass()) {
         return false;
       }
+      if (cls->IsArrayClass()) {
+        if (cls->IsBootStrapClassLoaded()) {
+          // For boot classpath arrays, we can only emit them if they are
+          // in the boot image already.
+          return helper_->IsInBootImage(cls.Get());
+        }
+        ObjPtr<mirror::Class> temp = cls.Get();
+        while ((temp = temp->GetComponentType())->IsArrayClass()) {}
+        StackHandleScope<1> hs(self_);
+        Handle<mirror::Class> other_class = hs.NewHandle(temp);
+        return CanEmit(other_class);
+      }
+      const dex::ClassDef* class_def = cls->GetClassDef();
+      DCHECK_NE(class_def, nullptr);
       auto existing = visited_.find(class_def);
       if (existing != visited_.end()) {
         // Already processed;
@@ -712,6 +723,11 @@
       cls->FixupNativePointers(cls, kRuntimePointerSize, visitor);
       RelocateMethodPointerArrays(cls, visitor);
     }
+    for (auto it : array_classes_) {
+      mirror::Class* cls = reinterpret_cast<mirror::Class*>(&objects_[it.second]);
+      cls->FixupNativePointers(cls, kRuntimePointerSize, visitor);
+      RelocateMethodPointerArrays(cls, visitor);
+    }
     for (auto it : native_relocations_) {
       if (it.second.first == NativeRelocationKind::kImTable) {
         ImTable* im_table = reinterpret_cast<ImTable*>(im_tables_.data() + it.second.second);
@@ -1229,13 +1245,26 @@
   }
 
   uint32_t CopyClass(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
-    const dex::ClassDef* class_def = cls->GetClassDef();
-    auto it = classes_.find(class_def);
-    if (it != classes_.end()) {
-      return it->second;
+    DCHECK(!cls->IsBootStrapClassLoaded());
+    uint32_t offset = 0u;
+    if (cls->IsArrayClass()) {
+      std::string class_name;
+      cls->GetDescriptor(&class_name);
+      auto it = array_classes_.find(class_name);
+      if (it != array_classes_.end()) {
+        return it->second;
+      }
+      offset = CopyObject(cls);
+      array_classes_[class_name] = offset;
+    } else {
+      const dex::ClassDef* class_def = cls->GetClassDef();
+      auto it = classes_.find(class_def);
+      if (it != classes_.end()) {
+        return it->second;
+      }
+      offset = CopyObject(cls);
+      classes_[class_def] = offset;
     }
-    uint32_t offset = CopyObject(cls);
-    classes_[class_def] = offset;
 
     uint32_t hash = cls->DescriptorHash();
     // Save the hash, the `HashSet` implementation requires to find it.
@@ -1249,7 +1278,11 @@
     // Clear internal state.
     mirror::Class* copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset);
     copy->SetClinitThreadId(static_cast<pid_t>(0u));
-    copy->SetStatusInternal(cls->IsVerified() ? ClassStatus::kVerified : ClassStatus::kResolved);
+    if (cls->IsArrayClass()) {
+      DCHECK(copy->IsVisiblyInitialized());
+    } else {
+      copy->SetStatusInternal(cls->IsVerified() ? ClassStatus::kVerified : ClassStatus::kResolved);
+    }
     copy->SetObjectSizeAllocFastPath(std::numeric_limits<uint32_t>::max());
     copy->SetAccessFlags(copy->GetAccessFlags() & ~kAccRecursivelyInitialized);
 
@@ -1417,6 +1450,7 @@
   std::vector<uint32_t> object_offsets_;
 
   std::map<const dex::ClassDef*, uint32_t> classes_;
+  std::map<std::string, uint32_t> array_classes_;
   std::map<const DexFile*, uint32_t> dex_caches_;
   std::map<uint32_t, uint32_t> class_hashes_;
 
diff --git a/test/845-data-image/src-art/Main.java b/test/845-data-image/src-art/Main.java
index 90a3d86..9d802ea 100644
--- a/test/845-data-image/src-art/Main.java
+++ b/test/845-data-image/src-art/Main.java
@@ -18,6 +18,7 @@
 import dalvik.system.VMRuntime;
 import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.Array;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.util.concurrent.CyclicBarrier;
@@ -214,6 +215,18 @@
     assertEquals(3, foo.someMethod());
     assertEquals(42, foo.someDefaultMethod());
 
+    // Test with array classes.
+    assertEquals("[LMain;", Main[].class.getName());
+    assertEquals("[[LMain;", Main[][].class.getName());
+
+    assertEquals("[LMain;", new Main[4].getClass().getName());
+    assertEquals("[[LMain;", new Main[1][2].getClass().getName());
+
+    Main array[] = new Main[] { new Main() };
+    assertEquals("[LMain;", array.getClass().getName());
+
+    assertEquals(Object[][][][].class, Array.newInstance(Object.class, 0, 0, 0, 0).getClass());
+
     // Call all interface methods to trigger the creation of a imt conflict method.
     itf2.defaultMethod1();
     itf2.defaultMethod2();
@@ -274,7 +287,7 @@
     }
   }
 
-  private static void assertEquals(String expected, String actual) {
+  private static void assertEquals(Object expected, Object actual) {
     if (!expected.equals(actual)) {
       throw new Error("Expected \"" + expected + "\", got \"" + actual + "\"");
     }