Fix unquickening logic for quickened input dexes

In the case where an input dex is already quickened (i.e.
obfuscation), do not attempt to unquicken it. This is done by
switching the quicken info table to the compact offset table (used by
cdex debug infos).

Added test.

Posssible to get a bit of extra savings by deleting the quicken info
num entries field.

Quicken filter size regression average on golem: -0.14%.

Bug: 72608794
Bug: 63756964
Test: test-art-host-gtest-dex2oat_test -j64
Change-Id: I5534a7509b4c718a9b959fa43b82bde857e0b59e
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 6d3658f..b16c56a 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -997,7 +997,7 @@
             if (class_it.IsAtMethod() && class_it.GetMethodCodeItem() != nullptr) {
               for (const DexInstructionPcPair& inst :
                        CodeItemInstructionAccessor(*dex_file, class_it.GetMethodCodeItem())) {
-                ASSERT_FALSE(inst->IsQuickened()) << output_;
+                ASSERT_FALSE(inst->IsQuickened()) << inst->Opcode() << " " << output_;
               }
             }
           }
@@ -1871,4 +1871,91 @@
   EXPECT_TRUE(found_fast_verify) << "Expected to find " << kFastVerifyString << "\n" << output_;
 }
 
+// Test that dex files with quickened opcodes aren't dequickened.
+TEST_F(Dex2oatTest, QuickenedInput) {
+  std::string error_msg;
+  ScratchFile temp_dex;
+  MutateDexFile(temp_dex.GetFile(), GetTestDexFileName("ManyMethods"), [] (DexFile* dex) {
+    bool mutated_successfully = false;
+    // Change the dex instructions to make an opcode that spans past the end of the code item.
+    for (size_t i = 0; i < dex->NumClassDefs(); ++i) {
+      const DexFile::ClassDef& def = dex->GetClassDef(i);
+      const uint8_t* data = dex->GetClassData(def);
+      if (data == nullptr) {
+        continue;
+      }
+      ClassDataItemIterator it(*dex, data);
+      it.SkipAllFields();
+      while (it.HasNextMethod()) {
+        DexFile::CodeItem* item = const_cast<DexFile::CodeItem*>(it.GetMethodCodeItem());
+        if (item != nullptr) {
+          CodeItemInstructionAccessor instructions(*dex, item);
+          // Make a quickened instruction that doesn't run past the end of the code item.
+          if (instructions.InsnsSizeInCodeUnits() > 2) {
+            const_cast<Instruction&>(instructions.InstructionAt(0)).SetOpcode(
+                Instruction::IGET_BYTE_QUICK);
+            mutated_successfully = true;
+          }
+        }
+        it.Next();
+      }
+    }
+    CHECK(mutated_successfully)
+        << "Failed to find candidate code item with only one code unit in last instruction.";
+  });
+
+  std::string dex_location = temp_dex.GetFilename();
+  std::string odex_location = GetOdexDir() + "/quickened.odex";
+  std::string vdex_location = GetOdexDir() + "/quickened.vdex";
+  std::unique_ptr<File> vdex_output(OS::CreateEmptyFile(vdex_location.c_str()));
+  // Quicken the dex
+  {
+    std::string input_vdex = "--input-vdex-fd=-1";
+    std::string output_vdex = StringPrintf("--output-vdex-fd=%d", vdex_output->Fd());
+    GenerateOdexForTest(dex_location,
+                        odex_location,
+                        CompilerFilter::kQuicken,
+                        // Disable cdex since we want to compare against the original dex file
+                        // after unquickening.
+                        { input_vdex, output_vdex, kDisableCompactDex },
+                        /* expect_success */ true,
+                        /* use_fd */ true);
+  }
+  // Unquicken by running the verify compiler filter on the vdex file and verify it matches.
+  std::string odex_location2 = GetOdexDir() + "/unquickened.odex";
+  std::string vdex_location2 = GetOdexDir() + "/unquickened.vdex";
+  std::unique_ptr<File> vdex_unquickened(OS::CreateEmptyFile(vdex_location2.c_str()));
+  {
+    std::string input_vdex = StringPrintf("--input-vdex-fd=%d", vdex_output->Fd());
+    std::string output_vdex = StringPrintf("--output-vdex-fd=%d", vdex_unquickened->Fd());
+    GenerateOdexForTest(dex_location,
+                        odex_location2,
+                        CompilerFilter::kVerify,
+                        // Disable cdex to avoid needing to write out the shared section.
+                        { input_vdex, output_vdex, kDisableCompactDex },
+                        /* expect_success */ true,
+                        /* use_fd */ true);
+  }
+  ASSERT_EQ(vdex_unquickened->Flush(), 0) << "Could not flush and close vdex file";
+  ASSERT_TRUE(success_);
+  {
+    // Check that hte vdex has one dex and compare it to the original one.
+    std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location2.c_str(),
+                                                  /*writable*/ false,
+                                                  /*low_4gb*/ false,
+                                                  /*unquicken*/ false,
+                                                  &error_msg));
+    std::vector<std::unique_ptr<const DexFile>> dex_files;
+    bool result = vdex->OpenAllDexFiles(&dex_files, &error_msg);
+    ASSERT_TRUE(result) << error_msg;
+    ASSERT_EQ(dex_files.size(), 1u) << error_msg;
+    ScratchFile temp;
+    ASSERT_TRUE(temp.GetFile()->WriteFully(dex_files[0]->Begin(), dex_files[0]->Size()));
+    ASSERT_EQ(temp.GetFile()->Flush(), 0) << "Could not flush extracted dex";
+    EXPECT_EQ(temp.GetFile()->Compare(temp_dex.GetFile()), 0);
+  }
+  ASSERT_EQ(vdex_output->FlushCloseOrErase(), 0) << "Could not flush and close";
+  ASSERT_EQ(vdex_unquickened->FlushCloseOrErase(), 0) << "Could not flush and close";
+}
+
 }  // namespace art