Merge "Enable PRODUCT_ART_USE_READ_BARRIER by default."
diff --git a/Android.bp b/Android.bp
index b9f1db5..d0e22fb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
     "dexdump",
     "dexlayout",
     "dexlist",
+    "dexoptanalyzer",
     "disassembler",
     "imgdiag",
     "oatdump",
diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk
index e568ce2..6de5aef 100644
--- a/build/Android.common_path.mk
+++ b/build/Android.common_path.mk
@@ -109,6 +109,7 @@
 
 ART_CORE_DEBUGGABLE_EXECUTABLES := \
     dex2oat \
+    dexoptanalyzer \
     imgdiag \
     oatdump \
     patchoat \
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index c87075f..bc08384 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -28,6 +28,7 @@
   DexToDexDecompiler \
   ErroneousA \
   ErroneousB \
+  ErroneousInit \
   ExceptionHandle \
   GetMethodSignature \
   ImageLayoutA \
@@ -87,7 +88,7 @@
 ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested
 
 ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces
-ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode
+ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode
 ART_GTEST_class_table_test_DEX_DEPS := XandY
 ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex
 ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes
@@ -100,6 +101,7 @@
 ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives
 ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods
 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_oat_file_test_DEX_DEPS := Main MultiDex
 ART_GTEST_oat_test_DEX_DEPS := Main
 ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY
@@ -136,6 +138,12 @@
 ART_GTEST_oat_file_assistant_test_TARGET_DEPS := \
   $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS)
 
+ART_GTEST_dexoptanalyzer_test_HOST_DEPS := \
+  $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) \
+  $(HOST_OUT_EXECUTABLES)/dexoptanalyzerd
+ART_GTEST_dexoptanalyzer_test_TARGET_DEPS := \
+  $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) \
+  dexoptanalyzerd
 
 ART_GTEST_dex2oat_test_HOST_DEPS := \
   $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS)
@@ -219,6 +227,7 @@
     art_dexdump_tests \
     art_dexlayout_tests \
     art_dexlist_tests \
+    art_dexoptanalyzer_tests \
     art_imgdiag_tests \
     art_oatdump_tests \
     art_profman_tests \
@@ -614,6 +623,9 @@
 ART_GTEST_oat_file_assistant_test_DEX_DEPS :=
 ART_GTEST_oat_file_assistant_test_HOST_DEPS :=
 ART_GTEST_oat_file_assistant_test_TARGET_DEPS :=
+ART_GTEST_dexoptanalyzer_test_DEX_DEPS :=
+ART_GTEST_dexoptanalyzer_test_HOST_DEPS :=
+ART_GTEST_dexoptanalyzer_test_TARGET_DEPS :=
 ART_GTEST_dex2oat_test_DEX_DEPS :=
 ART_GTEST_dex2oat_test_HOST_DEPS :=
 ART_GTEST_dex2oat_test_TARGET_DEPS :=
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index c03ffca..b00d083 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -2199,7 +2199,7 @@
     if (klass.Get() != nullptr) {
       // Only do this if the class is resolved. If even resolution fails, quickening will go very,
       // very wrong.
-      if (klass->IsResolved()) {
+      if (klass->IsResolved() && !klass->IsErroneousResolved()) {
         if (klass->GetStatus() < mirror::Class::kStatusVerified) {
           ObjectLock<mirror::Class> lock(soa.Self(), klass);
           // Set class status to verified.
@@ -2626,7 +2626,8 @@
 void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status status) {
   switch (status) {
     case mirror::Class::kStatusNotReady:
-    case mirror::Class::kStatusError:
+    case mirror::Class::kStatusErrorResolved:
+    case mirror::Class::kStatusErrorUnresolved:
     case mirror::Class::kStatusRetryVerificationAtRuntime:
     case mirror::Class::kStatusVerified:
     case mirror::Class::kStatusInitialized:
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 459aca3..15e4cd8 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -756,7 +756,7 @@
   bool my_early_exit = false;  // Only for ourselves, ignore caller.
   // Remove classes that failed to verify since we don't want to have java.lang.VerifyError in the
   // app image.
-  if (klass->GetStatus() == mirror::Class::kStatusError) {
+  if (klass->IsErroneous()) {
     result = true;
   } else {
     ObjPtr<mirror::ClassExt> ext(klass->GetExtData());
@@ -777,8 +777,8 @@
                                                   visited);
   }
   // Check static fields and their classes.
-  size_t num_static_fields = klass->NumReferenceStaticFields();
-  if (num_static_fields != 0 && klass->IsResolved()) {
+  if (klass->IsResolved() && klass->NumReferenceStaticFields() != 0) {
+    size_t num_static_fields = klass->NumReferenceStaticFields();
     // Presumably GC can happen when we are cross compiling, it should not cause performance
     // problems to do pointer size logic.
     MemberOffset field_offset = klass->GetFirstReferenceStaticFieldOffset(
@@ -1154,7 +1154,7 @@
       // Visit and assign offsets for fields and field arrays.
       mirror::Class* as_klass = obj->AsClass();
       mirror::DexCache* dex_cache = as_klass->GetDexCache();
-      DCHECK_NE(as_klass->GetStatus(), mirror::Class::kStatusError);
+      DCHECK(!as_klass->IsErroneous()) << as_klass->GetStatus();
       if (compile_app_image_) {
         // Extra sanity, no boot loader classes should be left!
         CHECK(!IsBootClassLoaderClass(as_klass)) << as_klass->PrettyClass();
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index de5af97..bd2c5e3 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -716,7 +716,10 @@
     if (compiled_class != nullptr) {
       status = compiled_class->GetStatus();
     } else if (writer_->compiler_driver_->GetVerificationResults()->IsClassRejected(class_ref)) {
-      status = mirror::Class::kStatusError;
+      // The oat class status is used only for verification of resolved classes,
+      // so use kStatusErrorResolved whether the class was resolved or unresolved
+      // during compile-time verification.
+      status = mirror::Class::kStatusErrorResolved;
     } else {
       status = mirror::Class::kStatusNotReady;
     }
diff --git a/dexoptanalyzer/Android.bp b/dexoptanalyzer/Android.bp
new file mode 100644
index 0000000..cf4c99e
--- /dev/null
+++ b/dexoptanalyzer/Android.bp
@@ -0,0 +1,68 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+    name: "dexoptanalyzer-defaults",
+    host_supported: true,
+    defaults: ["art_defaults"],
+    srcs: [
+        "dexoptanalyzer.cc",
+    ],
+
+    target: {
+        android: {
+            compile_multilib: "prefer32",
+        },
+    },
+
+    include_dirs: [
+        "art/cmdline",
+    ],
+
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+art_cc_binary {
+    name: "dexoptanalyzer",
+    defaults: ["dexoptanalyzer-defaults"],
+    shared_libs: [
+        "libart",
+    ],
+}
+
+art_cc_binary {
+    name: "dexoptanalyzerd",
+    defaults: [
+        "dexoptanalyzer-defaults",
+        "art_debug_defaults",
+    ],
+    shared_libs: [
+        "libartd",
+    ],
+}
+
+art_cc_test {
+    name: "art_dexoptanalyzer_tests",
+    defaults: [
+        "art_gtest_defaults",
+    ],
+    shared_libs: [
+        "libbacktrace"
+    ],
+    srcs: ["dexoptanalyzer_test.cc"],
+}
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
new file mode 100644
index 0000000..965e407
--- /dev/null
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "compiler_filter.h"
+#include "dex_file.h"
+#include "noop_compiler_callbacks.h"
+#include "oat_file_assistant.h"
+#include "os.h"
+#include "runtime.h"
+#include "thread-inl.h"
+#include "utils.h"
+
+namespace art {
+
+// See OatFileAssistant docs for the meaning of the valid return codes.
+enum ReturnCodes {
+  kNoDexOptNeeded = 0,
+  kDex2OatFromScratch = 1,
+  kDex2OatForBootImageOat = 2,
+  kDex2OatForFilterOat = 3,
+  kDex2OatForRelocationOat = 4,
+  kDex2OatForBootImageOdex = 5,
+  kDex2OatForFilterOdex = 6,
+  kDex2OatForRelocationOdex = 7,
+
+  kErrorInvalidArguments = 101,
+  kErrorCannotCreateRuntime = 102,
+  kErrorUnknownDexOptNeeded = 103
+};
+
+static int original_argc;
+static char** original_argv;
+
+static std::string CommandLine() {
+  std::vector<std::string> command;
+  for (int i = 0; i < original_argc; ++i) {
+    command.push_back(original_argv[i]);
+  }
+  return android::base::Join(command, ' ');
+}
+
+static void UsageErrorV(const char* fmt, va_list ap) {
+  std::string error;
+  android::base::StringAppendV(&error, fmt, ap);
+  LOG(ERROR) << error;
+}
+
+static void UsageError(const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+}
+
+NO_RETURN static void Usage(const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+
+  UsageError("Command: %s", CommandLine().c_str());
+  UsageError("  Performs a dexopt analysis on the given dex file and returns whether or not");
+  UsageError("  the dex file needs to be dexopted.");
+  UsageError("Usage: dexoptanalyzer [options]...");
+  UsageError("");
+  UsageError("  --dex-file=<filename>: the dex file which should be analyzed.");
+  UsageError("");
+  UsageError("  --isa=<string>: the instruction set for which the analysis should be performed.");
+  UsageError("");
+  UsageError("  --compiler-filter=<string>: the target compiler filter to be used as reference");
+  UsageError("       when deciding if the dex file needs to be optimized.");
+  UsageError("");
+  UsageError("  --assume-profile-changed: assumes the profile information has changed");
+  UsageError("       when deciding if the dex file needs to be optimized.");
+  UsageError("");
+  UsageError("  --image=<filename>: optional, the image to be used to decide if the associated");
+  UsageError("       oat file is up to date. Defaults to $ANDROID_ROOT/framework/boot.art.");
+  UsageError("       Example: --image=/system/framework/boot.art");
+  UsageError("");
+  UsageError("  --android-data=<directory>: optional, the directory which should be used as");
+  UsageError("       android-data. By default ANDROID_DATA env variable is used.");
+  UsageError("");
+  UsageError("Return code:");
+  UsageError("  To make it easier to integrate with the internal tools this command will make");
+  UsageError("    available its result (dexoptNeeded) as the exit/return code. i.e. it will not");
+  UsageError("    return 0 for success and a non zero values for errors as the conventional");
+  UsageError("    commands. The following return codes are possible:");
+  UsageError("        kNoDexOptNeeded = 0");
+  UsageError("        kDex2OatFromScratch = 1");
+  UsageError("        kDex2OatForBootImageOat = 2");
+  UsageError("        kDex2OatForFilterOat = 3");
+  UsageError("        kDex2OatForRelocationOat = 4");
+  UsageError("        kDex2OatForBootImageOdex = 5");
+  UsageError("        kDex2OatForFilterOdex = 6");
+  UsageError("        kDex2OatForRelocationOdex = 7");
+
+  UsageError("        kErrorInvalidArguments = 101");
+  UsageError("        kErrorCannotCreateRuntime = 102");
+  UsageError("        kErrorUnknownDexOptNeeded = 103");
+  UsageError("");
+
+  exit(kErrorInvalidArguments);
+}
+
+class DexoptAnalyzer FINAL {
+ public:
+  DexoptAnalyzer() : assume_profile_changed_(false) {}
+
+  void ParseArgs(int argc, char **argv) {
+    original_argc = argc;
+    original_argv = argv;
+
+    InitLogging(argv, Runtime::Aborter);
+    // Skip over the command name.
+    argv++;
+    argc--;
+
+    if (argc == 0) {
+      Usage("No arguments specified");
+    }
+
+    for (int i = 0; i < argc; ++i) {
+      const StringPiece option(argv[i]);
+      if (option == "--assume-profile-changed") {
+        assume_profile_changed_ = true;
+      } else if (option.starts_with("--dex-file=")) {
+        dex_file_ = option.substr(strlen("--dex-file=")).ToString();
+      } else if (option.starts_with("--compiler-filter=")) {
+        std::string filter_str = option.substr(strlen("--compiler-filter=")).ToString();
+        if (!CompilerFilter::ParseCompilerFilter(filter_str.c_str(), &compiler_filter_)) {
+          Usage("Invalid compiler filter '%s'", option.data());
+        }
+      } else if (option.starts_with("--isa=")) {
+        std::string isa_str = option.substr(strlen("--isa=")).ToString();
+        isa_ = GetInstructionSetFromString(isa_str.c_str());
+        if (isa_ == kNone) {
+          Usage("Invalid isa '%s'", option.data());
+        }
+      } else if (option.starts_with("--image=")) {
+        image_ = option.substr(strlen("--image=")).ToString();
+      } else if (option.starts_with("--android-data=")) {
+        // Overwrite android-data if needed (oat file assistant relies on a valid directory to
+        // compute dalvik-cache folder). This is mostly used in tests.
+        std::string new_android_data = option.substr(strlen("--android-data=")).ToString();
+        setenv("ANDROID_DATA", new_android_data.c_str(), 1);
+      } else {
+        Usage("Unknown argument '%s'", option.data());
+      }
+    }
+
+    if (image_.empty()) {
+      // If we don't receive the image, try to use the default one.
+      // Tests may specify a different image (e.g. core image).
+      std::string error_msg;
+      image_ = GetDefaultBootImageLocation(&error_msg);
+
+      if (image_.empty()) {
+        LOG(ERROR) << error_msg;
+        Usage("--image unspecified and ANDROID_ROOT not set or image file does not exist.");
+      }
+    }
+  }
+
+  bool CreateRuntime() {
+    RuntimeOptions options;
+    // The image could be custom, so make sure we explicitly pass it.
+    std::string img = "-Ximage:" + image_;
+    options.push_back(std::make_pair(img.c_str(), nullptr));
+    // The instruction set of the image should match the instruction set we will test.
+    const void* isa_opt = reinterpret_cast<const void*>(GetInstructionSetString(isa_));
+    options.push_back(std::make_pair("imageinstructionset", isa_opt));
+     // Disable libsigchain. We don't don't need it to evaluate DexOptNeeded status.
+    options.push_back(std::make_pair("-Xno-sig-chain", nullptr));
+    // Pretend we are a compiler so that we can re-use the same infrastructure to load a different
+    // ISA image and minimize the amount of things that get started.
+    NoopCompilerCallbacks callbacks;
+    options.push_back(std::make_pair("compilercallbacks", &callbacks));
+    // Make sure we don't attempt to relocate. The tool should only retrieve the DexOptNeeded
+    // status and not attempt to relocate the boot image.
+    options.push_back(std::make_pair("-Xnorelocate", nullptr));
+
+    if (!Runtime::Create(options, false)) {
+      LOG(ERROR) << "Unable to initialize runtime";
+      return false;
+    }
+    // Runtime::Create acquired the mutator_lock_ that is normally given away when we
+    // Runtime::Start. Give it away now.
+    Thread::Current()->TransitionFromRunnableToSuspended(kNative);
+
+    return true;
+  }
+
+  int GetDexOptNeeded() {
+    // If the file does not exist there's nothing to do.
+    // This is a fast path to avoid creating the runtime (b/34385298).
+    if (!OS::FileExists(dex_file_.c_str())) {
+      return kNoDexOptNeeded;
+    }
+    if (!CreateRuntime()) {
+      return kErrorCannotCreateRuntime;
+    }
+    OatFileAssistant oat_file_assistant(dex_file_.c_str(), isa_, /*load_executable*/ false);
+    // Always treat elements of the bootclasspath as up-to-date.
+    // TODO(calin): this check should be in OatFileAssistant.
+    if (oat_file_assistant.IsInBootClassPath()) {
+      return kNoDexOptNeeded;
+    }
+    int dexoptNeeded = oat_file_assistant.GetDexOptNeeded(
+        compiler_filter_, assume_profile_changed_);
+
+    // Convert OatFileAssitant codes to dexoptanalyzer codes.
+    switch (dexoptNeeded) {
+      case OatFileAssistant::kNoDexOptNeeded: return kNoDexOptNeeded;
+      case OatFileAssistant::kDex2OatFromScratch: return kDex2OatFromScratch;
+      case OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOat;
+      case OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOat;
+      case OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOat;
+
+      case -OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOdex;
+      case -OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOdex;
+      case -OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOdex;
+      default:
+        LOG(ERROR) << "Unknown dexoptNeeded " << dexoptNeeded;
+        return kErrorUnknownDexOptNeeded;
+    }
+  }
+
+ private:
+  std::string dex_file_;
+  InstructionSet isa_;
+  CompilerFilter::Filter compiler_filter_;
+  bool assume_profile_changed_;
+  std::string image_;
+};
+
+static int dexoptAnalyze(int argc, char** argv) {
+  DexoptAnalyzer analyzer;
+
+  // Parse arguments. Argument mistakes will lead to exit(kErrorInvalidArguments) in UsageError.
+  analyzer.ParseArgs(argc, argv);
+  return analyzer.GetDexOptNeeded();
+}
+
+}  // namespace art
+
+int main(int argc, char **argv) {
+  return art::dexoptAnalyze(argc, argv);
+}
diff --git a/dexoptanalyzer/dexoptanalyzer_test.cc b/dexoptanalyzer/dexoptanalyzer_test.cc
new file mode 100644
index 0000000..57d3f1f
--- /dev/null
+++ b/dexoptanalyzer/dexoptanalyzer_test.cc
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "arch/instruction_set.h"
+#include "compiler_filter.h"
+#include "dexopt_test.h"
+
+namespace art {
+
+class DexoptAnalyzerTest : public DexoptTest {
+ protected:
+  std::string GetDexoptAnalyzerCmd() {
+    std::string file_path = GetTestAndroidRoot();
+    file_path += "/bin/dexoptanalyzer";
+    if (kIsDebugBuild) {
+      file_path += "d";
+    }
+    EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
+    return file_path;
+  }
+
+  int Analyze(const std::string& dex_file,
+              CompilerFilter::Filter compiler_filter,
+              bool assume_profile_changed) {
+    std::string dexoptanalyzer_cmd = GetDexoptAnalyzerCmd();
+    std::vector<std::string> argv_str;
+    argv_str.push_back(dexoptanalyzer_cmd);
+    argv_str.push_back("--dex-file=" + dex_file);
+    argv_str.push_back("--isa=" + std::string(GetInstructionSetString(kRuntimeISA)));
+    argv_str.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(compiler_filter));
+    if (assume_profile_changed) {
+      argv_str.push_back("--assume-profile-changed");
+    }
+    argv_str.push_back("--image=" + GetImageLocation());
+    argv_str.push_back("--android-data=" + android_data_);
+
+    std::string error;
+    return ExecAndReturnCode(argv_str, &error);
+  }
+
+  int DexoptanalyzerToOatFileAssistant(int dexoptanalyzerResult) {
+    switch (dexoptanalyzerResult) {
+      case 0: return OatFileAssistant::kNoDexOptNeeded;
+      case 1: return OatFileAssistant::kDex2OatFromScratch;
+      case 2: return OatFileAssistant::kDex2OatForBootImage;
+      case 3: return OatFileAssistant::kDex2OatForFilter;
+      case 4: return OatFileAssistant::kDex2OatForRelocation;
+      case 5: return -OatFileAssistant::kDex2OatForBootImage;
+      case 6: return -OatFileAssistant::kDex2OatForFilter;
+      case 7: return -OatFileAssistant::kDex2OatForRelocation;
+      default: return dexoptanalyzerResult;
+    }
+  }
+
+  // Verify that the output of dexoptanalyzer for the given arguments is the same
+  // as the output of OatFileAssistant::GetDexOptNeeded.
+  void Verify(const std::string& dex_file,
+              CompilerFilter::Filter compiler_filter,
+              bool assume_profile_changed = false) {
+    int dexoptanalyzerResult = Analyze(dex_file, compiler_filter, assume_profile_changed);
+    dexoptanalyzerResult = DexoptanalyzerToOatFileAssistant(dexoptanalyzerResult);
+    OatFileAssistant oat_file_assistant(dex_file.c_str(), kRuntimeISA, /*load_executable*/ false);
+    int assistantResult = oat_file_assistant.GetDexOptNeeded(
+        compiler_filter, assume_profile_changed);
+    EXPECT_EQ(assistantResult, dexoptanalyzerResult);
+  }
+};
+
+// The tests below exercise the same test case from oat_file_assistant_test.cc.
+
+// Case: We have a DEX file, but no OAT file for it.
+TEST_F(DexoptAnalyzerTest, DexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
+  Copy(GetDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+  Verify(dex_location, CompilerFilter::kSpeedProfile);
+}
+
+// Case: We have a DEX file and up-to-date OAT file for it.
+TEST_F(DexoptAnalyzerTest, OatUpToDate) {
+  std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kEverything);
+}
+
+// Case: We have a DEX file and speed-profile OAT file for it.
+TEST_F(DexoptAnalyzerTest, ProfileOatUpToDate) {
+  std::string dex_location = GetScratchDir() + "/ProfileOatUpToDate.jar";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeedProfile);
+
+  Verify(dex_location, CompilerFilter::kSpeedProfile, false);
+  Verify(dex_location, CompilerFilter::kInterpretOnly, false);
+  Verify(dex_location, CompilerFilter::kSpeedProfile, true);
+  Verify(dex_location, CompilerFilter::kInterpretOnly, true);
+}
+
+// Case: We have a MultiDEX file and up-to-date OAT file for it.
+TEST_F(DexoptAnalyzerTest, MultiDexOatUpToDate) {
+  std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar";
+  Copy(GetMultiDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kSpeed, false);
+}
+
+// Case: We have a MultiDEX file where the secondary dex file is out of date.
+TEST_F(DexoptAnalyzerTest, MultiDexSecondaryOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/MultiDexSecondaryOutOfDate.jar";
+
+  // Compile code for GetMultiDexSrc1.
+  Copy(GetMultiDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  // Now overwrite the dex file with GetMultiDexSrc2 so the secondary checksum
+  // is out of date.
+  Copy(GetMultiDexSrc2(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed, false);
+}
+
+
+// Case: We have a DEX file and an OAT file out of date with respect to the
+// dex checksum.
+TEST_F(DexoptAnalyzerTest, OatDexOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/OatDexOutOfDate.jar";
+
+  // We create a dex, generate an oat for it, then overwrite the dex with a
+  // different dex to make the oat out of date.
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+  Copy(GetDexSrc2(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a DEX file and an OAT file out of date with respect to the
+// boot image.
+TEST_F(DexoptAnalyzerTest, OatImageOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/OatImageOutOfDate.jar";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(),
+                     CompilerFilter::kSpeed,
+                     /*relocate*/true,
+                     /*pic*/false,
+                     /*with_alternate_image*/true);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a DEX file and a verify-at-runtime OAT file out of date with
+// respect to the boot image.
+// It shouldn't matter that the OAT file is out of date, because it is
+// verify-at-runtime.
+TEST_F(DexoptAnalyzerTest, OatVerifyAtRuntimeImageOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/OatVerifyAtRuntimeImageOutOfDate.jar";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(),
+                     CompilerFilter::kVerifyAtRuntime,
+                     /*relocate*/true,
+                     /*pic*/false,
+                     /*with_alternate_image*/true);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+}
+
+// Case: We have a DEX file and an ODEX file, but no OAT file.
+TEST_F(DexoptAnalyzerTest, DexOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a stripped DEX file and a PIC ODEX file, but no OAT file.
+TEST_F(DexoptAnalyzerTest, StrippedDexOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/StrippedDexOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/StrippedDexOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  // Strip the dex file
+  Copy(GetStrippedDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a stripped DEX file, a PIC ODEX file, and an out-of-date OAT file.
+TEST_F(DexoptAnalyzerTest, StrippedDexOdexOat) {
+  std::string dex_location = GetScratchDir() + "/StrippedDexOdexOat.jar";
+  std::string odex_location = GetOdexDir() + "/StrippedDexOdexOat.odex";
+
+  // Create the oat file from a different dex file so it looks out of date.
+  Copy(GetDexSrc2(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  // Create the odex file
+  Copy(GetDexSrc1(), dex_location);
+  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  // Strip the dex file.
+  Copy(GetStrippedDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kEverything);
+}
+
+// Case: We have a stripped (or resource-only) DEX file, no ODEX file and no
+// OAT file. Expect: The status is kNoDexOptNeeded.
+TEST_F(DexoptAnalyzerTest, ResourceOnlyDex) {
+  std::string dex_location = GetScratchDir() + "/ResourceOnlyDex.jar";
+
+  Copy(GetStrippedDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+}
+
+// Case: We have a DEX file, an ODEX file and an OAT file, where the ODEX and
+// OAT files both have patch delta of 0.
+TEST_F(DexoptAnalyzerTest, OdexOatOverlap) {
+  std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar";
+  std::string odex_location = GetOdexDir() + "/OdexOatOverlap.odex";
+  std::string oat_location = GetOdexDir() + "/OdexOatOverlap.oat";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  // Create the oat file by copying the odex so they are located in the same
+  // place in memory.
+  Copy(odex_location, oat_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a DEX file and a PIC ODEX file, but no OAT file.
+TEST_F(DexoptAnalyzerTest, DexPicOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexPicOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/DexPicOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kEverything);
+}
+
+// Case: We have a DEX file and a VerifyAtRuntime ODEX file, but no OAT file..
+TEST_F(DexoptAnalyzerTest, DexVerifyAtRuntimeOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexVerifyAtRuntimeOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/DexVerifyAtRuntimeOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kVerifyAtRuntime);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: Non-standard extension for dex file.
+TEST_F(DexoptAnalyzerTest, LongDexExtension) {
+  std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx";
+  Copy(GetDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: Very short, non-existent Dex location.
+TEST_F(DexoptAnalyzerTest, ShortDexLocation) {
+  std::string dex_location = "/xx";
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+}  // namespace art
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 81f174e..7f98513 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -474,10 +474,14 @@
 art_cc_library {
     name: "libart-runtime-gtest",
     defaults: ["libart-gtest-defaults"],
-    srcs: ["common_runtime_test.cc"],
+    srcs: [
+        "common_runtime_test.cc",
+        "dexopt_test.cc"
+    ],
     shared_libs: [
         "libartd",
         "libbase",
+        "libbacktrace"
     ],
 }
 
diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h
index b9f688d..917db3e 100644
--- a/runtime/art_field-inl.h
+++ b/runtime/art_field-inl.h
@@ -52,7 +52,7 @@
 }
 
 inline MemberOffset ArtField::GetOffset() {
-  DCHECK(GetDeclaringClass()->IsResolved() || GetDeclaringClass()->IsErroneous());
+  DCHECK(GetDeclaringClass()->IsResolved());
   return MemberOffset(offset_);
 }
 
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 15938c5..a35c7ab 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -109,8 +109,7 @@
 }
 
 inline uint16_t ArtMethod::GetMethodIndex() {
-  DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved() ||
-         GetDeclaringClass()->IsErroneous());
+  DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved());
   return method_index_;
 }
 
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index b8ed530..e65672a 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1340,7 +1340,7 @@
           // The image space is not yet added to the heap, avoid read barriers.
           ObjPtr<mirror::Class> klass = types[j].Read();
           if (space->HasAddress(klass.Ptr())) {
-            DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError);
+            DCHECK(!klass->IsErroneous()) << klass->GetStatus();
             auto it = new_class_set->Find(ClassTable::TableSlot(klass));
             DCHECK(it != new_class_set->end());
             DCHECK_EQ(it->Read(), klass);
@@ -1703,7 +1703,7 @@
       for (int32_t j = 0, num_types = h_dex_cache->NumResolvedTypes(); j < num_types; j++) {
         ObjPtr<mirror::Class> klass = types[j].Read();
         if (klass != nullptr) {
-          DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError);
+          DCHECK(!klass->IsErroneous()) << klass->GetStatus();
         }
       }
     } else {
@@ -2232,7 +2232,7 @@
   // For temporary classes we must wait for them to be retired.
   if (init_done_ && klass->IsTemp()) {
     CHECK(!klass->IsResolved());
-    if (klass->IsErroneous()) {
+    if (klass->IsErroneousUnresolved()) {
       ThrowEarlierClassFailure(klass);
       return nullptr;
     }
@@ -2240,10 +2240,10 @@
     Handle<mirror::Class> h_class(hs.NewHandle(klass));
     ObjectLock<mirror::Class> lock(self, h_class);
     // Loop and wait for the resolving thread to retire this class.
-    while (!h_class->IsRetired() && !h_class->IsErroneous()) {
+    while (!h_class->IsRetired() && !h_class->IsErroneousUnresolved()) {
       lock.WaitIgnoringInterrupts();
     }
-    if (h_class->IsErroneous()) {
+    if (h_class->IsErroneousUnresolved()) {
       ThrowEarlierClassFailure(h_class.Get());
       return nullptr;
     }
@@ -2258,7 +2258,7 @@
   static const size_t kNumYieldIterations = 1000;
   // How long each sleep is in us.
   static const size_t kSleepDurationUS = 1000;  // 1 ms.
-  while (!klass->IsResolved() && !klass->IsErroneous()) {
+  while (!klass->IsResolved() && !klass->IsErroneousUnresolved()) {
     StackHandleScope<1> hs(self);
     HandleWrapperObjPtr<mirror::Class> h_class(hs.NewHandleWrapper(&klass));
     {
@@ -2269,7 +2269,7 @@
         // Check for circular dependencies between classes, the lock is required for SetStatus.
         if (!h_class->IsResolved() && h_class->GetClinitThreadId() == self->GetTid()) {
           ThrowClassCircularityError(h_class.Get());
-          mirror::Class::SetStatus(h_class, mirror::Class::kStatusError, self);
+          mirror::Class::SetStatus(h_class, mirror::Class::kStatusErrorUnresolved, self);
           return nullptr;
         }
       }
@@ -2286,7 +2286,7 @@
     ++index;
   }
 
-  if (klass->IsErroneous()) {
+  if (klass->IsErroneousUnresolved()) {
     ThrowEarlierClassFailure(klass);
     return nullptr;
   }
@@ -2687,7 +2687,7 @@
     // An exception occured during load, set status to erroneous while holding klass' lock in case
     // notification is necessary.
     if (!klass->IsErroneous()) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
     }
     return nullptr;
   }
@@ -2697,7 +2697,7 @@
   if (!LoadSuperAndInterfaces(klass, *new_dex_file)) {
     // Loading failed.
     if (!klass->IsErroneous()) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
     }
     return nullptr;
   }
@@ -2716,13 +2716,13 @@
   if (!LinkClass(self, descriptor, klass, interfaces, &h_new_class)) {
     // Linking failed.
     if (!klass->IsErroneous()) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
     }
     return nullptr;
   }
   self->AssertNoPendingException();
   CHECK(h_new_class.Get() != nullptr) << descriptor;
-  CHECK(h_new_class->IsResolved()) << descriptor;
+  CHECK(h_new_class->IsResolved() && !h_new_class->IsErroneousResolved()) << descriptor;
 
   // Instrumentation may have updated entrypoints for all methods of all
   // classes. However it could not update methods of this class while we
@@ -3817,7 +3817,7 @@
   }
   // Need to grab the lock to change status.
   ObjectLock<mirror::Class> super_lock(self, klass);
-  mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+  mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
   return false;
 }
 
@@ -3939,8 +3939,8 @@
   bool preverified = VerifyClassUsingOatFile(dex_file, klass.Get(), oat_file_class_status);
   // If the oat file says the class had an error, re-run the verifier. That way we will get a
   // precise error message. To ensure a rerun, test:
-  //     oat_file_class_status == mirror::Class::kStatusError => !preverified
-  DCHECK(!(oat_file_class_status == mirror::Class::kStatusError) || !preverified);
+  //     mirror::Class::IsErroneous(oat_file_class_status) => !preverified
+  DCHECK(!mirror::Class::IsErroneous(oat_file_class_status) || !preverified);
 
   std::string error_msg;
   verifier::MethodVerifier::FailureKind verifier_failure = verifier::MethodVerifier::kNoFailure;
@@ -3998,7 +3998,7 @@
                   << " because: " << error_msg;
     self->AssertNoPendingException();
     ThrowVerifyError(klass.Get(), "%s", error_msg.c_str());
-    mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+    mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
   }
   if (preverified || verifier_failure == verifier::MethodVerifier::kNoFailure) {
     // Class is verified so we don't need to do any access check on its methods.
@@ -4089,7 +4089,7 @@
     // at compile time).
     return false;
   }
-  if (oat_file_class_status == mirror::Class::kStatusError) {
+  if (mirror::Class::IsErroneous(oat_file_class_status)) {
     // Compile time verification failed with a hard error. This is caused by invalid instructions
     // in the class. These errors are unrecoverable.
     return false;
@@ -4248,7 +4248,7 @@
     Handle<mirror::ObjectArray<mirror::Class>> h_interfaces(
         hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Class>>(interfaces)));
     if (!LinkClass(self, descriptor.c_str(), klass, h_interfaces, &new_class)) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
       return nullptr;
     }
   }
@@ -4463,7 +4463,8 @@
       return false;
     }
 
-    CHECK(klass->IsResolved()) << klass->PrettyClass() << ": state=" << klass->GetStatus();
+    CHECK(klass->IsResolved() && !klass->IsErroneousResolved())
+        << klass->PrettyClass() << ": state=" << klass->GetStatus();
 
     if (!klass->IsVerified()) {
       VerifyClass(self, klass);
@@ -4498,7 +4499,7 @@
       // A separate thread could have moved us all the way to initialized. A "simple" example
       // involves a subclass of the current class being initialized at the same time (which
       // will implicitly initialize the superclass, if scheduled that way). b/28254258
-      DCHECK_NE(mirror::Class::kStatusError, klass->GetStatus());
+      DCHECK(!klass->IsErroneous()) << klass->GetStatus();
       if (klass->IsInitialized()) {
         return true;
       }
@@ -4525,7 +4526,7 @@
     }
 
     if (!ValidateSuperClassDescriptors(klass)) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       return false;
     }
     self->AllowThreadSuspension();
@@ -4561,7 +4562,7 @@
             << (self->GetException() != nullptr ? self->GetException()->Dump() : "");
         ObjectLock<mirror::Class> lock(self, klass);
         // Initialization failed because the super-class is erroneous.
-        mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+        mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
         return false;
       }
     }
@@ -4592,7 +4593,7 @@
         if (!iface_initialized) {
           ObjectLock<mirror::Class> lock(self, klass);
           // Initialization failed because one of our interfaces with default methods is erroneous.
-          mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+          mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
           return false;
         }
       }
@@ -4665,7 +4666,7 @@
 
     if (self->IsExceptionPending()) {
       WrapExceptionInInitializer(klass);
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       success = false;
     } else if (Runtime::Current()->IsTransactionAborted()) {
       // The exception thrown when the transaction aborted has been caught and cleared
@@ -4674,7 +4675,7 @@
                      << mirror::Class::PrettyDescriptor(klass.Get())
                      << " without exception while transaction was aborted: re-throw it now.";
       Runtime::Current()->ThrowTransactionAbortError(self);
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       success = false;
     } else {
       RuntimeStats* global_stats = Runtime::Current()->GetStats();
@@ -4758,7 +4759,7 @@
     // we were not using WaitIgnoringInterrupts), bail out.
     if (self->IsExceptionPending()) {
       WrapExceptionInInitializer(klass);
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       return false;
     }
     // Spurious wakeup? Go back to waiting.
@@ -5169,7 +5170,7 @@
     klass->SetIFieldsPtrUnchecked(nullptr);
     if (UNLIKELY(h_new_class.Get() == nullptr)) {
       self->AssertPendingOOMException();
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
       return false;
     }
 
@@ -7781,7 +7782,7 @@
       }
     }
   }
-  DCHECK((resolved == nullptr) || resolved->IsResolved() || resolved->IsErroneous())
+  DCHECK((resolved == nullptr) || resolved->IsResolved())
       << resolved->PrettyDescriptor() << " " << resolved->GetStatus();
   return resolved.Ptr();
 }
@@ -8516,13 +8517,12 @@
       }
       ++num_resolved;
       DCHECK(!klass->IsProxyClass());
-      if (!klass->IsResolved()) {
-        DCHECK(klass->IsErroneous());
+      DCHECK(klass->IsResolved());
+      if (klass->IsErroneousResolved()) {
         continue;
       }
       ObjPtr<mirror::DexCache> klass_dex_cache = klass->GetDexCache();
       if (klass_dex_cache == dex_cache) {
-        DCHECK(klass->IsResolved());
         CHECK_LT(klass->GetDexClassDefIndex(), num_class_defs);
         class_set.insert(klass->GetDexTypeIndex());
       }
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index d3bb58d..5042fb7 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -1213,7 +1213,7 @@
                               const DexFile& initial_dex_file ATTRIBUTE_UNUSED,
                               const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED,
                               /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED,
-                              /*out*/DexFile::ClassDef const** final_dex_cache ATTRIBUTE_UNUSED)
+                              /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED)
       REQUIRES_SHARED(Locks::mutator_lock_) {}
 
   // A class has been loaded.
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 7b6c0dc..026afdc 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -87,6 +87,7 @@
     EXPECT_FALSE(primitive->IsErroneous());
     EXPECT_TRUE(primitive->IsLoaded());
     EXPECT_TRUE(primitive->IsResolved());
+    EXPECT_FALSE(primitive->IsErroneousResolved());
     EXPECT_TRUE(primitive->IsVerified());
     EXPECT_TRUE(primitive->IsInitialized());
     EXPECT_FALSE(primitive->IsArrayInstance());
@@ -125,6 +126,7 @@
     EXPECT_FALSE(JavaLangObject->IsErroneous());
     EXPECT_TRUE(JavaLangObject->IsLoaded());
     EXPECT_TRUE(JavaLangObject->IsResolved());
+    EXPECT_FALSE(JavaLangObject->IsErroneousResolved());
     EXPECT_TRUE(JavaLangObject->IsVerified());
     EXPECT_TRUE(JavaLangObject->IsInitialized());
     EXPECT_FALSE(JavaLangObject->IsArrayInstance());
@@ -199,6 +201,7 @@
     EXPECT_FALSE(array->IsErroneous());
     EXPECT_TRUE(array->IsLoaded());
     EXPECT_TRUE(array->IsResolved());
+    EXPECT_FALSE(array->IsErroneousResolved());
     EXPECT_TRUE(array->IsVerified());
     EXPECT_TRUE(array->IsInitialized());
     EXPECT_FALSE(array->IsArrayInstance());
@@ -270,6 +273,7 @@
     EXPECT_TRUE(klass->GetDexCache() != nullptr);
     EXPECT_TRUE(klass->IsLoaded());
     EXPECT_TRUE(klass->IsResolved());
+    EXPECT_FALSE(klass->IsErroneousResolved());
     EXPECT_FALSE(klass->IsErroneous());
     EXPECT_FALSE(klass->IsArrayClass());
     EXPECT_TRUE(klass->GetComponentType() == nullptr);
@@ -857,6 +861,7 @@
   EXPECT_FALSE(MyClass->IsErroneous());
   EXPECT_TRUE(MyClass->IsLoaded());
   EXPECT_TRUE(MyClass->IsResolved());
+  EXPECT_FALSE(MyClass->IsErroneousResolved());
   EXPECT_FALSE(MyClass->IsVerified());
   EXPECT_FALSE(MyClass->IsInitialized());
   EXPECT_FALSE(MyClass->IsArrayInstance());
@@ -941,6 +946,47 @@
       array_klass);
 }
 
+TEST_F(ClassLinkerTest, LookupResolvedTypeErroneousInit) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<3> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader(
+      hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("ErroneousInit"))));
+  AssertNonExistentClass("LErroneousInit;");
+  Handle<mirror::Class> klass =
+      hs.NewHandle(class_linker_->FindClass(soa.Self(), "LErroneousInit;", class_loader));
+  ASSERT_OBJ_PTR_NE(klass.Get(), ObjPtr<mirror::Class>(nullptr));
+  dex::TypeIndex type_idx = klass->GetClassDef()->class_idx_;
+  Handle<mirror::DexCache> dex_cache = hs.NewHandle(klass->GetDexCache());
+  const DexFile& dex_file = klass->GetDexFile();
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+  // Zero out the resolved type and make sure LookupResolvedType still finds it.
+  dex_cache->SetResolvedType(type_idx, nullptr);
+  EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+  // Force initialization to turn the class erroneous.
+  bool initialized = class_linker_->EnsureInitialized(soa.Self(),
+                                                      klass,
+                                                      /* can_init_fields */ true,
+                                                      /* can_init_parents */ true);
+  EXPECT_FALSE(initialized);
+  EXPECT_TRUE(soa.Self()->IsExceptionPending());
+  soa.Self()->ClearException();
+  // Check that the LookupResolvedType() can still find the resolved type.
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+  // Zero out the resolved type and make sure LookupResolvedType() still finds it.
+  dex_cache->SetResolvedType(type_idx, nullptr);
+  EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+}
+
 TEST_F(ClassLinkerTest, LibCore) {
   ScopedObjectAccess soa(Thread::Current());
   ASSERT_TRUE(java_lang_dex_file_ != nullptr);
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
new file mode 100644
index 0000000..69c6151
--- /dev/null
+++ b/runtime/dexopt_test.cc
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include <backtrace/BacktraceMap.h>
+#include <gtest/gtest.h>
+
+#include "common_runtime_test.h"
+#include "compiler_callbacks.h"
+#include "dex2oat_environment_test.h"
+#include "dexopt_test.h"
+#include "gc/space/image_space.h"
+#include "mem_map.h"
+
+namespace art {
+void DexoptTest::SetUp() {
+  ReserveImageSpace();
+  Dex2oatEnvironmentTest::SetUp();
+}
+
+void DexoptTest::PreRuntimeCreate() {
+  std::string error_msg;
+  ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg;
+  ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg;
+  UnreserveImageSpace();
+}
+
+void DexoptTest::PostRuntimeCreate() {
+  ReserveImageSpace();
+}
+
+void DexoptTest::GenerateOatForTest(const std::string& dex_location,
+                        const std::string& oat_location,
+                        CompilerFilter::Filter filter,
+                        bool relocate,
+                        bool pic,
+                        bool with_alternate_image) {
+  std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA));
+  std::string dalvik_cache_tmp = dalvik_cache + ".redirected";
+
+  if (!relocate) {
+    // Temporarily redirect the dalvik cache so dex2oat doesn't find the
+    // relocated image file.
+    ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno);
+  }
+
+  std::vector<std::string> args;
+  args.push_back("--dex-file=" + dex_location);
+  args.push_back("--oat-file=" + oat_location);
+  args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
+  args.push_back("--runtime-arg");
+
+  // Use -Xnorelocate regardless of the relocate argument.
+  // We control relocation by redirecting the dalvik cache when needed
+  // rather than use this flag.
+  args.push_back("-Xnorelocate");
+
+  if (pic) {
+    args.push_back("--compile-pic");
+  }
+
+  std::string image_location = GetImageLocation();
+  if (with_alternate_image) {
+    args.push_back("--boot-image=" + GetImageLocation2());
+  }
+
+  std::string error_msg;
+  ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg;
+
+  if (!relocate) {
+    // Restore the dalvik cache if needed.
+    ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno);
+  }
+
+  // Verify the odex file was generated as expected.
+  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.get() != nullptr) << error_msg;
+  EXPECT_EQ(pic, odex_file->IsPic());
+  EXPECT_EQ(filter, odex_file->GetCompilerFilter());
+
+  std::unique_ptr<ImageHeader> image_header(
+          gc::space::ImageSpace::ReadImageHeader(image_location.c_str(),
+                                                 kRuntimeISA,
+                                                 &error_msg));
+  ASSERT_TRUE(image_header != nullptr) << error_msg;
+  const OatHeader& oat_header = odex_file->GetOatHeader();
+  uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum();
+
+  if (CompilerFilter::DependsOnImageChecksum(filter)) {
+    if (with_alternate_image) {
+      EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
+    } else {
+      EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
+    }
+  }
+
+  if (!with_alternate_image) {
+    if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) {
+      if (relocate) {
+        EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
+            oat_header.GetImageFileLocationOatDataBegin());
+        EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
+      } else {
+        EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
+            oat_header.GetImageFileLocationOatDataBegin());
+        EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
+      }
+    }
+  }
+}
+
+void DexoptTest::GenerateOdexForTest(const std::string& dex_location,
+                         const std::string& odex_location,
+                         CompilerFilter::Filter filter) {
+  GenerateOatForTest(dex_location,
+                     odex_location,
+                     filter,
+                     /*relocate*/false,
+                     /*pic*/false,
+                     /*with_alternate_image*/false);
+}
+
+void DexoptTest::GeneratePicOdexForTest(const std::string& dex_location,
+                            const std::string& odex_location,
+                            CompilerFilter::Filter filter) {
+  GenerateOatForTest(dex_location,
+                     odex_location,
+                     filter,
+                     /*relocate*/false,
+                     /*pic*/true,
+                     /*with_alternate_image*/false);
+}
+
+void DexoptTest::GenerateOatForTest(const char* dex_location,
+                        CompilerFilter::Filter filter,
+                        bool relocate,
+                        bool pic,
+                        bool with_alternate_image) {
+  std::string oat_location;
+  std::string error_msg;
+  ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
+        dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
+  GenerateOatForTest(dex_location,
+                     oat_location,
+                     filter,
+                     relocate,
+                     pic,
+                     with_alternate_image);
+}
+
+void DexoptTest::GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) {
+  GenerateOatForTest(dex_location,
+                     filter,
+                     /*relocate*/true,
+                     /*pic*/false,
+                     /*with_alternate_image*/false);
+}
+
+bool DexoptTest::PreRelocateImage(const std::string& image_location, std::string* error_msg) {
+  std::string image;
+  if (!GetCachedImageFile(image_location, &image, error_msg)) {
+    return false;
+  }
+
+  std::string patchoat = GetAndroidRoot();
+  patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat";
+
+  std::vector<std::string> argv;
+  argv.push_back(patchoat);
+  argv.push_back("--input-image-location=" + image_location);
+  argv.push_back("--output-image-file=" + image);
+  argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA)));
+  argv.push_back("--base-offset-delta=0x00008000");
+  return Exec(argv, error_msg);
+}
+
+void DexoptTest::ReserveImageSpace() {
+  MemMap::Init();
+
+  // Ensure a chunk of memory is reserved for the image space.
+  // The reservation_end includes room for the main space that has to come
+  // right after the image in case of the GSS collector.
+  uintptr_t reservation_start = ART_BASE_ADDRESS;
+  uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB;
+
+  std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true));
+  ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map";
+  for (BacktraceMap::const_iterator it = map->begin();
+      reservation_start < reservation_end && it != map->end(); ++it) {
+    ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end));
+    reservation_start = std::max(reservation_start, it->end);
+  }
+  ReserveImageSpaceChunk(reservation_start, reservation_end);
+}
+
+void DexoptTest::ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) {
+  if (start < end) {
+    std::string error_msg;
+    image_reservation_.push_back(std::unique_ptr<MemMap>(
+        MemMap::MapAnonymous("image reservation",
+            reinterpret_cast<uint8_t*>(start), end - start,
+            PROT_NONE, false, false, &error_msg)));
+    ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg;
+    LOG(INFO) << "Reserved space for image " <<
+      reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" <<
+      reinterpret_cast<void*>(image_reservation_.back()->End());
+  }
+}
+
+void DexoptTest::UnreserveImageSpace() {
+  image_reservation_.clear();
+}
+
+}  // namespace art
diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h
new file mode 100644
index 0000000..5f0eafd
--- /dev/null
+++ b/runtime/dexopt_test.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_DEXOPT_TEST_H_
+#define ART_RUNTIME_DEXOPT_TEST_H_
+
+#include <string>
+#include <vector>
+
+#include "dex2oat_environment_test.h"
+
+namespace art {
+
+class DexoptTest : public Dex2oatEnvironmentTest {
+ public:
+  virtual void SetUp() OVERRIDE;
+
+  virtual void PreRuntimeCreate();
+
+  virtual void PostRuntimeCreate() OVERRIDE;
+
+  // Generate an oat file for the purposes of test.
+  // The oat file will be generated for dex_location in the given oat_location
+  // with the following configuration:
+  //   filter - controls the compilation filter
+  //   pic - whether or not the code will be PIC
+  //   relocate - if true, the oat file will be relocated with respect to the
+  //      boot image. Otherwise the oat file will not be relocated.
+  //   with_alternate_image - if true, the oat file will be generated with an
+  //      image checksum different than the current image checksum.
+  void GenerateOatForTest(const std::string& dex_location,
+                          const std::string& oat_location,
+                          CompilerFilter::Filter filter,
+                          bool relocate,
+                          bool pic,
+                          bool with_alternate_image);
+
+  // Generate a non-PIC odex file for the purposes of test.
+  // The generated odex file will be un-relocated.
+  void GenerateOdexForTest(const std::string& dex_location,
+                           const std::string& odex_location,
+                           CompilerFilter::Filter filter);
+
+  void GeneratePicOdexForTest(const std::string& dex_location,
+                              const std::string& odex_location,
+                              CompilerFilter::Filter filter);
+
+  // Generate an oat file for the given dex location in its oat location (under
+  // the dalvik cache).
+  void GenerateOatForTest(const char* dex_location,
+                          CompilerFilter::Filter filter,
+                          bool relocate,
+                          bool pic,
+                          bool with_alternate_image);
+
+  // Generate a standard oat file in the oat location.
+  void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter);
+
+ private:
+  // Pre-Relocate the image to a known non-zero offset so we don't have to
+  // deal with the runtime randomly relocating the image by 0 and messing up
+  // the expected results of the tests.
+  bool PreRelocateImage(const std::string& image_location, std::string* error_msg);
+
+  // Reserve memory around where the image will be loaded so other memory
+  // won't conflict when it comes time to load the image.
+  // This can be called with an already loaded image to reserve the space
+  // around it.
+  void ReserveImageSpace();
+
+  // Reserve a chunk of memory for the image space in the given range.
+  // Only has effect for chunks with a positive number of bytes.
+  void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end);
+
+  // Unreserve any memory reserved by ReserveImageSpace. This should be called
+  // before the image is loaded.
+  void UnreserveImageSpace();
+
+  std::vector<std::unique_ptr<MemMap>> image_reservation_;
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_DEXOPT_TEST_H_
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 268cca0..7044979 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -3543,8 +3543,11 @@
   collector::GcType gc_type = collector_ran->GetGcType();
   const double multiplier = HeapGrowthMultiplier();  // Use the multiplier to grow more for
   // foreground.
-  const uint64_t adjusted_min_free = static_cast<uint64_t>(min_free_ * multiplier);
-  const uint64_t adjusted_max_free = static_cast<uint64_t>(max_free_ * multiplier);
+  // Ensure at least 2.5 MB to temporarily fix excessive GC caused by TLAB ergonomics.
+  const uint64_t adjusted_min_free = std::max(static_cast<uint64_t>(min_free_ * multiplier),
+                                              static_cast<uint64_t>(5 * MB / 2));
+  const uint64_t adjusted_max_free = std::max(static_cast<uint64_t>(max_free_ * multiplier),
+                                              static_cast<uint64_t>(5 * MB / 2));
   if (gc_type != collector::kGcTypeSticky) {
     // Grow the heap for non sticky GC.
     ssize_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated;
diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc
index fe6a6e9..3d3ad59 100644
--- a/runtime/hprof/hprof.cc
+++ b/runtime/hprof/hprof.cc
@@ -1169,7 +1169,7 @@
 }
 
 void Hprof::DumpHeapClass(mirror::Class* klass) {
-  if (!klass->IsResolved() && !klass->IsErroneous()) {
+  if (!klass->IsResolved()) {
     // Class is allocated but not yet resolved: we cannot access its fields or super class.
     return;
   }
diff --git a/runtime/image.cc b/runtime/image.cc
index 6d88895..54b099e 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -25,7 +25,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '5', '\0' };  // ArtMethod update
+const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '6', '\0' };  // Erroneous resolved class.
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 4ea1130..bbd6d35 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -88,11 +88,11 @@
 }
 
 void Instrumentation::InstallStubsForClass(mirror::Class* klass) {
-  if (klass->IsErroneous()) {
-    // We can't execute code in a erroneous class: do nothing.
-  } else if (!klass->IsResolved()) {
+  if (!klass->IsResolved()) {
     // We need the class to be resolved to install/uninstall stubs. Otherwise its methods
     // could not be initialized or linked with regards to class inheritance.
+  } else if (klass->IsErroneousResolved()) {
+    // We can't execute code in a erroneous class: do nothing.
   } else {
     for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) {
       InstallStubsForMethod(&method);
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 9964b73..f08d4da 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -115,7 +115,9 @@
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   bool class_linker_initialized = class_linker != nullptr && class_linker->IsInitialized();
   if (LIKELY(class_linker_initialized)) {
-    if (UNLIKELY(new_status <= old_status && new_status != kStatusError &&
+    if (UNLIKELY(new_status <= old_status &&
+                 new_status != kStatusErrorUnresolved &&
+                 new_status != kStatusErrorResolved &&
                  new_status != kStatusRetired)) {
       LOG(FATAL) << "Unexpected change back of class status for " << h_this->PrettyClass()
                  << " " << old_status << " -> " << new_status;
@@ -127,10 +129,12 @@
             << h_this->PrettyClass() << " " << old_status << " -> " << new_status;
     }
   }
-  if (UNLIKELY(new_status == kStatusError)) {
-    CHECK_NE(h_this->GetStatus(), kStatusError)
+  if (UNLIKELY(IsErroneous(new_status))) {
+    CHECK(!h_this->IsErroneous())
         << "Attempt to set as erroneous an already erroneous class "
-        << h_this->PrettyClass();
+        << h_this->PrettyClass()
+        << " old_status: " << old_status << " new_status: " << new_status;
+    CHECK_EQ(new_status == kStatusErrorResolved, old_status >= kStatusResolved);
     if (VLOG_IS_ON(class_linker)) {
       LOG(ERROR) << "Setting " << h_this->PrettyDescriptor() << " to erroneous.";
       if (self->IsExceptionPending()) {
@@ -177,7 +181,7 @@
       // Class is a temporary one, ensure that waiters for resolution get notified of retirement
       // so that they can grab the new version of the class from the class linker's table.
       CHECK_LT(new_status, kStatusResolved) << h_this->PrettyDescriptor();
-      if (new_status == kStatusRetired || new_status == kStatusError) {
+      if (new_status == kStatusRetired || new_status == kStatusErrorUnresolved) {
         h_this->NotifyAll(self);
       }
     } else {
@@ -305,7 +309,7 @@
     }
     if (h_this->NumStaticFields() > 0) {
       os << "  static fields (" << h_this->NumStaticFields() << " entries):\n";
-      if (h_this->IsResolved() || h_this->IsErroneous()) {
+      if (h_this->IsResolved()) {
         for (size_t i = 0; i < h_this->NumStaticFields(); ++i) {
           os << StringPrintf("    %2zd: %s\n", i,
                              ArtField::PrettyField(h_this->GetStaticField(i)).c_str());
@@ -316,7 +320,7 @@
     }
     if (h_this->NumInstanceFields() > 0) {
       os << "  instance fields (" << h_this->NumInstanceFields() << " entries):\n";
-      if (h_this->IsResolved() || h_this->IsErroneous()) {
+      if (h_this->IsResolved()) {
         for (size_t i = 0; i < h_this->NumInstanceFields(); ++i) {
           os << StringPrintf("    %2zd: %s\n", i,
                              ArtField::PrettyField(h_this->GetInstanceField(i)).c_str());
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index fb2792a..7f6aa12 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -84,6 +84,13 @@
   // will be gc'ed once all refs to the class point to the newly
   // cloned version.
   //
+  // kStatusErrorUnresolved, kStatusErrorResolved: Class is erroneous. We need
+  // to distinguish between classes that have been resolved and classes that
+  // have not. This is important because the const-class instruction needs to
+  // return a previously resolved class even if its subsequent initialization
+  // failed. We also need this to decide whether to wrap a previous
+  // initialization failure in ClassDefNotFound error or not.
+  //
   // kStatusNotReady: If a Class cannot be found in the class table by
   // FindClass, it allocates an new one with AllocClass in the
   // kStatusNotReady and calls LoadClass. Note if it does find a
@@ -119,8 +126,9 @@
   //
   // TODO: Explain the other states
   enum Status {
-    kStatusRetired = -2,  // Retired, should not be used. Use the newly cloned one instead.
-    kStatusError = -1,
+    kStatusRetired = -3,  // Retired, should not be used. Use the newly cloned one instead.
+    kStatusErrorResolved = -2,
+    kStatusErrorUnresolved = -1,
     kStatusNotReady = 0,
     kStatusIdx = 1,  // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_.
     kStatusLoaded = 2,  // DEX idx values resolved.
@@ -158,8 +166,25 @@
 
   // Returns true if the class has failed to link.
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  bool IsErroneousUnresolved() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetStatus<kVerifyFlags>() == kStatusErrorUnresolved;
+  }
+
+  // Returns true if the class has failed to initialize.
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  bool IsErroneousResolved() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetStatus<kVerifyFlags>() == kStatusErrorResolved;
+  }
+
+  // Returns true if the class status indicets that the class has failed to link or initialize.
+  static bool IsErroneous(Status status) {
+    return status == kStatusErrorUnresolved || status == kStatusErrorResolved;
+  }
+
+  // Returns true if the class has failed to link or initialize.
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsErroneous() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetStatus<kVerifyFlags>() == kStatusError;
+    return IsErroneous(GetStatus<kVerifyFlags>());
   }
 
   // Returns true if the class has been loaded.
@@ -177,7 +202,8 @@
   // Returns true if the class has been linked.
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsResolved() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetStatus<kVerifyFlags>() >= kStatusResolved;
+    Status status = GetStatus<kVerifyFlags>();
+    return status >= kStatusResolved || status == kStatusErrorResolved;
   }
 
   // Returns true if the class was compile-time verified.
@@ -345,7 +371,7 @@
   // be replaced with a class with the right size for embedded imt/vtable.
   bool IsTemp() REQUIRES_SHARED(Locks::mutator_lock_) {
     Status s = GetStatus();
-    return s < Status::kStatusResolving && ShouldHaveEmbeddedVTable();
+    return s < Status::kStatusResolving && s != kStatusErrorResolved && ShouldHaveEmbeddedVTable();
   }
 
   String* GetName() REQUIRES_SHARED(Locks::mutator_lock_);  // Returns the cached name.
@@ -1017,7 +1043,7 @@
   // Returns the number of instance fields containing reference types. Does not count fields in any
   // super classes.
   uint32_t NumReferenceInstanceFields() REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK(IsResolved() || IsErroneous());
+    DCHECK(IsResolved());
     return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_instance_fields_));
   }
 
@@ -1045,7 +1071,7 @@
 
   // Returns the number of static fields containing reference types.
   uint32_t NumReferenceStaticFields() REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK(IsResolved() || IsErroneous());
+    DCHECK(IsResolved());
     return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_static_fields_));
   }
 
diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc
index 284d2d1..a8fa7db 100644
--- a/runtime/native/java_lang_VMClassLoader.cc
+++ b/runtime/native/java_lang_VMClassLoader.cc
@@ -81,14 +81,12 @@
   if (c != nullptr && c->IsErroneous()) {
     cl->ThrowEarlierClassFailure(c.Ptr());
     Thread* self = soa.Self();
-    ObjPtr<mirror::Class> eiie_class =
-        self->DecodeJObject(WellKnownClasses::java_lang_ExceptionInInitializerError)->AsClass();
     ObjPtr<mirror::Class> iae_class =
         self->DecodeJObject(WellKnownClasses::java_lang_IllegalAccessError)->AsClass();
     ObjPtr<mirror::Class> ncdfe_class =
         self->DecodeJObject(WellKnownClasses::java_lang_NoClassDefFoundError)->AsClass();
     ObjPtr<mirror::Class> exception = self->GetException()->GetClass();
-    if (exception == eiie_class || exception == iae_class || exception == ncdfe_class) {
+    if (exception == iae_class || exception == ncdfe_class) {
       self->ThrowNewWrappedException("Ljava/lang/ClassNotFoundException;",
                                      c->PrettyDescriptor().c_str());
     }
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 62d99fb..111755e 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -201,8 +201,12 @@
     // A representation of an invalid OatClass, used when an OatClass can't be found.
     // See FindOatClass().
     static OatClass Invalid() {
-      return OatClass(nullptr, mirror::Class::kStatusError, kOatClassNoneCompiled, 0, nullptr,
-                      nullptr);
+      return OatClass(/* oat_file */ nullptr,
+                      mirror::Class::kStatusErrorUnresolved,
+                      kOatClassNoneCompiled,
+                      /* bitmap_size */ 0,
+                      /* bitmap_pointer */ nullptr,
+                      /* methods_pointer */ nullptr);
     }
 
    private:
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 9669dab..a4ba6fd 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -14,23 +14,16 @@
  * limitations under the License.
  */
 
-#include <algorithm>
-#include <fstream>
 #include <string>
 #include <vector>
 #include <sys/param.h>
 
 #include "android-base/strings.h"
-#include <backtrace/BacktraceMap.h>
 #include <gtest/gtest.h>
 
 #include "art_field-inl.h"
 #include "class_linker-inl.h"
-#include "common_runtime_test.h"
-#include "compiler_callbacks.h"
-#include "dex2oat_environment_test.h"
-#include "gc/space/image_space.h"
-#include "mem_map.h"
+#include "dexopt_test.h"
 #include "oat_file_assistant.h"
 #include "oat_file_manager.h"
 #include "os.h"
@@ -40,242 +33,17 @@
 
 namespace art {
 
-class OatFileAssistantTest : public Dex2oatEnvironmentTest {
- public:
-  virtual void SetUp() OVERRIDE {
-    ReserveImageSpace();
-    Dex2oatEnvironmentTest::SetUp();
-  }
+class OatFileAssistantTest : public DexoptTest {};
 
-  // Pre-Relocate the image to a known non-zero offset so we don't have to
-  // deal with the runtime randomly relocating the image by 0 and messing up
-  // the expected results of the tests.
-  bool PreRelocateImage(const std::string& image_location, std::string* error_msg) {
-    std::string image;
-    if (!GetCachedImageFile(image_location, &image, error_msg)) {
-      return false;
-    }
-
-    std::string patchoat = GetAndroidRoot();
-    patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat";
-
-    std::vector<std::string> argv;
-    argv.push_back(patchoat);
-    argv.push_back("--input-image-location=" + image_location);
-    argv.push_back("--output-image-file=" + image);
-    argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA)));
-    argv.push_back("--base-offset-delta=0x00008000");
-    return Exec(argv, error_msg);
-  }
-
-  virtual void PreRuntimeCreate() {
-    std::string error_msg;
-    ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg;
-    ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg;
-    UnreserveImageSpace();
-  }
-
-  virtual void PostRuntimeCreate() OVERRIDE {
-    ReserveImageSpace();
-  }
-
-  // Generate an oat file for the purposes of test.
-  void GenerateOatForTest(const std::string& dex_location,
-                          const std::string& oat_location,
-                          CompilerFilter::Filter filter,
-                          bool relocate,
-                          bool pic,
-                          bool with_alternate_image) {
-    std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA));
-    std::string dalvik_cache_tmp = dalvik_cache + ".redirected";
-
-    if (!relocate) {
-      // Temporarily redirect the dalvik cache so dex2oat doesn't find the
-      // relocated image file.
-      ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno);
-    }
-
-    std::vector<std::string> args;
-    args.push_back("--dex-file=" + dex_location);
-    args.push_back("--oat-file=" + oat_location);
-    args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
-    args.push_back("--runtime-arg");
-
-    // Use -Xnorelocate regardless of the relocate argument.
-    // We control relocation by redirecting the dalvik cache when needed
-    // rather than use this flag.
-    args.push_back("-Xnorelocate");
-
-    if (pic) {
-      args.push_back("--compile-pic");
-    }
-
-    std::string image_location = GetImageLocation();
-    if (with_alternate_image) {
-      args.push_back("--boot-image=" + GetImageLocation2());
-    }
-
-    std::string error_msg;
-    ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg;
-
-    if (!relocate) {
-      // Restore the dalvik cache if needed.
-      ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno);
-    }
-
-    // Verify the odex file was generated as expected.
-    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.get() != nullptr) << error_msg;
-    EXPECT_EQ(pic, odex_file->IsPic());
-    EXPECT_EQ(filter, odex_file->GetCompilerFilter());
-
-    std::unique_ptr<ImageHeader> image_header(
-            gc::space::ImageSpace::ReadImageHeader(image_location.c_str(),
-                                                   kRuntimeISA,
-                                                   &error_msg));
-    ASSERT_TRUE(image_header != nullptr) << error_msg;
-    const OatHeader& oat_header = odex_file->GetOatHeader();
-    uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum();
-
-    if (CompilerFilter::DependsOnImageChecksum(filter)) {
-      if (with_alternate_image) {
-        EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
-      } else {
-        EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
-      }
-    }
-
-    if (!with_alternate_image) {
-      if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) {
-        if (relocate) {
-          EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
-              oat_header.GetImageFileLocationOatDataBegin());
-          EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
-        } else {
-          EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
-              oat_header.GetImageFileLocationOatDataBegin());
-          EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
-        }
-      }
-    }
-  }
-
-  // Generate a non-PIC odex file for the purposes of test.
-  // The generated odex file will be un-relocated.
-  void GenerateOdexForTest(const std::string& dex_location,
-                           const std::string& odex_location,
-                           CompilerFilter::Filter filter) {
-    GenerateOatForTest(dex_location,
-                       odex_location,
-                       filter,
-                       /*relocate*/false,
-                       /*pic*/false,
-                       /*with_alternate_image*/false);
-  }
-
-  void GeneratePicOdexForTest(const std::string& dex_location,
-                              const std::string& odex_location,
-                              CompilerFilter::Filter filter) {
-    GenerateOatForTest(dex_location,
-                       odex_location,
-                       filter,
-                       /*relocate*/false,
-                       /*pic*/true,
-                       /*with_alternate_image*/false);
-  }
-
-  // Generate an oat file in the oat location.
-  void GenerateOatForTest(const char* dex_location,
-                          CompilerFilter::Filter filter,
-                          bool relocate,
-                          bool pic,
-                          bool with_alternate_image) {
-    std::string oat_location;
-    std::string error_msg;
-    ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
-          dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
-    GenerateOatForTest(dex_location,
-                       oat_location,
-                       filter,
-                       relocate,
-                       pic,
-                       with_alternate_image);
-  }
-
-  // Generate a standard oat file in the oat location.
-  void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) {
-    GenerateOatForTest(dex_location,
-                       filter,
-                       /*relocate*/true,
-                       /*pic*/false,
-                       /*with_alternate_image*/false);
-  }
-
- private:
-  // Reserve memory around where the image will be loaded so other memory
-  // won't conflict when it comes time to load the image.
-  // This can be called with an already loaded image to reserve the space
-  // around it.
-  void ReserveImageSpace() {
-    MemMap::Init();
-
-    // Ensure a chunk of memory is reserved for the image space.
-    // The reservation_end includes room for the main space that has to come
-    // right after the image in case of the GSS collector.
-    uintptr_t reservation_start = ART_BASE_ADDRESS;
-    uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB;
-
-    std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true));
-    ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map";
-    for (BacktraceMap::const_iterator it = map->begin();
-        reservation_start < reservation_end && it != map->end(); ++it) {
-      ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end));
-      reservation_start = std::max(reservation_start, it->end);
-    }
-    ReserveImageSpaceChunk(reservation_start, reservation_end);
-  }
-
-  // Reserve a chunk of memory for the image space in the given range.
-  // Only has effect for chunks with a positive number of bytes.
-  void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) {
-    if (start < end) {
-      std::string error_msg;
-      image_reservation_.push_back(std::unique_ptr<MemMap>(
-          MemMap::MapAnonymous("image reservation",
-              reinterpret_cast<uint8_t*>(start), end - start,
-              PROT_NONE, false, false, &error_msg)));
-      ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg;
-      LOG(INFO) << "Reserved space for image " <<
-        reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" <<
-        reinterpret_cast<void*>(image_reservation_.back()->End());
-    }
-  }
-
-
-  // Unreserve any memory reserved by ReserveImageSpace. This should be called
-  // before the image is loaded.
-  void UnreserveImageSpace() {
-    image_reservation_.clear();
-  }
-
-  std::vector<std::unique_ptr<MemMap>> image_reservation_;
-};
-
-class OatFileAssistantNoDex2OatTest : public OatFileAssistantTest {
+class OatFileAssistantNoDex2OatTest : public DexoptTest {
  public:
   virtual void SetUpRuntimeOptions(RuntimeOptions* options) {
-    OatFileAssistantTest::SetUpRuntimeOptions(options);
+    DexoptTest::SetUpRuntimeOptions(options);
     options->push_back(std::make_pair("-Xnodex2oat", nullptr));
   }
 };
 
+
 // Case: We have a DEX file, but no OAT file for it.
 // Expect: The status is kDex2OatNeeded.
 TEST_F(OatFileAssistantTest, DexNoOat) {
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index 66eb2ec..f1e78b4 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -255,7 +255,7 @@
                                 const DexFile& initial_dex_file,
                                 const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED,
                                 /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED,
-                                /*out*/DexFile::ClassDef const** final_dex_cache ATTRIBUTE_UNUSED)
+                                /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED)
         OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
       std::string location(initial_dex_file.GetLocation());
       std::string event =
diff --git a/runtime/utils.cc b/runtime/utils.cc
index 8867743..410416e 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -788,49 +788,58 @@
   *task_cpu = strtoull(fields[36].c_str(), nullptr, 10);
 }
 
-const char* GetAndroidRoot() {
-  const char* android_root = getenv("ANDROID_ROOT");
-  if (android_root == nullptr) {
-    if (OS::DirectoryExists("/system")) {
-      android_root = "/system";
+static const char* GetAndroidDirSafe(const char* env_var,
+                                     const char* default_dir,
+                                     std::string* error_msg) {
+  const char* android_dir = getenv(env_var);
+  if (android_dir == nullptr) {
+    if (OS::DirectoryExists(default_dir)) {
+      android_dir = default_dir;
     } else {
-      LOG(FATAL) << "ANDROID_ROOT not set and /system does not exist";
-      return "";
+      *error_msg = StringPrintf("%s not set and %s does not exist", env_var, default_dir);
+      return nullptr;
     }
   }
-  if (!OS::DirectoryExists(android_root)) {
-    LOG(FATAL) << "Failed to find ANDROID_ROOT directory " << android_root;
-    return "";
+  if (!OS::DirectoryExists(android_dir)) {
+    *error_msg = StringPrintf("Failed to find %s directory %s", env_var, android_dir);
+    return nullptr;
   }
-  return android_root;
+  return android_dir;
 }
 
-const char* GetAndroidData() {
+const char* GetAndroidDir(const char* env_var, const char* default_dir) {
   std::string error_msg;
-  const char* dir = GetAndroidDataSafe(&error_msg);
+  const char* dir = GetAndroidDirSafe(env_var, default_dir, &error_msg);
   if (dir != nullptr) {
     return dir;
   } else {
     LOG(FATAL) << error_msg;
-    return "";
+    return nullptr;
   }
 }
 
+const char* GetAndroidRoot() {
+  return GetAndroidDir("ANDROID_ROOT", "/system");
+}
+
+const char* GetAndroidRootSafe(std::string* error_msg) {
+  return GetAndroidDirSafe("ANDROID_ROOT", "/system", error_msg);
+}
+
+const char* GetAndroidData() {
+  return GetAndroidDir("ANDROID_DATA", "/data");
+}
+
 const char* GetAndroidDataSafe(std::string* error_msg) {
-  const char* android_data = getenv("ANDROID_DATA");
-  if (android_data == nullptr) {
-    if (OS::DirectoryExists("/data")) {
-      android_data = "/data";
-    } else {
-      *error_msg = "ANDROID_DATA not set and /data does not exist";
-      return nullptr;
-    }
+  return GetAndroidDirSafe("ANDROID_DATA", "/data", error_msg);
+}
+
+std::string GetDefaultBootImageLocation(std::string* error_msg) {
+  const char* android_root = GetAndroidRootSafe(error_msg);
+  if (android_root == nullptr) {
+    return "";
   }
-  if (!OS::DirectoryExists(android_data)) {
-    *error_msg = StringPrintf("Failed to find ANDROID_DATA directory %s", android_data);
-    return nullptr;
-  }
-  return android_data;
+  return StringPrintf("%s/framework/boot.art", android_root);
 }
 
 void GetDalvikCache(const char* subdir, const bool create_if_absent, std::string* dalvik_cache,
diff --git a/runtime/utils.h b/runtime/utils.h
index 16ef706..9e663b3 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -143,12 +143,18 @@
 
 // Find $ANDROID_ROOT, /system, or abort.
 const char* GetAndroidRoot();
+// Find $ANDROID_ROOT, /system, or return null.
+const char* GetAndroidRootSafe(std::string* error_msg);
 
 // Find $ANDROID_DATA, /data, or abort.
 const char* GetAndroidData();
 // Find $ANDROID_DATA, /data, or return null.
 const char* GetAndroidDataSafe(std::string* error_msg);
 
+// Returns the default boot image location (ANDROID_ROOT/framework/boot.art).
+// Returns an empty string if ANDROID_ROOT is not set.
+std::string GetDefaultBootImageLocation(std::string* error_msg);
+
 // Returns the dalvik-cache location, with subdir appended. Returns the empty string if the cache
 // could not be found.
 std::string GetDalvikCache(const char* subdir);
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 507ea16..2610252 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -51,7 +51,6 @@
 jclass WellKnownClasses::java_lang_ClassNotFoundException;
 jclass WellKnownClasses::java_lang_Daemons;
 jclass WellKnownClasses::java_lang_Error;
-jclass WellKnownClasses::java_lang_ExceptionInInitializerError;
 jclass WellKnownClasses::java_lang_invoke_MethodHandle;
 jclass WellKnownClasses::java_lang_IllegalAccessError;
 jclass WellKnownClasses::java_lang_NoClassDefFoundError;
@@ -290,7 +289,6 @@
   java_lang_Object = CacheClass(env, "java/lang/Object");
   java_lang_OutOfMemoryError = CacheClass(env, "java/lang/OutOfMemoryError");
   java_lang_Error = CacheClass(env, "java/lang/Error");
-  java_lang_ExceptionInInitializerError = CacheClass(env, "java/lang/ExceptionInInitializerError");
   java_lang_IllegalAccessError = CacheClass(env, "java/lang/IllegalAccessError");
   java_lang_invoke_MethodHandle = CacheClass(env, "java/lang/invoke/MethodHandle");
   java_lang_NoClassDefFoundError = CacheClass(env, "java/lang/NoClassDefFoundError");
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index b3ce3d1..db8a53c 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -61,7 +61,6 @@
   static jclass java_lang_ClassNotFoundException;
   static jclass java_lang_Daemons;
   static jclass java_lang_Error;
-  static jclass java_lang_ExceptionInInitializerError;
   static jclass java_lang_IllegalAccessError;
   static jclass java_lang_invoke_MethodHandle;
   static jclass java_lang_NoClassDefFoundError;
diff --git a/test/008-exceptions/expected.txt b/test/008-exceptions/expected.txt
index 083ecf7..fcf2ef4 100644
--- a/test/008-exceptions/expected.txt
+++ b/test/008-exceptions/expected.txt
@@ -1,11 +1,11 @@
 Got an NPE: second throw
 java.lang.NullPointerException: second throw
-	at Main.catchAndRethrow(Main.java:77)
-	at Main.exceptions_007(Main.java:59)
-	at Main.main(Main.java:67)
+	at Main.catchAndRethrow(Main.java:94)
+	at Main.exceptions_007(Main.java:74)
+	at Main.main(Main.java:82)
 Caused by: java.lang.NullPointerException: first throw
-	at Main.throwNullPointerException(Main.java:84)
-	at Main.catchAndRethrow(Main.java:74)
+	at Main.throwNullPointerException(Main.java:101)
+	at Main.catchAndRethrow(Main.java:91)
 	... 2 more
 Static Init
 BadError: This is bad by convention: BadInit
@@ -15,3 +15,11 @@
 BadErrorNoStringInit: This is bad by convention
 java.lang.NoClassDefFoundError: BadInitNoStringInit
 BadErrorNoStringInit: This is bad by convention
+BadSuperClass Static Init
+BadError: This is bad by convention: BadInit
+MultiDexBadInit Static Init
+java.lang.Error: MultiDexBadInit
+java.lang.NoClassDefFoundError: MultiDexBadInit
+  cause: java.lang.Error: MultiDexBadInit
+java.lang.NoClassDefFoundError: MultiDexBadInit
+  cause: java.lang.Error: MultiDexBadInit
diff --git a/test/008-exceptions/multidex.jpp b/test/008-exceptions/multidex.jpp
new file mode 100644
index 0000000..a3746f5
--- /dev/null
+++ b/test/008-exceptions/multidex.jpp
@@ -0,0 +1,27 @@
+BadError:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadError
+BadInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadInit
+BadErrorNoStringInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadErrorNoStringInit
+BadInitNoStringInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadInitNoStringInit
+BadSuperClass:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadSuperClass
+DerivedFromBadSuperClass:
+  @@com.android.jack.annotations.ForceInMainDex
+  class DerivedFromBadSuperClass
+Main:
+  @@com.android.jack.annotations.ForceInMainDex
+  class Main
+MultiDexBadInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class MultiDexBadInit
+MultiDexBadInitWrapper1:
+  @@com.android.jack.annotations.ForceInMainDex
+  class MultiDexBadInitWrapper1
diff --git a/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java
new file mode 100644
index 0000000..f3953bd
--- /dev/null
+++ b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class MultiDexBadInitWrapper2 {
+    public static void setDummy(int value) {
+        if (doThrow) { throw new Error(); }
+        MultiDexBadInit.dummy = value;
+    }
+
+    public static boolean doThrow = false;
+}
diff --git a/test/008-exceptions/src/Main.java b/test/008-exceptions/src/Main.java
index b8231f1..74af00c 100644
--- a/test/008-exceptions/src/Main.java
+++ b/test/008-exceptions/src/Main.java
@@ -50,6 +50,21 @@
     }
 }
 
+// A class that throws BadError during static initialization, serving as a super class.
+class BadSuperClass {
+ static int dummy;
+ static {
+     System.out.println("BadSuperClass Static Init");
+     if (true) {
+         throw new BadError("BadInit");
+     }
+ }
+}
+
+// A class that derives from BadSuperClass.
+class DerivedFromBadSuperClass extends BadSuperClass {
+}
+
 /**
  * Exceptions across method calls
  */
@@ -63,10 +78,12 @@
             npe.printStackTrace(System.out);
         }
     }
-    public static void main (String args[]) {
+    public static void main(String args[]) {
         exceptions_007();
         exceptionsRethrowClassInitFailure();
         exceptionsRethrowClassInitFailureNoStringInit();
+        exceptionsForSuperClassInitFailure();
+        exceptionsInMultiDex();
     }
 
     private static void catchAndRethrow() {
@@ -129,4 +146,70 @@
             error.printStackTrace(System.out);
         }
     }
+
+    private static void exceptionsForSuperClassInitFailure() {
+        try {
+            // Resolve DerivedFromBadSuperClass.
+            BadSuperClass.dummy = 1;
+            throw new IllegalStateException("Should not reach here.");
+        } catch (BadError e) {
+            System.out.println(e);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        try {
+            // Before splitting mirror::Class::kStatusError into
+            // kStatusErrorUnresolved and kStatusErrorResolved,
+            // this would trigger a
+            //     CHECK(super_class->IsResolved())
+            // failure in
+            //     ClassLinker::LoadSuperAndInterfaces().
+            // After the change we're getting either VerifyError
+            // (for Optimizing) or NoClassDefFoundError wrapping
+            // BadError (for interpreter or JIT).
+            new DerivedFromBadSuperClass();
+            throw new IllegalStateException("Should not reach here.");
+        } catch (NoClassDefFoundError ncdfe) {
+            if (!(ncdfe.getCause() instanceof BadError)) {
+                ncdfe.getCause().printStackTrace();
+            }
+        } catch (VerifyError e) {
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+    private static void exceptionsInMultiDex() {
+        try {
+            MultiDexBadInit.dummy = 1;
+            throw new IllegalStateException("Should not reach here.");
+        } catch (Error e) {
+            System.out.println(e);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        // Before splitting mirror::Class::kStatusError into
+        // kStatusErrorUnresolved and kStatusErrorResolved,
+        // the exception from wrapper 1 would have been
+        // wrapped in NoClassDefFoundError but the exception
+        // from wrapper 2 would have been unwrapped.
+        try {
+            MultiDexBadInitWrapper1.setDummy(1);
+            throw new IllegalStateException("Should not reach here.");
+        } catch (NoClassDefFoundError ncdfe) {
+            System.out.println(ncdfe);
+            System.out.println("  cause: " + ncdfe.getCause());
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        try {
+            MultiDexBadInitWrapper2.setDummy(1);
+            throw new IllegalStateException("Should not reach here.");
+        } catch (NoClassDefFoundError ncdfe) {
+            System.out.println(ncdfe);
+            System.out.println("  cause: " + ncdfe.getCause());
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
 }
diff --git a/test/008-exceptions/src/MultiDexBadInit.java b/test/008-exceptions/src/MultiDexBadInit.java
new file mode 100644
index 0000000..e3ebb9c
--- /dev/null
+++ b/test/008-exceptions/src/MultiDexBadInit.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class MultiDexBadInit {
+    static int dummy;
+    static {
+        System.out.println("MultiDexBadInit Static Init");
+        if (true) {
+            throw new Error("MultiDexBadInit");
+        }
+    }
+}
diff --git a/test/008-exceptions/src/MultiDexBadInitWrapper1.java b/test/008-exceptions/src/MultiDexBadInitWrapper1.java
new file mode 100644
index 0000000..059e6a3
--- /dev/null
+++ b/test/008-exceptions/src/MultiDexBadInitWrapper1.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class MultiDexBadInitWrapper1 {
+    public static void setDummy(int value) {
+        if (doThrow) { throw new Error(); }
+        MultiDexBadInit.dummy = value;
+    }
+
+    public static boolean doThrow = false;
+}
diff --git a/test/142-classloader2/expected.txt b/test/142-classloader2/expected.txt
index 86f5e22..056d978 100644
--- a/test/142-classloader2/expected.txt
+++ b/test/142-classloader2/expected.txt
@@ -1 +1,5 @@
+Loaded class B.
+Caught VerifyError.
+Loaded class B.
+Caught wrapped VerifyError.
 Everything OK.
diff --git a/test/142-classloader2/src/Main.java b/test/142-classloader2/src/Main.java
index 80b00e7..a0c7764 100644
--- a/test/142-classloader2/src/Main.java
+++ b/test/142-classloader2/src/Main.java
@@ -74,16 +74,25 @@
         // Try to load a dex file with bad dex code. Use new instance to force verification.
         try {
           Class<?> badClass = Main.class.getClassLoader().loadClass("B");
+          System.out.println("Loaded class B.");
           badClass.newInstance();
-          System.out.println("Should not be able to load class from bad dex file.");
+          System.out.println("Should not be able to instantiate B with bad dex bytecode.");
         } catch (VerifyError e) {
+          System.out.println("Caught VerifyError.");
         }
 
         // Make sure the same error is rethrown when reloading the bad class.
         try {
           Class<?> badClass = Main.class.getClassLoader().loadClass("B");
-          System.out.println("Should not be able to load class from bad dex file.");
-        } catch (VerifyError e) {
+          System.out.println("Loaded class B.");
+          badClass.newInstance();
+          System.out.println("Should not be able to instantiate B with bad dex bytecode.");
+        } catch (NoClassDefFoundError e) {
+          if (e.getCause() instanceof VerifyError) {
+            System.out.println("Caught wrapped VerifyError.");
+          } else {
+            e.printStackTrace();
+          }
         }
 
         System.out.println("Everything OK.");
diff --git a/test/154-gc-loop/expected.txt b/test/154-gc-loop/expected.txt
new file mode 100644
index 0000000..6106818
--- /dev/null
+++ b/test/154-gc-loop/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Finalize count too large: false
diff --git a/test/154-gc-loop/heap_interface.cc b/test/154-gc-loop/heap_interface.cc
new file mode 100644
index 0000000..8d610a8
--- /dev/null
+++ b/test/154-gc-loop/heap_interface.cc
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gc/heap.h"
+#include "runtime.h"
+
+namespace art {
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_backgroundProcessState(JNIEnv*, jclass) {
+  Runtime::Current()->UpdateProcessState(kProcessStateJankImperceptible);
+}
+
+}  // namespace
+}  // namespace art
diff --git a/test/154-gc-loop/info.txt b/test/154-gc-loop/info.txt
new file mode 100644
index 0000000..f599db1
--- /dev/null
+++ b/test/154-gc-loop/info.txt
@@ -0,0 +1 @@
+Test that GC doesn't happen too often for a few small allocations.
diff --git a/test/154-gc-loop/src/Main.java b/test/154-gc-loop/src/Main.java
new file mode 100644
index 0000000..3a256c1
--- /dev/null
+++ b/test/154-gc-loop/src/Main.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.WeakReference;
+
+public class Main {
+  static final class GcWatcher {
+    protected void finalize() throws Throwable {
+        watcher = new WeakReference<GcWatcher>(new GcWatcher());
+        ++finalizeCounter;
+    }
+  }
+  static WeakReference<GcWatcher> watcher = new WeakReference<GcWatcher>(new GcWatcher());
+  static Object o = new Object();
+  static int finalizeCounter = 0;
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[0]);
+    backgroundProcessState();
+    try {
+        Runtime.getRuntime().gc();
+        for (int i = 0; i < 10; ++i) {
+            o = new Object();
+            Thread.sleep(1000);
+        }
+    } catch (Exception e) {}
+    System.out.println("Finalize count too large: " +
+            ((finalizeCounter >= 10) ? Integer.toString(finalizeCounter) : "false"));
+  }
+
+  private static native void backgroundProcessState();
+}
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index d0b77a4..328216b 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -29,7 +29,7 @@
 class [Ljava.lang.String; 10000
 class java.lang.Object 111
 class Main$TestForNonInit 11
-class Main$TestForInitFail 1001
+class Main$TestForInitFail 1011
 int []
 class [Ljava.lang.String; []
 class java.lang.Object []
diff --git a/test/Android.bp b/test/Android.bp
index 89e4092..287df13 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -173,12 +173,13 @@
     whole_static_libs: [
         "libart-compiler-gtest",
         "libart-runtime-gtest",
-        "libgtest",
+        "libgtest"
     ],
     shared_libs: [
         "libartd",
         "libartd-compiler",
         "libbase",
+        "libbacktrace"
     ],
     target: {
         android: {
@@ -317,6 +318,7 @@
         "141-class-unload/jni_unload.cc",
         "148-multithread-gc-annotations/gc_coverage.cc",
         "149-suspend-all-stress/suspend_all.cc",
+        "154-gc-loop/heap_interface.cc",
         "454-get-vreg/get_vreg_jni.cc",
         "457-regs/regs_jni.cc",
         "461-get-reference-vreg/get_reference_vreg_jni.cc",
diff --git a/test/ErroneousInit/ErroneousInit.java b/test/ErroneousInit/ErroneousInit.java
new file mode 100644
index 0000000..67b7b20
--- /dev/null
+++ b/test/ErroneousInit/ErroneousInit.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class ErroneousInit {
+    static {
+        if (true) {
+            throw new Error();
+        }
+    }
+}
diff --git a/test/run-test b/test/run-test
index c78fa35..a228789 100755
--- a/test/run-test
+++ b/test/run-test
@@ -408,7 +408,9 @@
 mkdir -p $tmp_dir
 
 # Add thread suspend timeout flag
-run_args="${run_args} --runtime-option -XX:ThreadSuspendTimeout=$suspend_timeout"
+if [ ! "$runtime" = "jvm" ]; then
+  run_args="${run_args} --runtime-option -XX:ThreadSuspendTimeout=$suspend_timeout"
+fi
 
 if [ "$basic_verify" = "true" ]; then
   # Set HspaceCompactForOOMMinIntervalMs to zero to run hspace compaction for OOM more frequently in tests.