Add minimal libdexfile API for external users.

This defines a stable C ABI that is safe for external use in
libdexfile_external. On top of it is a thin stable C++ API in
libdexfile_support to be linked into clients.

TBD: Unit tests, e.g. move the relevant tests from libunwindstack.

Test: mmma art/
Test: gtests and runtests on target
Test: atest system/core/libunwindstack (no new failures, 1 old one)
Bug: 119632407
Change-Id: I64388337e6d31e4bf36ec435a78c4defc2e17aa2
diff --git a/libdexfile/external/dex_file_ext.cc b/libdexfile/external/dex_file_ext.cc
new file mode 100644
index 0000000..3c193f4
--- /dev/null
+++ b/libdexfile/external/dex_file_ext.cc
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2018 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 <inttypes.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/mapped_file.h>
+#include <android-base/stringprintf.h>
+
+#include <dex/class_accessor-inl.h>
+#include <dex/code_item_accessors-inl.h>
+#include <dex/dex_file-inl.h>
+#include <dex/dex_file_loader.h>
+
+#include "art_api/ext_dex_file.h"
+
+extern "C" class ExtDexFileString {
+ public:
+  const std::string str_;
+};
+
+namespace art {
+namespace {
+
+const ExtDexFileString empty_string{""};
+
+struct MethodCacheEntry {
+  int32_t offset;  // Offset relative to the start of the dex file header.
+  int32_t len;
+  int32_t index;  // Method index.
+  std::string name;  // Method name. Not filled in for all cache entries.
+};
+
+class MappedFileContainer : public DexFileContainer {
+ public:
+  explicit MappedFileContainer(std::unique_ptr<android::base::MappedFile>&& map)
+      : map_(std::move(map)) {}
+  ~MappedFileContainer() override {}
+  int GetPermissions() override { return 0; }
+  bool IsReadOnly() override { return true; }
+  bool EnableWrite() override { return false; }
+  bool DisableWrite() override { return false; }
+
+ private:
+  std::unique_ptr<android::base::MappedFile> map_;
+  DISALLOW_COPY_AND_ASSIGN(MappedFileContainer);
+};
+
+}  // namespace
+}  // namespace art
+
+extern "C" {
+
+const ExtDexFileString* ExtDexFileMakeString(const char* str) {
+  if (str[0] == '\0') {
+    return &art::empty_string;
+  }
+  return new ExtDexFileString{str};
+}
+
+const char* ExtDexFileGetString(const ExtDexFileString* ext_string, /*out*/ size_t* size) {
+  DCHECK(ext_string != nullptr);
+  *size = ext_string->str_.size();
+  return ext_string->str_.data();
+}
+
+void ExtDexFileFreeString(const ExtDexFileString* ext_string) {
+  DCHECK(ext_string != nullptr);
+  if (ext_string != &art::empty_string) {
+    delete (ext_string);
+  }
+}
+
+// Wraps DexFile to add the caching needed by the external interface. This is
+// what gets passed over as ExtDexFile*.
+class ExtDexFile {
+  // Method cache for GetMethodInfoForOffset. This is populated as we iterate
+  // sequentially through the class defs. MethodCacheEntry.name is only set for
+  // methods returned by GetMethodInfoForOffset.
+  std::map<int32_t, art::MethodCacheEntry> method_cache_;
+
+  // Index of first class def for which method_cache_ isn't complete.
+  uint32_t class_def_index_ = 0;
+
+ public:
+  std::unique_ptr<const art::DexFile> dex_file_;
+  explicit ExtDexFile(std::unique_ptr<const art::DexFile>&& dex_file)
+      : dex_file_(std::move(dex_file)) {}
+
+  art::MethodCacheEntry* GetMethodCacheEntryForOffset(int64_t dex_offset) {
+    // First look in the method cache.
+    auto it = method_cache_.upper_bound(dex_offset);
+    if (it != method_cache_.end() && dex_offset >= it->second.offset) {
+      return &it->second;
+    }
+
+    for (; class_def_index_ < dex_file_->NumClassDefs(); class_def_index_++) {
+      art::ClassAccessor accessor(*dex_file_, class_def_index_);
+
+      for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
+        art::CodeItemInstructionAccessor code = method.GetInstructions();
+        if (!code.HasCodeItem()) {
+          continue;
+        }
+
+        int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
+        int32_t len = code.InsnsSizeInBytes();
+        int32_t index = method.GetIndex();
+        auto res =
+            method_cache_.emplace(offset + len, art::MethodCacheEntry{offset, len, index, ""});
+        if (offset <= dex_offset && dex_offset < offset + len) {
+          return &res.first->second;
+        }
+      }
+    }
+
+    return nullptr;
+  }
+
+  const std::string& GetMethodName(art::MethodCacheEntry& entry) {
+    if (entry.name.empty()) {
+      entry.name = dex_file_->PrettyMethod(entry.index, false);
+    }
+    return entry.name;
+  }
+};
+
+int ExtDexFileOpenFromMemory(const void* addr,
+                             /*inout*/ size_t* size,
+                             const char* location,
+                             /*out*/ const ExtDexFileString** ext_error_msg,
+                             /*out*/ ExtDexFile** ext_dex_file) {
+  if (*size < sizeof(art::DexFile::Header)) {
+    *size = sizeof(art::DexFile::Header);
+    *ext_error_msg = nullptr;
+    return false;
+  }
+
+  const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(addr);
+  uint32_t file_size = header->file_size_;
+  if (art::CompactDexFile::IsMagicValid(header->magic_)) {
+    // Compact dex files store the data section separately so that it can be shared.
+    // Therefore we need to extend the read memory range to include it.
+    // TODO: This might be wasteful as we might read data in between as well.
+    //       In practice, this should be fine, as such sharing only happens on disk.
+    uint32_t computed_file_size;
+    if (__builtin_add_overflow(header->data_off_, header->data_size_, &computed_file_size)) {
+      *ext_error_msg = new ExtDexFileString{
+          android::base::StringPrintf("Corrupt CompactDexFile header in '%s'", location)};
+      return false;
+    }
+    if (computed_file_size > file_size) {
+      file_size = computed_file_size;
+    }
+  } else if (!art::StandardDexFile::IsMagicValid(header->magic_)) {
+    *ext_error_msg = new ExtDexFileString{
+        android::base::StringPrintf("Unrecognized dex file header in '%s'", location)};
+    return false;
+  }
+
+  if (*size < file_size) {
+    *size = file_size;
+    *ext_error_msg = nullptr;
+    return false;
+  }
+
+  std::string loc_str(location);
+  art::DexFileLoader loader;
+  std::string error_msg;
+  std::unique_ptr<const art::DexFile> dex_file = loader.Open(static_cast<const uint8_t*>(addr),
+                                                             *size,
+                                                             loc_str,
+                                                             header->checksum_,
+                                                             /*oat_dex_file=*/nullptr,
+                                                             /*verify=*/false,
+                                                             /*verify_checksum=*/false,
+                                                             &error_msg);
+  if (dex_file == nullptr) {
+    *ext_error_msg = new ExtDexFileString{std::move(error_msg)};
+    return false;
+  }
+
+  *ext_dex_file = new ExtDexFile(std::move(dex_file));
+  return true;
+}
+
+int ExtDexFileOpenFromFd(int fd,
+                         off_t offset,
+                         const char* location,
+                         /*out*/ const ExtDexFileString** ext_error_msg,
+                         /*out*/ ExtDexFile** ext_dex_file) {
+  size_t length;
+  {
+    struct stat sbuf;
+    std::memset(&sbuf, 0, sizeof(sbuf));
+    if (fstat(fd, &sbuf) == -1) {
+      *ext_error_msg = new ExtDexFileString{
+          android::base::StringPrintf("fstat '%s' failed: %s", location, std::strerror(errno))};
+      return false;
+    }
+    if (S_ISDIR(sbuf.st_mode)) {
+      *ext_error_msg = new ExtDexFileString{
+          android::base::StringPrintf("Attempt to mmap directory '%s'", location)};
+      return false;
+    }
+    length = sbuf.st_size;
+  }
+
+  if (length < offset + sizeof(art::DexFile::Header)) {
+    *ext_error_msg = new ExtDexFileString{android::base::StringPrintf(
+        "Offset %" PRId64 " too large for '%s' of size %zu", int64_t{offset}, location, length)};
+    return false;
+  }
+
+  // Cannot use MemMap in libartbase here, because it pulls in dlopen which we
+  // can't have when being compiled statically.
+  std::unique_ptr<android::base::MappedFile> map =
+      android::base::MappedFile::FromFd(fd, offset, length, PROT_READ);
+  if (map == nullptr) {
+    *ext_error_msg = new ExtDexFileString{
+        android::base::StringPrintf("mmap '%s' failed: %s", location, std::strerror(errno))};
+    return false;
+  }
+
+  const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(map->data());
+  uint32_t file_size;
+  if (__builtin_add_overflow(offset, header->file_size_, &file_size)) {
+    *ext_error_msg =
+        new ExtDexFileString{android::base::StringPrintf("Corrupt header in '%s'", location)};
+    return false;
+  }
+  if (length < file_size) {
+    *ext_error_msg = new ExtDexFileString{
+        android::base::StringPrintf("Dex file '%s' too short: expected %" PRIu32 ", got %" PRIu64,
+                                    location,
+                                    file_size,
+                                    uint64_t{length})};
+    return false;
+  }
+
+  void* addr = map->data();
+  size_t size = map->size();
+  auto container = std::make_unique<art::MappedFileContainer>(std::move(map));
+
+  std::string loc_str(location);
+  std::string error_msg;
+  art::DexFileLoader loader;
+  std::unique_ptr<const art::DexFile> dex_file = loader.Open(reinterpret_cast<const uint8_t*>(addr),
+                                                             size,
+                                                             loc_str,
+                                                             header->checksum_,
+                                                             /*oat_dex_file=*/nullptr,
+                                                             /*verify=*/false,
+                                                             /*verify_checksum=*/false,
+                                                             &error_msg,
+                                                             std::move(container));
+  if (dex_file == nullptr) {
+    *ext_error_msg = new ExtDexFileString{std::move(error_msg)};
+    return false;
+  }
+  *ext_dex_file = new ExtDexFile(std::move(dex_file));
+  return true;
+}
+
+int ExtDexFileGetMethodInfoForOffset(ExtDexFile* ext_dex_file,
+                                     int64_t dex_offset,
+                                     /*out*/ ExtDexFileMethodInfo* method_info) {
+  if (!ext_dex_file->dex_file_->IsInDataSection(ext_dex_file->dex_file_->Begin() + dex_offset)) {
+    return false;  // The DEX offset is not within the bytecode of this dex file.
+  }
+
+  if (ext_dex_file->dex_file_->IsCompactDexFile()) {
+    // The data section of compact dex files might be shared.
+    // Check the subrange unique to this compact dex.
+    const art::CompactDexFile::Header& cdex_header =
+        ext_dex_file->dex_file_->AsCompactDexFile()->GetHeader();
+    uint32_t begin = cdex_header.data_off_ + cdex_header.OwnedDataBegin();
+    uint32_t end = cdex_header.data_off_ + cdex_header.OwnedDataEnd();
+    if (dex_offset < begin || dex_offset >= end) {
+      return false;  // The DEX offset is not within the bytecode of this dex file.
+    }
+  }
+
+  art::MethodCacheEntry* entry = ext_dex_file->GetMethodCacheEntryForOffset(dex_offset);
+  if (entry != nullptr) {
+    method_info->offset = entry->offset;
+    method_info->len = entry->len;
+    method_info->name = new ExtDexFileString{ext_dex_file->GetMethodName(*entry)};
+    return true;
+  }
+
+  return false;
+}
+
+void ExtDexFileGetAllMethodInfos(ExtDexFile* ext_dex_file,
+                                 int with_signature,
+                                 ExtDexFileMethodInfoCallback* method_info_cb,
+                                 void* user_data) {
+  for (art::ClassAccessor accessor : ext_dex_file->dex_file_->GetClasses()) {
+    for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
+      art::CodeItemInstructionAccessor code = method.GetInstructions();
+      if (!code.HasCodeItem()) {
+        continue;
+      }
+
+      ExtDexFileMethodInfo method_info;
+      method_info.offset = static_cast<int32_t>(reinterpret_cast<const uint8_t*>(code.Insns()) -
+                                                ext_dex_file->dex_file_->Begin());
+      method_info.len = code.InsnsSizeInBytes();
+      method_info.name = new ExtDexFileString{
+          ext_dex_file->dex_file_->PrettyMethod(method.GetIndex(), with_signature)};
+      method_info_cb(&method_info, user_data);
+    }
+  }
+}
+
+void ExtDexFileFree(ExtDexFile* ext_dex_file) { delete (ext_dex_file); }
+
+}  // extern "C"