ART: Add odex IMT dumping to oatdump

Support scanning oat files besides the boot image.

Bug: 31594153
Test: m test-art-host
Change-Id: I672d0534b8a8274a4430217656b68c55e4b31cc4
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index f2062c3..c7fd12a 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -45,6 +45,7 @@
 #include "image-inl.h"
 #include "imtable-inl.h"
 #include "indenter.h"
+#include "interpreter/unstarted_runtime.h"
 #include "linker/buffered_output_stream.h"
 #include "linker/file_output_stream.h"
 #include "mirror/array-inl.h"
@@ -2448,39 +2449,55 @@
   return EXIT_SUCCESS;
 }
 
-static int DumpOatWithRuntime(Runtime* runtime, OatFile* oat_file, OatDumperOptions* options,
-                              std::ostream* os) {
-  CHECK(runtime != nullptr && oat_file != nullptr && options != nullptr);
-
+static jobject InstallOatFile(Runtime* runtime,
+                              std::unique_ptr<OatFile> oat_file,
+                              std::vector<const DexFile*>* class_path)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
   Thread* self = Thread::Current();
   CHECK(self != nullptr);
   // Need well-known-classes.
   WellKnownClasses::Init(self->GetJniEnv());
 
   // Need to register dex files to get a working dex cache.
-  ScopedObjectAccess soa(self);
+  OatFile* oat_file_ptr = oat_file.get();
   ClassLinker* class_linker = runtime->GetClassLinker();
-  runtime->GetOatFileManager().RegisterOatFile(std::unique_ptr<const OatFile>(oat_file));
-  std::vector<const DexFile*> class_path;
-  for (const OatFile::OatDexFile* odf : oat_file->GetOatDexFiles()) {
+  runtime->GetOatFileManager().RegisterOatFile(std::move(oat_file));
+  for (const OatFile::OatDexFile* odf : oat_file_ptr->GetOatDexFiles()) {
     std::string error_msg;
     const DexFile* const dex_file = OpenDexFile(odf, &error_msg);
     CHECK(dex_file != nullptr) << error_msg;
     class_linker->RegisterDexFile(*dex_file, nullptr);
-    class_path.push_back(dex_file);
+    class_path->push_back(dex_file);
   }
 
-  // Need a class loader.
-  // Fake that we're a compiler.
-  jobject class_loader = class_linker->CreatePathClassLoader(self, class_path);
+  // Need a class loader. Fake that we're a compiler.
+  // Note: this will run initializers through the unstarted runtime, so make sure it's
+  //       initialized.
+  interpreter::UnstartedRuntime::Initialize();
+
+  jobject class_loader = class_linker->CreatePathClassLoader(self, *class_path);
+
+  return class_loader;
+}
+
+static int DumpOatWithRuntime(Runtime* runtime,
+                              std::unique_ptr<OatFile> oat_file,
+                              OatDumperOptions* options,
+                              std::ostream* os) {
+  CHECK(runtime != nullptr && oat_file != nullptr && options != nullptr);
+  ScopedObjectAccess soa(Thread::Current());
+
+  OatFile* oat_file_ptr = oat_file.get();
+  std::vector<const DexFile*> class_path;
+  jobject class_loader = InstallOatFile(runtime, std::move(oat_file), &class_path);
 
   // Use the class loader while dumping.
-  StackHandleScope<1> scope(self);
+  StackHandleScope<1> scope(soa.Self());
   Handle<mirror::ClassLoader> loader_handle = scope.NewHandle(
       soa.Decode<mirror::ClassLoader>(class_loader));
   options->class_loader_ = &loader_handle;
 
-  OatDumper oat_dumper(*oat_file, *options);
+  OatDumper oat_dumper(*oat_file_ptr, *options);
   bool success = oat_dumper.Dump(*os);
   return (success) ? EXIT_SUCCESS : EXIT_FAILURE;
 }
@@ -2499,23 +2516,23 @@
 static int DumpOat(Runtime* runtime, const char* oat_filename, OatDumperOptions* options,
                    std::ostream* os) {
   std::string error_msg;
-  OatFile* oat_file = OatFile::Open(oat_filename,
-                                    oat_filename,
-                                    nullptr,
-                                    nullptr,
-                                    false,
-                                    /*low_4gb*/false,
-                                    nullptr,
-                                    &error_msg);
+  std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_filename,
+                                                  oat_filename,
+                                                  nullptr,
+                                                  nullptr,
+                                                  false,
+                                                  /*low_4gb*/false,
+                                                  nullptr,
+                                                  &error_msg));
   if (oat_file == nullptr) {
     fprintf(stderr, "Failed to open oat file from '%s': %s\n", oat_filename, error_msg.c_str());
     return EXIT_FAILURE;
   }
 
   if (runtime != nullptr) {
-    return DumpOatWithRuntime(runtime, oat_file, options, os);
+    return DumpOatWithRuntime(runtime, std::move(oat_file), options, os);
   } else {
-    return DumpOatWithoutRuntime(oat_file, options, os);
+    return DumpOatWithoutRuntime(oat_file.get(), options, os);
   }
 }
 
@@ -2554,7 +2571,56 @@
 
 class IMTDumper {
  public:
-  static bool DumpImt(Runtime* runtime, const std::string& imt_file) {
+  static bool Dump(Runtime* runtime,
+                   const std::string& imt_file,
+                   bool dump_imt_stats,
+                   const char* oat_filename) {
+    Thread* self = Thread::Current();
+
+    ScopedObjectAccess soa(self);
+    StackHandleScope<1> scope(self);
+    MutableHandle<mirror::ClassLoader> class_loader = scope.NewHandle<mirror::ClassLoader>(nullptr);
+    std::vector<const DexFile*> class_path;
+
+    if (oat_filename != nullptr) {
+      std::string error_msg;
+      std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_filename,
+                                                      oat_filename,
+                                                      nullptr,
+                                                      nullptr,
+                                                      false,
+                                                      /*low_4gb*/false,
+                                                      nullptr,
+                                                      &error_msg));
+      if (oat_file == nullptr) {
+        fprintf(stderr, "Failed to open oat file from '%s': %s\n", oat_filename, error_msg.c_str());
+        return false;
+      }
+
+      class_loader.Assign(soa.Decode<mirror::ClassLoader>(
+          InstallOatFile(runtime, std::move(oat_file), &class_path)));
+    } else {
+      class_loader.Assign(nullptr);  // Boot classloader. Just here for explicit documentation.
+      class_path = runtime->GetClassLinker()->GetBootClassPath();
+    }
+
+    if (!imt_file.empty()) {
+      return DumpImt(runtime, imt_file, class_loader);
+    }
+
+    if (dump_imt_stats) {
+      return DumpImtStats(runtime, class_path, class_loader);
+    }
+
+    LOG(FATAL) << "Should not reach here";
+    UNREACHABLE();
+  }
+
+ private:
+  static bool DumpImt(Runtime* runtime,
+                      const std::string& imt_file,
+                      Handle<mirror::ClassLoader> h_class_loader)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     std::vector<std::string> lines = ReadCommentedInputFromFile(imt_file);
     std::unordered_set<std::string> prepared;
 
@@ -2564,11 +2630,12 @@
       // determine its IMT slot, and check the class' IMT.
       size_t first_space = line.find(' ');
       if (first_space == std::string::npos) {
-        DumpIMTForClass(runtime, line, &prepared);
+        DumpIMTForClass(runtime, line, h_class_loader, &prepared);
       } else {
         DumpIMTForMethod(runtime,
                          line.substr(0, first_space),
                          line.substr(first_space + 1, std::string::npos),
+                         h_class_loader,
                          &prepared);
       }
       std::cerr << std::endl;
@@ -2577,9 +2644,12 @@
     return true;
   }
 
-  static bool DumpImtStats(Runtime* runtime, const std::vector<const DexFile*>& dex_files) {
-    size_t wo_imt = 0;
-    size_t w_imt = 0;
+  static bool DumpImtStats(Runtime* runtime,
+                           const std::vector<const DexFile*>& dex_files,
+                           Handle<mirror::ClassLoader> h_class_loader)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    size_t without_imt = 0;
+    size_t with_imt = 0;
     std::map<size_t, size_t> histogram;
 
     ClassLinker* class_linker = runtime->GetClassLinker();
@@ -2587,37 +2657,34 @@
     std::unordered_set<std::string> prepared;
 
     Thread* self = Thread::Current();
-    ScopedObjectAccess soa(self);
     StackHandleScope<1> scope(self);
     MutableHandle<mirror::Class> h_klass(scope.NewHandle<mirror::Class>(nullptr));
 
     for (const DexFile* dex_file : dex_files) {
       for (uint32_t class_def_index = 0;
-          class_def_index != dex_file->NumClassDefs();
-          ++class_def_index) {
+           class_def_index != dex_file->NumClassDefs();
+           ++class_def_index) {
         const DexFile::ClassDef& class_def = dex_file->GetClassDef(class_def_index);
         const char* descriptor = dex_file->GetClassDescriptor(class_def);
-        h_klass.Assign(class_linker->FindClass(self,
-                                               descriptor,
-                                               ScopedNullHandle<mirror::ClassLoader>()));
+        h_klass.Assign(class_linker->FindClass(self, descriptor, h_class_loader));
         if (h_klass.Get() == nullptr) {
           std::cerr << "Warning: could not load " << descriptor << std::endl;
           continue;
         }
 
         if (HasNoIMT(runtime, h_klass, pointer_size, &prepared)) {
-          wo_imt++;
+          without_imt++;
           continue;
         }
 
         ImTable* im_table = PrepareAndGetImTable(runtime, h_klass, pointer_size, &prepared);
         if (im_table == nullptr) {
           // Should not happen, but accept.
-          wo_imt++;
+          without_imt++;
           continue;
         }
 
-        w_imt++;
+        with_imt++;
         for (size_t imt_index = 0; imt_index != ImTable::kSize; ++imt_index) {
           ArtMethod* ptr = im_table->Get(imt_index, pointer_size);
           if (ptr->IsRuntimeMethod()) {
@@ -2637,9 +2704,9 @@
     std::cerr << "IMT stats:"
               << std::endl << std::endl;
 
-    std::cerr << "  " << w_imt << " classes with IMT."
+    std::cerr << "  " << with_imt << " classes with IMT."
               << std::endl << std::endl;
-    std::cerr << "  " << wo_imt << " classes without IMT (or copy from Object)."
+    std::cerr << "  " << without_imt << " classes without IMT (or copy from Object)."
               << std::endl << std::endl;
 
     double sum_one = 0;
@@ -2662,8 +2729,7 @@
     return true;
   }
 
- private:
-  // Check whether the given class has no IMT (or the one shared with java.lang.Object).
+  // Return whether the given class has no IMT (or the one shared with java.lang.Object).
   static bool HasNoIMT(Runtime* runtime,
                        Handle<mirror::Class> klass,
                        const PointerSize pointer_size,
@@ -2708,6 +2774,7 @@
 
   static ImTable* PrepareAndGetImTable(Runtime* runtime,
                                        Thread* self,
+                                       Handle<mirror::ClassLoader> h_loader,
                                        const std::string& class_name,
                                        const PointerSize pointer_size,
                                        mirror::Class** klass_out,
@@ -2724,10 +2791,7 @@
       descriptor = DotToDescriptor(class_name.c_str());
     }
 
-    ScopedNullHandle<mirror::ClassLoader> null_handle;
-
-    mirror::Class* klass =
-        runtime->GetClassLinker()->FindClass(self, descriptor.c_str(), null_handle);
+    mirror::Class* klass = runtime->GetClassLinker()->FindClass(self, descriptor.c_str(), h_loader);
 
     if (klass == nullptr) {
       self->ClearException();
@@ -2755,13 +2819,18 @@
 
   static void DumpIMTForClass(Runtime* runtime,
                               const std::string& class_name,
-                              std::unordered_set<std::string>* prepared) {
-    Thread* self = Thread::Current();
-    ScopedObjectAccess soa(self);
-
+                              Handle<mirror::ClassLoader> h_loader,
+                              std::unordered_set<std::string>* prepared)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     const PointerSize pointer_size = runtime->GetClassLinker()->GetImagePointerSize();
     mirror::Class* klass;
-    ImTable* imt = PrepareAndGetImTable(runtime, self, class_name, pointer_size, &klass, prepared);
+    ImTable* imt = PrepareAndGetImTable(runtime,
+                                        Thread::Current(),
+                                        h_loader,
+                                        class_name,
+                                        pointer_size,
+                                        &klass,
+                                        prepared);
     if (imt == nullptr) {
       return;
     }
@@ -2804,14 +2873,14 @@
   static void DumpIMTForMethod(Runtime* runtime,
                                const std::string& class_name,
                                const std::string& method,
-                               std::unordered_set<std::string>* prepared) {
-    Thread* self = Thread::Current();
-    ScopedObjectAccess soa(self);
-
+                               Handle<mirror::ClassLoader> h_loader,
+                               std::unordered_set<std::string>* prepared)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     const PointerSize pointer_size = runtime->GetClassLinker()->GetImagePointerSize();
     mirror::Class* klass;
     ImTable* imt = PrepareAndGetImTable(runtime,
-                                        self,
+                                        Thread::Current(),
+                                        h_loader,
                                         class_name,
                                         pointer_size,
                                         &klass,
@@ -3090,9 +3159,9 @@
         "\n"
         "  --dump-imt=<file.txt>: output IMT collisions (if any) for the given receiver\n"
         "                         types and interface methods in the given file. The file\n"
-        "                         is read line-wise, and each line should either be a class\n"
+        "                         is read line-wise, where each line should either be a class\n"
         "                         name or descriptor, or a class name/descriptor and a prefix\n"
-        "                         of a complete method name.\n"
+        "                         of a complete method name (separated by a whitespace).\n"
         "      Example: --dump-imt=imt.txt\n"
         "\n"
         "  --dump-imt-stats: output IMT statistics for the given boot image\n"
@@ -3176,12 +3245,11 @@
   virtual bool ExecuteWithRuntime(Runtime* runtime) {
     CHECK(args_ != nullptr);
 
-    if (!args_->imt_dump_.empty()) {
-      return IMTDumper::DumpImt(runtime, args_->imt_dump_);
-    }
-
-    if (args_->imt_stat_dump_) {
-      return IMTDumper::DumpImtStats(runtime, runtime->GetClassLinker()->GetBootClassPath());
+    if (!args_->imt_dump_.empty() || args_->imt_stat_dump_) {
+      return IMTDumper::Dump(runtime,
+                             args_->imt_dump_,
+                             args_->imt_stat_dump_,
+                             args_->oat_filename_);
     }
 
     if (args_->oat_filename_ != nullptr) {