ART: Allow the execution to stop if the compilation fails via an option

The current implementation continues the execution of the application if
dex2oat fails by relying on the interpreter.

This patch adds a -Xno-dex-file-fallback option to stop the default behavior.
This can be used two-fold.

First, one can enforce that a runtime only starts with a boot image. A
follow-up patch will ensure that dex2oat (for apps) and patchoat in general
request that mode and close gracefully otherwise.

Second, this can be used for testing and debugging purposes, as it ensures
that compiler failures & aborts are not silently ignored.

Add testing.

Bug: 19100590
Change-Id: Iaf07b5ccf00942ca8a8ec8687599320a3ddbc089
Signed-off-by: Jean Christophe Beyler <jean.christophe.beyler@intel.com>
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index 288f7ac..b740b41 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -202,6 +202,7 @@
   EXPECT_SINGLE_PARSE_VALUE(false, "-XX:DisableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM);
   EXPECT_SINGLE_PARSE_VALUE(0.5, "-XX:HeapTargetUtilization=0.5", M::HeapTargetUtilization);
   EXPECT_SINGLE_PARSE_VALUE(5u, "-XX:ParallelGCThreads=5", M::ParallelGCThreads);
+  EXPECT_SINGLE_PARSE_EXISTS("-Xno-dex-file-fallback", M::NoDexFileFallback);
 }  // TEST_F
 
 TEST_F(CmdlineParserTest, TestSimpleFailures) {
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index ee66b49..3278751 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1008,10 +1008,15 @@
 
   // Failed, bail.
   if (open_oat_file.get() == nullptr) {
-    std::string error_msg;
     // dex2oat was disabled or crashed. Add the dex file in the list of dex_files to make progress.
-    DexFile::Open(dex_location, dex_location, &error_msg, dex_files);
-    error_msgs->push_back(error_msg);
+    if (Runtime::Current()->IsDexFileFallbackEnabled()) {
+      std::string error_msg;
+      if (!DexFile::Open(dex_location, dex_location, &error_msg, dex_files)) {
+        error_msgs->push_back(error_msg);
+      }
+    } else {
+      error_msgs->push_back("Fallback mode disabled, skipping dex files.");
+    }
     return false;
   }
 
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 99369ca..c0c7baa 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -241,6 +241,8 @@
       .Define("-Xzygote-max-failed-boots=_")
           .WithType<unsigned int>()
           .IntoKey(M::ZygoteMaxFailedBoots)
+      .Define("-Xno-dex-file-fallback")
+          .IntoKey(M::NoDexFileFallback)
       .Ignore({
           "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa",
           "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_",
@@ -631,6 +633,8 @@
   UsageMessage(stream, "  -X[no]relocate\n");
   UsageMessage(stream, "  -X[no]dex2oat (Whether to invoke dex2oat on the application)\n");
   UsageMessage(stream, "  -X[no]image-dex2oat (Whether to create and use a boot image)\n");
+  UsageMessage(stream, "  -Xno-dex-file-fallback "
+                       "(Don't fall back to dex files without oat files)\n");
   UsageMessage(stream, "\n");
 
   UsageMessage(stream, "The following previously supported Dalvik options are ignored:\n");
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index f38f65e..1af08c1 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -169,6 +169,7 @@
       dump_gc_performance_on_shutdown_(false),
       preinitialization_transaction_(nullptr),
       verify_(false),
+      allow_dex_file_fallback_(true),
       target_sdk_version_(0),
       implicit_null_checks_(false),
       implicit_so_checks_(false),
@@ -380,7 +381,9 @@
   InitLogging(NULL);  // Calls Locks::Init() as a side effect.
   instance_ = new Runtime;
   if (!instance_->Init(options, ignore_unrecognized)) {
-    delete instance_;
+    // TODO: Currently deleting the instance will abort the runtime on destruction. Now This will
+    // leak memory, instead. Fix the destructor. b/19100793.
+    // delete instance_;
     instance_ = NULL;
     return false;
   }
@@ -762,6 +765,7 @@
   intern_table_ = new InternTable;
 
   verify_ = runtime_options.GetOrDefault(Opt::Verify);
+  allow_dex_file_fallback_ = !runtime_options.Exists(Opt::NoDexFileFallback);
 
   if (runtime_options.GetOrDefault(Opt::Interpret)) {
     GetInstrumentation()->ForceInterpretOnly();
@@ -800,6 +804,11 @@
                        runtime_options.GetOrDefault(Opt::EnableHSpaceCompactForOOM),
                        runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs));
 
+  if (heap_->GetImageSpace() == nullptr && !allow_dex_file_fallback_) {
+    LOG(ERROR) << "Dex file fallback disabled, cannot continue without image.";
+    return false;
+  }
+
   dump_gc_performance_on_shutdown_ = runtime_options.Exists(Opt::DumpGCPerformanceOnShutdown);
 
   if (runtime_options.Exists(Opt::JdwpOptions)) {
diff --git a/runtime/runtime.h b/runtime/runtime.h
index fb9ca40..944c8bd 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -505,6 +505,10 @@
     return verify_;
   }
 
+  bool IsDexFileFallbackEnabled() const {
+    return allow_dex_file_fallback_;
+  }
+
   bool RunningOnValgrind() const {
     return running_on_valgrind_;
   }
@@ -668,6 +672,10 @@
   // If false, verification is disabled. True by default.
   bool verify_;
 
+  // If true, the runtime may use dex files directly with the interpreter if an oat file is not
+  // available/usable.
+  bool allow_dex_file_fallback_;
+
   // Specifies target SDK version to allow workarounds for certain API levels.
   int32_t target_sdk_version_;
 
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 71a0152..d9cc4d4 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -115,6 +115,6 @@
                                                                           // Runtime::Abort.
 RUNTIME_OPTIONS_KEY (void (*)(),          HookAbort,                      nullptr)
 RUNTIME_OPTIONS_KEY (unsigned int,        ZygoteMaxFailedBoots,           1)
-
+RUNTIME_OPTIONS_KEY (Unit,                NoDexFileFallback)
 
 #undef RUNTIME_OPTIONS_KEY
diff --git a/test/116-nodex2oat/run b/test/116-nodex2oat/run
index 9e5c7dd..2cdb3f7 100755
--- a/test/116-nodex2oat/run
+++ b/test/116-nodex2oat/run
@@ -24,7 +24,7 @@
   exit 1
 fi
 
-# Make sure we can run without an oat file,
+# Make sure we can run without an oat file.
 echo "Run -Xnodex2oat"
 ${RUN} ${flags} --runtime-option -Xnodex2oat
 
diff --git a/test/118-noimage-dex2oat/expected.txt b/test/118-noimage-dex2oat/expected.txt
index bcb695d..0103e89 100644
--- a/test/118-noimage-dex2oat/expected.txt
+++ b/test/118-noimage-dex2oat/expected.txt
@@ -1,6 +1,8 @@
 Run -Xnoimage-dex2oat
 Has image is false, is image dex2oat enabled is false, is BOOTCLASSPATH on disk is false.
 testB18485243 PASS
+Run -Xnoimage-dex2oat -Xno-dex-file-fallback
+Failed to initialize runtime (check log for details)
 Run -Ximage-dex2oat
 Has image is true, is image dex2oat enabled is true, is BOOTCLASSPATH on disk is true.
 testB18485243 PASS
diff --git a/test/118-noimage-dex2oat/run b/test/118-noimage-dex2oat/run
index 2037797..4b1d0ce 100644
--- a/test/118-noimage-dex2oat/run
+++ b/test/118-noimage-dex2oat/run
@@ -46,10 +46,14 @@
 bpath_arg="--runtime-option -Xbootclasspath:${bpath}"
 
 
-# Make sure we can run without an oat file,
+# Make sure we can run without an oat file.
 echo "Run -Xnoimage-dex2oat"
 ${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat --runtime-option -Xnodex2oat
 
+# Make sure we cannot run without an oat file without fallback.
+echo "Run -Xnoimage-dex2oat -Xno-dex-file-fallback"
+${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat --runtime-option -Xnodex2oat --runtime-option -Xno-dex-file-fallback
+
 # Make sure we can run with the oat file.
 echo "Run -Ximage-dex2oat"
 ${RUN} ${flags} ${bpath_arg} --runtime-option -Ximage-dex2oat
diff --git a/test/119-noimage-patchoat/check b/test/119-noimage-patchoat/check
new file mode 100755
index 0000000..7b47ac1
--- /dev/null
+++ b/test/119-noimage-patchoat/check
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+# Strip the process pids and line numbers from exact error messages.
+sed -e 's/^art E.*\] //' "$2" > "$2.tmp"
+
+diff --strip-trailing-cr -q "$1" "$2.tmp" >/dev/null
diff --git a/test/119-noimage-patchoat/expected.txt b/test/119-noimage-patchoat/expected.txt
index e864268..3d2b4f1 100644
--- a/test/119-noimage-patchoat/expected.txt
+++ b/test/119-noimage-patchoat/expected.txt
@@ -1,5 +1,8 @@
 Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false
 Has image is false, is image dex2oat enabled is false.
+Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false -Xno-dex-file-fallback
+Dex file fallback disabled, cannot continue without image.
+Failed to initialize runtime (check log for details)
 Run -Ximage-dex2oat
 Has image is true, is image dex2oat enabled is true.
 Run default
diff --git a/test/119-noimage-patchoat/run b/test/119-noimage-patchoat/run
index c409cbb..02a64f8 100644
--- a/test/119-noimage-patchoat/run
+++ b/test/119-noimage-patchoat/run
@@ -29,10 +29,14 @@
   false_bin="/system/bin/false"
 fi
 
-# Make sure we can run without an image file,
+# Make sure we can run without an image file.
 echo "Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false"
 ${RUN} ${flags} ${BPATH} --runtime-option -Xnoimage-dex2oat --runtime-option -Xpatchoat:${false_bin}
 
+# Make sure we cannot run without an image file without fallback.
+echo "Run -Xnoimage-dex2oat -Xpatchoat:/system/bin/false -Xno-dex-file-fallback"
+${RUN} ${flags} ${BPATH} --runtime-option -Xnoimage-dex2oat --runtime-option -Xpatchoat:${false_bin} --runtime-option -Xno-dex-file-fallback
+
 # Make sure we can run with the image file.
 echo "Run -Ximage-dex2oat"
 ${RUN} ${flags} ${BPATH} --runtime-option -Ximage-dex2oat
diff --git a/test/134-nodex2oat-nofallback/check b/test/134-nodex2oat-nofallback/check
new file mode 100755
index 0000000..d929c8f
--- /dev/null
+++ b/test/134-nodex2oat-nofallback/check
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+# The head of the log.
+HEAD=$(head -n 1 "$2")
+EXPECTED_HEAD="Unable to locate class 'Main'"
+
+# Content somewhere inside the output.
+grep 'Suppressed: java.io.IOException: Fallback mode disabled, skipping dex files.' "$2" >/dev/null
+MSG_FOUND=$?
+
+if [[ "$HEAD" != "$EXPECTED_HEAD" || $MSG_FOUND -ne 0 ]] ; then
+  # Print out the log and return with error.
+  cat "$2"
+  exit 1
+fi
+
+# Success.
+exit 0
\ No newline at end of file
diff --git a/test/134-nodex2oat-nofallback/expected.txt b/test/134-nodex2oat-nofallback/expected.txt
new file mode 100644
index 0000000..9b6c846
--- /dev/null
+++ b/test/134-nodex2oat-nofallback/expected.txt
@@ -0,0 +1,64 @@
+# This file isn't used, but gives an example of the output we expect.
+Unable to locate class 'Main'
+java.lang.ClassNotFoundException: Didn't find class "Main" on path: DexPathList[[zip file "/tmp/user/test-12345/134-nodex2oat-nofallback.jar"],nativeLibraryDirectories=[/ssd2/aosp-master3/out/host/linux-x86/lib]]
+       at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
+       at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
+       at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
+       Suppressed: java.io.IOException: Fallback mode disabled, skipping dex files.
+               at dalvik.system.DexFile.openDexFileNative(Native Method)
+               at dalvik.system.DexFile.openDexFile(DexFile.java:295)
+               at dalvik.system.DexFile.<init>(DexFile.java:80)
+               at dalvik.system.DexFile.<init>(DexFile.java:59)
+               at dalvik.system.DexPathList.loadDexFile(DexPathList.java:262)
+               at dalvik.system.DexPathList.makeDexElements(DexPathList.java:231)
+               at dalvik.system.DexPathList.<init>(DexPathList.java:109)
+               at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
+               at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:38)
+               at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:128)
+               at java.lang.ClassLoader.access$000(ClassLoader.java:65)
+               at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:81)
+               at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:137)
+       Caused by: java.io.IOException: Failed to open oat file from dex location '/tmp/user/test-12345/134-nodex2oat-nofallback.jar'
+               ... 13 more
+       Caused by: java.io.IOException: Failed to open oat file from /tmp/user/test-12345/x86/134-nodex2oat-nofallback.odex (error Failed to open oat filename for reading: No such file or directory) (no dalvik_cache availible) and relocation failed.
+               ... 13 more
+       Caused by: java.io.IOException 
+               ... 13 more
+       Suppressed: java.lang.ClassNotFoundException: Main
+               at java.lang.Class.classForName(Native Method)
+               at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
+               at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
+               at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
+               ... 1 more
+       Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
+java.lang.ClassNotFoundException: Didn't find class "Main" on path: DexPathList[[zip file "/tmp/user/test-12345/134-nodex2oat-nofallback.jar"],nativeLibraryDirectories=[/ssd2/aosp-master3/out/host/linux-x86/lib]]
+       at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
+       at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
+       at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
+       Suppressed: java.io.IOException: Fallback mode disabled, skipping dex files.
+               at dalvik.system.DexFile.openDexFileNative(Native Method)
+               at dalvik.system.DexFile.openDexFile(DexFile.java:295)
+               at dalvik.system.DexFile.<init>(DexFile.java:80)
+               at dalvik.system.DexFile.<init>(DexFile.java:59)
+               at dalvik.system.DexPathList.loadDexFile(DexPathList.java:262)
+               at dalvik.system.DexPathList.makeDexElements(DexPathList.java:231)
+               at dalvik.system.DexPathList.<init>(DexPathList.java:109)
+               at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
+               at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:38)
+               at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:128)
+               at java.lang.ClassLoader.access$000(ClassLoader.java:65)
+               at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:81)
+               at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:137)
+       Caused by: java.io.IOException: Failed to open oat file from dex location '/tmp/user/test-12345/134-nodex2oat-nofallback.jar'
+               ... 13 more
+       Caused by: java.io.IOException: Failed to open oat file from /tmp/user/test-12345/x86/134-nodex2oat-nofallback.odex (error Failed to open oat filename for reading: No such file or directory) (no dalvik_cache availible) and relocation failed.
+               ... 13 more
+       Caused by: java.io.IOException: 
+               ... 13 more
+       Suppressed: java.lang.ClassNotFoundException: Main
+               at java.lang.Class.classForName(Native Method)
+               at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
+               at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
+               at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
+               ... 1 more
+       Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
diff --git a/test/134-nodex2oat-nofallback/info.txt b/test/134-nodex2oat-nofallback/info.txt
new file mode 100644
index 0000000..3004729
--- /dev/null
+++ b/test/134-nodex2oat-nofallback/info.txt
@@ -0,0 +1,2 @@
+Test that disables dex2oat'ing the application, and disable fallback. This is derived from test
+116. It needs it separate test as it needs a custom check script.
\ No newline at end of file
diff --git a/test/134-nodex2oat-nofallback/run b/test/134-nodex2oat-nofallback/run
new file mode 100755
index 0000000..38b4adb
--- /dev/null
+++ b/test/134-nodex2oat-nofallback/run
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+flags="${@}"
+
+# Make sure we cannot run without an oat file without fallback.
+${RUN} ${flags} --runtime-option -Xnodex2oat --runtime-option -Xno-dex-file-fallback
diff --git a/test/134-nodex2oat-nofallback/src/Main.java b/test/134-nodex2oat-nofallback/src/Main.java
new file mode 100644
index 0000000..37ac9d5
--- /dev/null
+++ b/test/134-nodex2oat-nofallback/src/Main.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public class Main {
+  public static void main(String[] args) {
+    System.out.println(
+        "Has oat is " + hasOat() + ", is dex2oat enabled is " + isDex2OatEnabled() + ".");
+
+    if (hasOat() && !isDex2OatEnabled()) {
+      throw new Error("Application with dex2oat disabled runs with an oat file");
+    } else if (!hasOat() && isDex2OatEnabled()) {
+      throw new Error("Application with dex2oat enabled runs without an oat file");
+    }
+  }
+
+  static {
+    System.loadLibrary("arttest");
+  }
+
+  private native static boolean hasOat();
+
+  private native static boolean isDex2OatEnabled();
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index c8e0ec5..8e4b46b 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -181,7 +181,8 @@
 # Note 116-nodex2oat is not broken per-se it just doesn't (and isn't meant to) work with --prebuild.
 TEST_ART_BROKEN_PREBUILD_RUN_TESTS := \
   116-nodex2oat \
-  118-noimage-dex2oat
+  118-noimage-dex2oat \
+  134-nodex2oat-nofallback
 
 ifneq (,$(filter prebuild,$(PREBUILD_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),prebuild, \