Merge remote-tracking branch 'aosp/upstream-main' into dev am: a90c04389f am: ddc43dc959 am: d9eed84a58 am: 58b4d19a6b am: 7be4275909

Original change: https://android-review.googlesource.com/c/platform/external/zucchini/+/1877854

Change-Id: Ifdb1f7882cc237258a3395d28158d50bb4b43136
diff --git a/README.md b/README.md
index d3fd0a1..2d885b6 100644
--- a/README.md
+++ b/README.md
@@ -204,12 +204,14 @@
 Name | Format | Description
 --- | --- | ---
 magic | uint32 = kMagic | Magic value.
+major_version | uint16 | Major version number indicating breaking changes.
+minor_version | uint16 | Minor version number indicating possibly breaking changes.
 old_size | uint32 | Size of old file in bytes.
 old_crc | uint32 | CRC32 of old file.
 new_size | uint32 | Size of new file in bytes.
 new_crc | uint32 | CRC32 of new file.
 
-**kMagic** == `'Z' | ('u' << 8) | ('c' << 16)`
+**kMagic** == `'Z' | ('u' << 8) | ('c' << 16) | ('c' << 24)`
 
 **PatchElement**
 Contains all the information required to produce a single element in new file.
@@ -235,6 +237,7 @@
 new_offset | uint32 | Starting offset of the element in new file.
 new_length | uint32 | Length of the element in new file.
 exe_type | uint32 | Executable type for this unit, see `enum ExecutableType`.
+version | uint16 | Version specific to the executable type for this unit.
 
 **EquivalenceList**
 Encodes a list of equivalences, where dst offsets (in new image) are ascending.
@@ -278,3 +281,16 @@
 --- | --- | ---
 size |uint32 | Size of content in bytes.
 content |T[] | List of integers.
+
+# Format Changelog
+All breaking changes to zucchini patch format will be documented in this
+section.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+## [Unreleased]
+
+## [1.0] - 2021-10-27
+### Added
+Major/Minor version is encoded in PatchHeader
+Disassembler version associated with an element version is encoded in PatchElementHeader.
diff --git a/algorithm.h b/algorithm.h
index f5d49e3..4cafe93 100644
--- a/algorithm.h
+++ b/algorithm.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include <algorithm>
+#include <deque>
 #include <type_traits>
 #include <vector>
 
@@ -69,7 +70,7 @@
 
 // Sorts values in |container| and removes duplicates.
 template <class T>
-void SortAndUniquify(std::vector<T>* container) {
+void SortAndUniquify(std::deque<T>* container) {
   std::sort(container->begin(), container->end());
   container->erase(std::unique(container->begin(), container->end()),
                    container->end());
diff --git a/disassembler_dex.cc b/disassembler_dex.cc
index 5b25c50..8ea0314 100644
--- a/disassembler_dex.cc
+++ b/disassembler_dex.cc
@@ -69,6 +69,10 @@
       return sizeof(dex::MethodIdItem);
     case dex::kTypeClassDefItem:
       return sizeof(dex::ClassDefItem);
+    case dex::kTypeCallSiteIdItem:
+      return sizeof(dex::CallSiteIdItem);
+    case dex::kTypeMethodHandleItem:
+      return sizeof(dex::MethodHandleItem);
     // No need to handle dex::kTypeMapList.
     case dex::kTypeTypeList:
       return sizeof(uint32_t);  // Variable-length.
@@ -416,17 +420,23 @@
 
   // |item_size| is the size of a fixed-size item. |rel_location| is the
   // relative location of MVI from the start of the item containing it.
+  // |rel_item_offset| is the offset to use relative to |item_offset| in cases
+  // where a value other than |rel_location| is required. For an example of this
+  // see ReadMethodHandleFieldOrMethodId.
   ItemReferenceReader(offset_t lo,
                       offset_t hi,
                       const dex::MapItem& map_item,
                       size_t item_size,
                       size_t rel_location,
-                      Mapper&& mapper)
+                      Mapper&& mapper,
+                      bool mapper_wants_item = false)
       : hi_(hi),
         item_base_offset_(base::checked_cast<offset_t>(map_item.offset)),
         num_items_(base::checked_cast<uint32_t>(map_item.size)),
         item_size_(base::checked_cast<uint32_t>(item_size)),
         rel_location_(base::checked_cast<uint32_t>(rel_location)),
+        mapper_input_delta_(
+            mapper_wants_item ? 0 : base::checked_cast<uint32_t>(rel_location)),
         mapper_(std::move(mapper)) {
     static_assert(sizeof(decltype(map_item.offset)) <= sizeof(offset_t),
                   "map_item.offset too large.");
@@ -457,7 +467,11 @@
       // |reference_width| is unneeded.
       if (location >= hi_)
         break;
-      const offset_t target = mapper_.Run(location);
+
+      // |location == item_offset + mapper_input_delta_| in the majority of
+      // cases. The exception is when |mapper_| wants an item aligned location
+      // instead e.g. ReadMethodHandleFieldOrMethodId.
+      const offset_t target = mapper_.Run(item_offset + mapper_input_delta_);
 
       // kDexSentinelOffset (0) may appear for the following:
       // - ProtoIdItem: parameters_off.
@@ -467,6 +481,9 @@
       // - AnnotationSetRefItem: annotations_off.
       // kDexSentinelIndexAsOffset (0xFFFFFFFF) may appear for the following:
       // - ClassDefItem: superclass_idx, source_file_idx.
+      // - MethodHandleItem: |mapper_| uses ReadMethodHandleFieldOrMethodId and
+      //   determines the item at |cur_idx_| is not of the required reference
+      //   type.
       if (target == kDexSentinelOffset || target == kDexSentinelIndexAsOffset) {
         ++cur_idx_;
         continue;
@@ -492,6 +509,7 @@
   const uint32_t num_items_;
   const uint32_t item_size_;
   const uint32_t rel_location_;
+  const uint32_t mapper_input_delta_;
   const Mapper mapper_;
   offset_t cur_idx_ = 0;
 };
@@ -688,6 +706,53 @@
          base::checked_cast<offset_t>(unsafe_idx * target_item_size);
 }
 
+// Reads a field or method index of the MethodHandleItem located at |location|
+// in |image| and translates |method_handle_item.field_or_method_id| to the
+// offset of a fixed-size item specified by |target_map_item| and
+// |target_item_size|. The index is deemed to be of the correct target type if
+// |method_handle_item.method_handle_type| falls within the range [|min_type|,
+// |max_type|]. If the target type is correct ReadTargetIndex is called.
+// Returns the target offset if valid, or kDexSentinelIndexAsOffset if
+// |method_handle_item.method_handle_type| is of the wrong type, or
+// kInvalidOffset otherwise.
+//
+// As of DEX version 39 MethodHandleType values for FieldId and MethodId each
+// form one consecutive block of values. If this changes, then the interface to
+// this function will need to be redesigned.
+static offset_t ReadMethodHandleFieldOrMethodId(
+    ConstBufferView image,
+    const dex::MapItem& target_map_item,
+    size_t target_item_size,
+    dex::MethodHandleType min_type,
+    dex::MethodHandleType max_type,
+    offset_t location) {
+  dex::MethodHandleItem method_handle_item =
+      image.read<dex::MethodHandleItem>(location);
+
+  // Cannot use base::checked_cast as dex::MethodHandleType is an enum class so
+  // static_assert on the size instead.
+  static_assert(sizeof(decltype(dex::MethodHandleItem::method_handle_type)) <=
+                    sizeof(dex::MethodHandleType),
+                "dex::MethodHandleItem::method_handle_type may not fit into "
+                "dex::MethodHandleType.");
+  dex::MethodHandleType method_handle_type =
+      static_cast<dex::MethodHandleType>(method_handle_item.method_handle_type);
+
+  if (method_handle_type >= dex::MethodHandleType::kMaxMethodHandleType) {
+    return kInvalidOffset;
+  }
+
+  // Use DexSentinelIndexAsOffset to skip the item as it isn't of the
+  // corresponding method handle type.
+  if (method_handle_type < min_type || method_handle_type > max_type) {
+    return kDexSentinelIndexAsOffset;
+  }
+
+  return ReadTargetIndex<decltype(dex::MethodHandleItem::field_or_method_id)>(
+      image, target_map_item, target_item_size,
+      location + offsetof(dex::MethodHandleItem, field_or_method_id));
+}
+
 // Reads uint32_t value in |image| at (valid) |location| and checks whether it
 // is a safe offset of a fixed-size item. Returns the target offset (possibly a
 // sentinel) if valid, or kInvalidOffset otherwise. This is compatible with
@@ -779,10 +844,11 @@
     dex_version = dex_version * 10 + (header->magic[i] - '0');
   }
 
-  // Only support DEX versions 35 and 37.
-  // TODO(huangs): Handle version 38.
-  if (dex_version != 35 && dex_version != 37)
+  // Only support DEX versions 35, 37, 38, and 39
+  if (dex_version != 35 && dex_version != 37 && dex_version != 38 &&
+      dex_version != 39) {
     return false;
+  }
 
   if (header->file_size > image.size() ||
       header->file_size < sizeof(dex::HeaderItem) ||
@@ -864,18 +930,27 @@
       {{2, TypeTag(kCodeToTypeId), PoolTag(kTypeId)},
        &DisassemblerDex::MakeReadCodeToTypeId16,
        &DisassemblerDex::MakeWriteTypeId16},
+      {{2, TypeTag(kCodeToProtoId), PoolTag(kProtoId)},
+       &DisassemblerDex::MakeReadCodeToProtoId16,
+       &DisassemblerDex::MakeWriteProtoId16},
       {{2, TypeTag(kMethodIdToProtoId), PoolTag(kProtoId)},
        &DisassemblerDex::MakeReadMethodIdToProtoId16,
        &DisassemblerDex::MakeWriteProtoId16},
       {{2, TypeTag(kCodeToFieldId), PoolTag(kFieldId)},
        &DisassemblerDex::MakeReadCodeToFieldId16,
        &DisassemblerDex::MakeWriteFieldId16},
+      {{2, TypeTag(kMethodHandleToFieldId), PoolTag(kFieldId)},
+       &DisassemblerDex::MakeReadMethodHandleToFieldId16,
+       &DisassemblerDex::MakeWriteFieldId16},
       {{4, TypeTag(kAnnotationsDirectoryToFieldId), PoolTag(kFieldId)},
        &DisassemblerDex::MakeReadAnnotationsDirectoryToFieldId32,
        &DisassemblerDex::MakeWriteFieldId32},
       {{2, TypeTag(kCodeToMethodId), PoolTag(kMethodId)},
        &DisassemblerDex::MakeReadCodeToMethodId16,
        &DisassemblerDex::MakeWriteMethodId16},
+      {{2, TypeTag(kMethodHandleToMethodId), PoolTag(kMethodId)},
+       &DisassemblerDex::MakeReadMethodHandleToMethodId16,
+       &DisassemblerDex::MakeWriteMethodId16},
       {{4, TypeTag(kAnnotationsDirectoryToMethodId), PoolTag(kMethodId)},
        &DisassemblerDex::MakeReadAnnotationsDirectoryToMethodId32,
        &DisassemblerDex::MakeWriteMethodId32},
@@ -883,6 +958,12 @@
         PoolTag(kMethodId)},
        &DisassemblerDex::MakeReadAnnotationsDirectoryToParameterMethodId32,
        &DisassemblerDex::MakeWriteMethodId32},
+      {{2, TypeTag(kCodeToCallSiteId), PoolTag(kCallSiteId)},
+       &DisassemblerDex::MakeReadCodeToCallSiteId16,
+       &DisassemblerDex::MakeWriteCallSiteId16},
+      {{2, TypeTag(kCodeToMethodHandle), PoolTag(kMethodHandle)},
+       &DisassemblerDex::MakeReadCodeToMethodHandle16,
+       &DisassemblerDex::MakeWriteMethodHandle16},
       {{4, TypeTag(kProtoIdToParametersTypeList), PoolTag(kTypeList)},
        &DisassemblerDex::MakeReadProtoIdToParametersTypeList,
        &DisassemblerDex::MakeWriteAbs32},
@@ -936,6 +1017,9 @@
         PoolTag(kAnnotationsDirectory)},
        &DisassemblerDex::MakeReadClassDefToAnnotationDirectory,
        &DisassemblerDex::MakeWriteAbs32},
+      {{4, TypeTag(kCallSiteIdToCallSite), PoolTag(kCallSite)},
+       &DisassemblerDex::MakeReadCallSiteIdToCallSite32,
+       &DisassemblerDex::MakeWriteAbs32},
   };
 }
 
@@ -1125,6 +1209,46 @@
       offsetof(dex::ClassDefItem, static_values_off), std::move(mapper));
 }
 
+std::unique_ptr<ReferenceReader>
+DisassemblerDex::MakeReadCallSiteIdToCallSite32(offset_t lo, offset_t hi) {
+  auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
+  return std::make_unique<ItemReferenceReader>(
+      lo, hi, call_site_map_item_, sizeof(dex::CallSiteIdItem),
+      offsetof(dex::CallSiteIdItem, call_site_off), std::move(mapper));
+}
+
+std::unique_ptr<ReferenceReader>
+DisassemblerDex::MakeReadMethodHandleToFieldId16(offset_t lo, offset_t hi) {
+  auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_,
+                                    field_map_item_, sizeof(dex::FieldIdItem),
+                                    dex::MethodHandleType::kStaticPut,
+                                    dex::MethodHandleType::kInstanceGet);
+  // Use |mapper_wants_item == true| for ItemReferenceReader such that
+  // |location| is aligned with MethodHandleItem when passed to |mapper|. This
+  // allows ReadMethodHandleFieldOrMethodId to safely determine whether the
+  // reference in the MethodHandleItem is of the correct type to be emitted.
+  return std::make_unique<ItemReferenceReader>(
+      lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem),
+      offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper),
+      /*mapper_wants_item=*/true);
+}
+
+std::unique_ptr<ReferenceReader>
+DisassemblerDex::MakeReadMethodHandleToMethodId16(offset_t lo, offset_t hi) {
+  auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_,
+                                    method_map_item_, sizeof(dex::MethodIdItem),
+                                    dex::MethodHandleType::kInvokeStatic,
+                                    dex::MethodHandleType::kInvokeInterface);
+  // Use |mapper_wants_item == true| for ItemReferenceReader such that
+  // |location| is aligned with MethodHandleItem when passed to |mapper|. This
+  // allows ReadMethodHandleFieldOrMethodId to safely determine whether the
+  // reference in the MethodHandleItem is of the correct type to be emitted.
+  return std::make_unique<ItemReferenceReader>(
+      lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem),
+      offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper),
+      /*mapper_wants_item=*/true);
+}
+
 std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadTypeListToTypeId16(
     offset_t lo,
     offset_t hi) {
@@ -1233,6 +1357,10 @@
       std::move(mapper));
 }
 
+// MakeReadCode* readers use offset relative to the instruction beginning based
+// on the instruction format ID.
+// See https://source.android.com/devices/tech/dalvik/instruction-formats
+
 std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToStringId16(
     offset_t lo,
     offset_t hi) {
@@ -1295,6 +1423,71 @@
       image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
 }
 
+std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToProtoId16(
+    offset_t lo,
+    offset_t hi) {
+  auto filter = base::BindRepeating(
+      [](const InstructionParser::Value& value) -> offset_t {
+        if (value.instr->format == dex::FormatId::c) {
+          if (value.instr->opcode == 0xFA ||  // invoke-polymorphic
+              value.instr->opcode == 0xFB) {  // invoke-polymorphic/range
+            // HHHH from e.g, invoke-polymorphic {vC, vD, vE, vF, vG},
+            //   meth@BBBB, proto@HHHH
+            return value.instr_offset + 6;
+          }
+          if (value.instr->opcode == 0xFF) {  // const-method-type
+            // BBBB from e.g., const-method-type vAA, proto@BBBB
+            return value.instr_offset + 2;
+          }
+        }
+        return kInvalidOffset;
+      });
+  auto mapper = base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
+                                    proto_map_item_, sizeof(dex::ProtoIdItem));
+  return std::make_unique<InstructionReferenceReader>(
+      image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
+}
+
+std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToCallSiteId16(
+    offset_t lo,
+    offset_t hi) {
+  auto filter = base::BindRepeating(
+      [](const InstructionParser::Value& value) -> offset_t {
+        if (value.instr->format == dex::FormatId::c &&
+            (value.instr->opcode == 0xFC ||   // invoke-custom
+             value.instr->opcode == 0xFD)) {  // invoke-custom/range
+          // BBBB from e.g, invoke-custom {vC, vD, vE, vF, vG},
+          //   call_site@BBBB
+          return value.instr_offset + 2;
+        }
+        return kInvalidOffset;
+      });
+  auto mapper =
+      base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
+                          call_site_map_item_, sizeof(dex::CallSiteIdItem));
+  return std::make_unique<InstructionReferenceReader>(
+      image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
+}
+
+std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToMethodHandle16(
+    offset_t lo,
+    offset_t hi) {
+  auto filter = base::BindRepeating(
+      [](const InstructionParser::Value& value) -> offset_t {
+        if (value.instr->format == dex::FormatId::c &&
+            value.instr->opcode == 0xFE) {  // const-method-handle
+          // BBBB from e.g, const-method-handle vAA, method_handle@BBBB
+          return value.instr_offset + 2;
+        }
+        return kInvalidOffset;
+      });
+  auto mapper = base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
+                                    method_handle_map_item_,
+                                    sizeof(dex::MethodHandleItem));
+  return std::make_unique<InstructionReferenceReader>(
+      image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
+}
+
 std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToFieldId16(
     offset_t lo,
     offset_t hi) {
@@ -1321,7 +1514,9 @@
       [](const InstructionParser::Value& value) -> offset_t {
         if (value.instr->format == dex::FormatId::c &&
             (value.instr->opcode == 0x6E ||   // invoke-kind
-             value.instr->opcode == 0x74)) {  // invoke-kind/range
+             value.instr->opcode == 0x74 ||   // invoke-kind/range
+             value.instr->opcode == 0xFA ||   // invoke-polymorphic
+             value.instr->opcode == 0xFB)) {  // invoke-polymorphic/range
           // BBBB from e.g., invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB.
           return value.instr_offset + 2;
         }
@@ -1491,6 +1686,22 @@
   return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
 }
 
+std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteCallSiteId16(
+    MutableBufferView image) {
+  auto writer =
+      base::BindRepeating(WriteTargetIndex<uint16_t>, call_site_map_item_,
+                          sizeof(dex::CallSiteIdItem));
+  return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
+}
+
+std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteMethodHandle16(
+    MutableBufferView image) {
+  auto writer =
+      base::BindRepeating(WriteTargetIndex<uint16_t>, method_handle_map_item_,
+                          sizeof(dex::MethodHandleItem));
+  return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
+}
+
 std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteRelCode8(
     MutableBufferView image) {
   auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) {
@@ -1587,15 +1798,7 @@
     return false;
 
   // Read and validate map list, ensuring that required item types are present.
-  // - GetItemBaseSize() should have an entry for each item.
-  // - dex::kTypeCodeItem is actually not required; it's possible to have a DEX
-  //   file with classes that have no code. However, this is unlikely to appear
-  //   in application, so for simplicity we require DEX files to have code.
-  std::set<uint16_t> required_item_types = {
-      dex::kTypeStringIdItem, dex::kTypeTypeIdItem,   dex::kTypeProtoIdItem,
-      dex::kTypeFieldIdItem,  dex::kTypeMethodIdItem, dex::kTypeClassDefItem,
-      dex::kTypeTypeList,     dex::kTypeCodeItem,
-  };
+  // GetItemBaseSize() should have an entry for each item.
   for (offset_t i = 0; i < list_size; ++i) {
     const dex::MapItem* item = &item_list[i];
     // Reject unreasonably large |item->size|.
@@ -1605,29 +1808,46 @@
       return false;
     if (!map_item_map_.insert(std::make_pair(item->type, item)).second)
       return false;  // A given type must appear at most once.
-    required_item_types.erase(item->type);
   }
-  // TODO(huangs): Replace this with guards throughout file.
-  if (!required_item_types.empty())
-    return false;
 
   // Make local copies of main map items.
-  string_map_item_ = *map_item_map_[dex::kTypeStringIdItem];
-  type_map_item_ = *map_item_map_[dex::kTypeTypeIdItem];
-  proto_map_item_ = *map_item_map_[dex::kTypeProtoIdItem];
-  field_map_item_ = *map_item_map_[dex::kTypeFieldIdItem];
-  method_map_item_ = *map_item_map_[dex::kTypeMethodIdItem];
-  class_def_map_item_ = *map_item_map_[dex::kTypeClassDefItem];
-  type_list_map_item_ = *map_item_map_[dex::kTypeTypeList];
-  code_map_item_ = *map_item_map_[dex::kTypeCodeItem];
-
-  // The following types are optional and may not be present in every DEX file.
+  if (map_item_map_.count(dex::kTypeStringIdItem)) {
+    string_map_item_ = *map_item_map_[dex::kTypeStringIdItem];
+  }
+  if (map_item_map_.count(dex::kTypeTypeIdItem)) {
+    type_map_item_ = *map_item_map_[dex::kTypeTypeIdItem];
+  }
+  if (map_item_map_.count(dex::kTypeProtoIdItem)) {
+    proto_map_item_ = *map_item_map_[dex::kTypeProtoIdItem];
+  }
+  if (map_item_map_.count(dex::kTypeFieldIdItem)) {
+    field_map_item_ = *map_item_map_[dex::kTypeFieldIdItem];
+  }
+  if (map_item_map_.count(dex::kTypeMethodIdItem)) {
+    method_map_item_ = *map_item_map_[dex::kTypeMethodIdItem];
+  }
+  if (map_item_map_.count(dex::kTypeClassDefItem)) {
+    class_def_map_item_ = *map_item_map_[dex::kTypeClassDefItem];
+  }
+  if (map_item_map_.count(dex::kTypeCallSiteIdItem)) {
+    call_site_map_item_ = *map_item_map_[dex::kTypeCallSiteIdItem];
+  }
+  if (map_item_map_.count(dex::kTypeMethodHandleItem)) {
+    method_handle_map_item_ = *map_item_map_[dex::kTypeMethodHandleItem];
+  }
+  if (map_item_map_.count(dex::kTypeTypeList)) {
+    type_list_map_item_ = *map_item_map_[dex::kTypeTypeList];
+  }
   if (map_item_map_.count(dex::kTypeAnnotationSetRefList)) {
     annotation_set_ref_list_map_item_ =
         *map_item_map_[dex::kTypeAnnotationSetRefList];
   }
-  if (map_item_map_.count(dex::kTypeAnnotationSetItem))
+  if (map_item_map_.count(dex::kTypeAnnotationSetItem)) {
     annotation_set_map_item_ = *map_item_map_[dex::kTypeAnnotationSetItem];
+  }
+  if (map_item_map_.count(dex::kTypeCodeItem)) {
+    code_map_item_ = *map_item_map_[dex::kTypeCodeItem];
+  }
   if (map_item_map_.count(dex::kTypeAnnotationsDirectoryItem)) {
     annotations_directory_map_item_ =
         *map_item_map_[dex::kTypeAnnotationsDirectoryItem];
diff --git a/disassembler_dex.h b/disassembler_dex.h
index 2038a3c..d9d93b2 100644
--- a/disassembler_dex.h
+++ b/disassembler_dex.h
@@ -24,6 +24,7 @@
 
 class DisassemblerDex : public Disassembler {
  public:
+  static constexpr uint16_t kVersion = 1;
   // Pools follow canonical order.
   enum ReferencePool : uint8_t {
     kStringId,
@@ -32,8 +33,8 @@
     kFieldId,
     kMethodId,
     // kClassDef,  // Unused
-    // kCallSiteId, // Unused
-    // kMethodHandle, // Unused
+    kCallSiteId,
+    kMethodHandle,
     kTypeList,
     kAnnotationSetRefList,
     kAnnotionSet,
@@ -43,7 +44,7 @@
     kAnnotation,
     kEncodedArray,
     kAnnotationsDirectory,
-    // kCallSite, // Unused
+    kCallSite,
     kNumPools
   };
 
@@ -69,15 +70,22 @@
     kTypeListToTypeId,
     kCodeToTypeId,
 
-    kMethodIdToProtoId,  // kProtoId
+    kCodeToProtoId,  // kProtoId
+    kMethodIdToProtoId,
 
     kCodeToFieldId,  // kFieldId
+    kMethodHandleToFieldId,
     kAnnotationsDirectoryToFieldId,
 
     kCodeToMethodId,  // kMethodId
+    kMethodHandleToMethodId,
     kAnnotationsDirectoryToMethodId,
     kAnnotationsDirectoryToParameterMethodId,
 
+    kCodeToCallSiteId,  // kCallSiteId
+
+    kCodeToMethodHandle,  // kMethodHandle
+
     kProtoIdToParametersTypeList,  // kTypeList
     kClassDefToInterfacesTypeList,
 
@@ -102,10 +110,7 @@
 
     kClassDefToAnnotationDirectory,  // kAnnotationsDirectory
 
-    // Intentionally ignored references (never appeared in test corpus).
-    // kMethodHandleToFieldId,
-    // kMethodHandleToMethodId,
-    // kCallSiteIdToCallSite,
+    kCallSiteIdToCallSite,  // kCallSite
 
     kNumTypes
   };
@@ -172,6 +177,13 @@
   std::unique_ptr<ReferenceReader> MakeReadClassDefToStaticValuesEncodedArray(
       offset_t lo,
       offset_t hi);
+  std::unique_ptr<ReferenceReader> MakeReadCallSiteIdToCallSite32(offset_t lo,
+                                                                  offset_t hi);
+  std::unique_ptr<ReferenceReader> MakeReadMethodHandleToFieldId16(offset_t lo,
+                                                                   offset_t hi);
+  std::unique_ptr<ReferenceReader> MakeReadMethodHandleToMethodId16(
+      offset_t lo,
+      offset_t hi);
   std::unique_ptr<ReferenceReader> MakeReadTypeListToTypeId16(offset_t lo,
                                                               offset_t hi);
   std::unique_ptr<ReferenceReader> MakeReadAnnotationSetToAnnotation(
@@ -203,10 +215,16 @@
                                                             offset_t hi);
   std::unique_ptr<ReferenceReader> MakeReadCodeToTypeId16(offset_t lo,
                                                           offset_t hi);
+  std::unique_ptr<ReferenceReader> MakeReadCodeToProtoId16(offset_t lo,
+                                                           offset_t hi);
   std::unique_ptr<ReferenceReader> MakeReadCodeToFieldId16(offset_t lo,
                                                            offset_t hi);
   std::unique_ptr<ReferenceReader> MakeReadCodeToMethodId16(offset_t lo,
                                                             offset_t hi);
+  std::unique_ptr<ReferenceReader> MakeReadCodeToCallSiteId16(offset_t lo,
+                                                              offset_t hi);
+  std::unique_ptr<ReferenceReader> MakeReadCodeToMethodHandle16(offset_t lo,
+                                                                offset_t hi);
   std::unique_ptr<ReferenceReader> MakeReadCodeToRelCode8(offset_t lo,
                                                           offset_t hi);
   std::unique_ptr<ReferenceReader> MakeReadCodeToRelCode16(offset_t lo,
@@ -225,6 +243,10 @@
   std::unique_ptr<ReferenceWriter> MakeWriteFieldId32(MutableBufferView image);
   std::unique_ptr<ReferenceWriter> MakeWriteMethodId16(MutableBufferView image);
   std::unique_ptr<ReferenceWriter> MakeWriteMethodId32(MutableBufferView image);
+  std::unique_ptr<ReferenceWriter> MakeWriteCallSiteId16(
+      MutableBufferView image);
+  std::unique_ptr<ReferenceWriter> MakeWriteMethodHandle16(
+      MutableBufferView image);
   std::unique_ptr<ReferenceWriter> MakeWriteRelCode8(MutableBufferView image);
   std::unique_ptr<ReferenceWriter> MakeWriteRelCode16(MutableBufferView image);
   std::unique_ptr<ReferenceWriter> MakeWriteRelCode32(MutableBufferView image);
@@ -248,12 +270,12 @@
   dex::MapItem field_map_item_ = {};
   dex::MapItem method_map_item_ = {};
   dex::MapItem class_def_map_item_ = {};
+  dex::MapItem call_site_map_item_ = {};
+  dex::MapItem method_handle_map_item_ = {};
   dex::MapItem type_list_map_item_ = {};
-  dex::MapItem code_map_item_ = {};
-
-  // Optionally supported (not all DEX files have these).
   dex::MapItem annotation_set_ref_list_map_item_ = {};
   dex::MapItem annotation_set_map_item_ = {};
+  dex::MapItem code_map_item_ = {};
   dex::MapItem annotations_directory_map_item_ = {};
 
   // Sorted list of offsets of parsed items in |image_|.
diff --git a/disassembler_elf.cc b/disassembler_elf.cc
index 94dc12a..22a29ba 100644
--- a/disassembler_elf.cc
+++ b/disassembler_elf.cc
@@ -596,6 +596,24 @@
       image, AbsoluteAddress(Traits::kBitness, 0), this->translator_);
 }
 
+template <class TRAITS>
+template <class ADDR_TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerElfArm<TRAITS>::MakeReadRel32(
+    offset_t lower,
+    offset_t upper) {
+  return std::make_unique<Rel32ReaderArm<ADDR_TRAITS>>(
+      this->translator_, this->image_,
+      this->rel32_locations_table_[ADDR_TRAITS::addr_type], lower, upper);
+}
+
+template <class TRAITS>
+template <class ADDR_TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerElfArm<TRAITS>::MakeWriteRel32(
+    MutableBufferView image) {
+  return std::make_unique<Rel32WriterArm<ADDR_TRAITS>>(this->translator_,
+                                                       image);
+}
+
 /******** DisassemblerElfAArch32 ********/
 
 DisassemblerElfAArch32::DisassemblerElfAArch32() = default;
@@ -616,24 +634,34 @@
        &DisassemblerElfAArch32::MakeWriteAbs32},
       {ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_A24),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch32::MakeReadRel32A24,
-       &DisassemblerElfAArch32::MakeWriteRel32A24},
+       &DisassemblerElfAArch32::MakeReadRel32<
+           AArch32Rel32Translator::AddrTraits_A24>,
+       &DisassemblerElfAArch32::MakeWriteRel32<
+           AArch32Rel32Translator::AddrTraits_A24>},
       {ReferenceTypeTraits{2, TypeTag(AArch32ReferenceType::kRel32_T8),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch32::MakeReadRel32T8,
-       &DisassemblerElfAArch32::MakeWriteRel32T8},
+       &DisassemblerElfAArch32::MakeReadRel32<
+           AArch32Rel32Translator::AddrTraits_T8>,
+       &DisassemblerElfAArch32::MakeWriteRel32<
+           AArch32Rel32Translator::AddrTraits_T8>},
       {ReferenceTypeTraits{2, TypeTag(AArch32ReferenceType::kRel32_T11),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch32::MakeReadRel32T11,
-       &DisassemblerElfAArch32::MakeWriteRel32T11},
+       &DisassemblerElfAArch32::MakeReadRel32<
+           AArch32Rel32Translator::AddrTraits_T11>,
+       &DisassemblerElfAArch32::MakeWriteRel32<
+           AArch32Rel32Translator::AddrTraits_T11>},
       {ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_T20),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch32::MakeReadRel32T20,
-       &DisassemblerElfAArch32::MakeWriteRel32T20},
+       &DisassemblerElfAArch32::MakeReadRel32<
+           AArch32Rel32Translator::AddrTraits_T20>,
+       &DisassemblerElfAArch32::MakeWriteRel32<
+           AArch32Rel32Translator::AddrTraits_T20>},
       {ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_T24),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch32::MakeReadRel32T24,
-       &DisassemblerElfAArch32::MakeWriteRel32T24},
+       &DisassemblerElfAArch32::MakeReadRel32<
+           AArch32Rel32Translator::AddrTraits_T24>,
+       &DisassemblerElfAArch32::MakeWriteRel32<
+           AArch32Rel32Translator::AddrTraits_T24>},
   };
 }
 
@@ -673,86 +701,6 @@
   return num < den * kAArch32BitCondAlwaysDensityThreshold;
 }
 
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32A24(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_A24>>(
-      translator_, image_,
-      rel32_locations_table_[AArch32Rel32Translator::ADDR_A24], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32A24(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch32Rel32Translator::AddrTraits_A24>>(translator_,
-                                                              image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T8(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T8>>(
-      translator_, image_,
-      rel32_locations_table_[AArch32Rel32Translator::ADDR_T8], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T8(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T8>>(translator_,
-                                                             image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T11(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T11>>(
-      translator_, image_,
-      rel32_locations_table_[AArch32Rel32Translator::ADDR_T11], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T11(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T11>>(translator_,
-                                                              image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T20(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T20>>(
-      translator_, image_,
-      rel32_locations_table_[AArch32Rel32Translator::ADDR_T20], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T20(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T20>>(translator_,
-                                                              image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T24(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T24>>(
-      translator_, image_,
-      rel32_locations_table_[AArch32Rel32Translator::ADDR_T24], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T24(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T24>>(translator_,
-                                                              image);
-}
-
 /******** DisassemblerElfAArch64 ********/
 
 DisassemblerElfAArch64::DisassemblerElfAArch64() = default;
@@ -774,16 +722,22 @@
        &DisassemblerElfAArch64::MakeWriteAbs32},
       {ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd14),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch64::MakeReadRel32Immd14,
-       &DisassemblerElfAArch64::MakeWriteRel32Immd14},
+       &DisassemblerElfAArch64::MakeReadRel32<
+           AArch64Rel32Translator::AddrTraits_Immd14>,
+       &DisassemblerElfAArch64::MakeWriteRel32<
+           AArch64Rel32Translator::AddrTraits_Immd14>},
       {ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd19),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch64::MakeReadRel32Immd19,
-       &DisassemblerElfAArch64::MakeWriteRel32Immd19},
+       &DisassemblerElfAArch64::MakeReadRel32<
+           AArch64Rel32Translator::AddrTraits_Immd19>,
+       &DisassemblerElfAArch64::MakeWriteRel32<
+           AArch64Rel32Translator::AddrTraits_Immd19>},
       {ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd26),
                            PoolTag(ArmReferencePool::kPoolRel32)},
-       &DisassemblerElfAArch64::MakeReadRel32Immd26,
-       &DisassemblerElfAArch64::MakeWriteRel32Immd26},
+       &DisassemblerElfAArch64::MakeReadRel32<
+           AArch64Rel32Translator::AddrTraits_Immd26>,
+       &DisassemblerElfAArch64::MakeWriteRel32<
+           AArch64Rel32Translator::AddrTraits_Immd26>},
   };
 }
 
@@ -793,57 +747,6 @@
   return std::make_unique<Rel32FinderAArch64>(image_, translator_);
 }
 
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch64::MakeReadRel32Immd14(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch64Rel32Translator::AddrTraits_Immd14>>(
-      translator_, this->image_,
-      rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD14], lower,
-      upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch64::MakeWriteRel32Immd14(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch64Rel32Translator::AddrTraits_Immd14>>(translator_,
-                                                                 image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch64::MakeReadRel32Immd19(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch64Rel32Translator::AddrTraits_Immd19>>(
-      translator_, this->image_,
-      rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD19], lower,
-      upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch64::MakeWriteRel32Immd19(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch64Rel32Translator::AddrTraits_Immd19>>(translator_,
-                                                                 image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch64::MakeReadRel32Immd26(
-    offset_t lower,
-    offset_t upper) {
-  return std::make_unique<
-      Rel32ReaderArm<AArch64Rel32Translator::AddrTraits_Immd26>>(
-      translator_, this->image_,
-      rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD26], lower,
-      upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch64::MakeWriteRel32Immd26(
-    MutableBufferView image) {
-  return std::make_unique<
-      Rel32WriterArm<AArch64Rel32Translator::AddrTraits_Immd26>>(translator_,
-                                                                 image);
-}
-
 // Explicit instantiation for supported classes.
 template class DisassemblerElfArm<ElfAArch32Traits>;
 template class DisassemblerElfArm<ElfAArch64Traits>;
diff --git a/disassembler_elf.h b/disassembler_elf.h
index 0bd11a6..353c444 100644
--- a/disassembler_elf.h
+++ b/disassembler_elf.h
@@ -63,6 +63,7 @@
 };
 
 struct Elf32Traits {
+  static constexpr uint16_t kVersion = 1;
   static constexpr Bitness kBitness = kBit32;
   static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS32;
   using Elf_Shdr = elf::Elf32_Shdr;
@@ -94,6 +95,7 @@
 };
 
 struct Elf64Traits {
+  static constexpr uint16_t kVersion = 1;
   static constexpr Bitness kBitness = kBit64;
   static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS64;
   using Elf_Shdr = elf::Elf64_Shdr;
@@ -151,6 +153,7 @@
 class DisassemblerElf : public Disassembler {
  public:
   using Traits = TRAITS;
+  static constexpr uint16_t kVersion = Traits::kVersion;
   // Applies quick checks to determine whether |image| *may* point to the start
   // of an executable. Returns true iff the check passes.
   static bool QuickDetect(ConstBufferView image);
@@ -296,6 +299,13 @@
   std::unique_ptr<ReferenceReader> MakeReadAbs32(offset_t lo, offset_t hi);
   std::unique_ptr<ReferenceWriter> MakeWriteAbs32(MutableBufferView image);
 
+  // Specialized Read/Write functions for different rel32 address types.
+  template <class ADDR_TRAITS>
+  std::unique_ptr<ReferenceReader> MakeReadRel32(offset_t lower,
+                                                 offset_t upper);
+  template <class ADDR_TRAITS>
+  std::unique_ptr<ReferenceWriter> MakeWriteRel32(MutableBufferView image);
+
  protected:
   // Sorted file offsets of rel32 locations for each rel32 address type.
   std::deque<offset_t>
@@ -322,27 +332,6 @@
   // or THUMB2 mode, this function implements heuristics to distinguish between
   // the two. Returns true if section is THUMB2 mode; otherwise return false.
   bool IsExecSectionThumb2(const typename Traits::Elf_Shdr& section) const;
-
-  // Specialized Read/Write functions for different rel32 address types.
-  std::unique_ptr<ReferenceReader> MakeReadRel32A24(offset_t lower,
-                                                    offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32A24(MutableBufferView image);
-
-  std::unique_ptr<ReferenceReader> MakeReadRel32T8(offset_t lower,
-                                                   offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32T8(MutableBufferView image);
-
-  std::unique_ptr<ReferenceReader> MakeReadRel32T11(offset_t lower,
-                                                    offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32T11(MutableBufferView image);
-
-  std::unique_ptr<ReferenceReader> MakeReadRel32T20(offset_t lower,
-                                                    offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32T20(MutableBufferView image);
-
-  std::unique_ptr<ReferenceReader> MakeReadRel32T24(offset_t lower,
-                                                    offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32T24(MutableBufferView image);
 };
 
 // Disassembler for ELF with AArch64 (AKA ARM64).
@@ -360,22 +349,6 @@
   // DisassemblerElfArm:
   std::unique_ptr<typename Traits::Rel32FinderUse> MakeRel32Finder(
       const typename Traits::Elf_Shdr& section) override;
-
-  // Specialized Read/Write functions for different rel32 address types.
-  std::unique_ptr<ReferenceReader> MakeReadRel32Immd14(offset_t lower,
-                                                       offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32Immd14(
-      MutableBufferView image);
-
-  std::unique_ptr<ReferenceReader> MakeReadRel32Immd19(offset_t lower,
-                                                       offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32Immd19(
-      MutableBufferView image);
-
-  std::unique_ptr<ReferenceReader> MakeReadRel32Immd26(offset_t lower,
-                                                       offset_t upper);
-  std::unique_ptr<ReferenceWriter> MakeWriteRel32Immd26(
-      MutableBufferView image);
 };
 
 }  // namespace zucchini
diff --git a/disassembler_elf_unittest.cc b/disassembler_elf_unittest.cc
index d98eb50..1e52136 100644
--- a/disassembler_elf_unittest.cc
+++ b/disassembler_elf_unittest.cc
@@ -153,8 +153,7 @@
     header.e_machine = elf::EM_386;
     header.e_version = 1;
     header.e_shentsize = sizeof(elf::Elf32_Shdr);
-    ConstBufferView image(reinterpret_cast<const uint8_t*>(&header),
-                          sizeof(header));
+    image = {reinterpret_cast<const uint8_t*>(&header), sizeof(header)};
     EXPECT_TRUE(DisassemblerElfX86::QuickDetect(image));
     EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image));
   }
@@ -169,8 +168,7 @@
     header.e_machine = elf::EM_X86_64;
     header.e_version = 1;
     header.e_shentsize = sizeof(elf::Elf64_Shdr);
-    ConstBufferView image(reinterpret_cast<const uint8_t*>(&header),
-                          sizeof(header));
+    image = {reinterpret_cast<const uint8_t*>(&header), sizeof(header)};
     EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image));
     EXPECT_TRUE(DisassemblerElfX64::QuickDetect(image));
   }
diff --git a/disassembler_no_op.h b/disassembler_no_op.h
index ef10651..1d436dd 100644
--- a/disassembler_no_op.h
+++ b/disassembler_no_op.h
@@ -18,6 +18,8 @@
 // This disassembler works on any file and does not look for reference.
 class DisassemblerNoOp : public Disassembler {
  public:
+  static constexpr uint16_t kVersion = 1;
+
   DisassemblerNoOp();
   DisassemblerNoOp(const DisassemblerNoOp&) = delete;
   const DisassemblerNoOp& operator=(const DisassemblerNoOp&) = delete;
diff --git a/disassembler_win32.h b/disassembler_win32.h
index 77b65ac..dfb2533 100644
--- a/disassembler_win32.h
+++ b/disassembler_win32.h
@@ -26,6 +26,7 @@
 class Rel32FinderX64;
 
 struct Win32X86Traits {
+  static constexpr uint16_t kVersion = 1;
   static constexpr Bitness kBitness = kBit32;
   static constexpr ExecutableType kExeType = kExeTypeWin32X86;
   enum : uint16_t { kMagic = 0x10B };
@@ -39,6 +40,7 @@
 };
 
 struct Win32X64Traits {
+  static constexpr uint16_t kVersion = 1;
   static constexpr Bitness kBitness = kBit64;
   static constexpr ExecutableType kExeType = kExeTypeWin32X64;
   enum : uint16_t { kMagic = 0x20B };
@@ -55,6 +57,7 @@
 class DisassemblerWin32 : public Disassembler {
  public:
   using Traits = TRAITS;
+  static constexpr uint16_t kVersion = Traits::kVersion;
   enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount };
 
   // Applies quick checks to determine whether |image| *may* point to the start
diff --git a/disassembler_ztf.h b/disassembler_ztf.h
index 0e73c2a..9b4a94b 100644
--- a/disassembler_ztf.h
+++ b/disassembler_ztf.h
@@ -123,6 +123,8 @@
 // Disassembler for Zucchini Text Format (ZTF).
 class DisassemblerZtf : public Disassembler {
  public:
+  static constexpr uint16_t kVersion = 1;
+
   // Target Pools
   enum ReferencePool : uint8_t {
     kAngles,      // <>
diff --git a/element_detection.cc b/element_detection.cc
index 356c0d7..5548610 100644
--- a/element_detection.cc
+++ b/element_detection.cc
@@ -9,6 +9,7 @@
 #include "components/zucchini/buildflags.h"
 #include "components/zucchini/disassembler.h"
 #include "components/zucchini/disassembler_no_op.h"
+#include "components/zucchini/patch_utils.h"
 
 #if BUILDFLAG(ENABLE_DEX)
 #include "components/zucchini/disassembler_dex.h"
@@ -134,6 +135,40 @@
   }
 }
 
+uint16_t DisassemblerVersionOfType(ExecutableType exe_type) {
+  switch (exe_type) {
+#if BUILDFLAG(ENABLE_WIN)
+    case kExeTypeWin32X86:
+      return DisassemblerWin32X86::kVersion;
+    case kExeTypeWin32X64:
+      return DisassemblerWin32X64::kVersion;
+#endif  // BUILDFLAG(ENABLE_WIN)
+#if BUILDFLAG(ENABLE_ELF)
+    case kExeTypeElfX86:
+      return DisassemblerElfX86::kVersion;
+    case kExeTypeElfX64:
+      return DisassemblerElfX64::kVersion;
+    case kExeTypeElfAArch32:
+      return DisassemblerElfAArch32::kVersion;
+    case kExeTypeElfAArch64:
+      return DisassemblerElfAArch64::kVersion;
+#endif  // BUILDFLAG(ENABLE_ELF)
+#if BUILDFLAG(ENABLE_DEX)
+    case kExeTypeDex:
+      return DisassemblerDex::kVersion;
+#endif  // BUILDFLAG(ENABLE_DEX)
+#if BUILDFLAG(ENABLE_ZTF)
+    case kExeTypeZtf:
+      return DisassemblerZtf::kVersion;
+#endif  // BUILDFLAG(ENABLE_ZTF)
+    case kExeTypeNoOp:
+      return DisassemblerNoOp::kVersion;
+    default:
+      // If an architecture is disabled then null is handled gracefully.
+      return kInvalidVersion;
+  }
+}
+
 absl::optional<Element> DetectElementFromDisassembler(ConstBufferView image) {
   std::unique_ptr<Disassembler> disasm = MakeDisassemblerWithoutFallback(image);
   if (disasm)
diff --git a/element_detection.h b/element_detection.h
index 856ec27..febedc5 100644
--- a/element_detection.h
+++ b/element_detection.h
@@ -28,6 +28,9 @@
 std::unique_ptr<Disassembler> MakeDisassemblerOfType(ConstBufferView image,
                                                      ExecutableType exe_type);
 
+// Returns the version associated with disassembler of type |exe_type|.
+uint16_t DisassemblerVersionOfType(ExecutableType exe_type);
+
 // Attempts to detect an element associated with |image| and returns it, or
 // returns nullopt if no element is detected.
 using ElementDetector =
diff --git a/equivalence_map.cc b/equivalence_map.cc
index 26c0764..b4d5e27 100644
--- a/equivalence_map.cc
+++ b/equivalence_map.cc
@@ -214,7 +214,7 @@
 
 /******** OffsetMapper ********/
 
-OffsetMapper::OffsetMapper(std::vector<Equivalence>&& equivalences,
+OffsetMapper::OffsetMapper(std::deque<Equivalence>&& equivalences,
                            offset_t old_image_size,
                            offset_t new_image_size)
     : equivalences_(std::move(equivalences)),
@@ -291,7 +291,7 @@
                                                 : kOffsetBound - 1;
 }
 
-void OffsetMapper::ForwardProjectAll(std::vector<offset_t>* offsets) const {
+void OffsetMapper::ForwardProjectAll(std::deque<offset_t>* offsets) const {
   DCHECK(std::is_sorted(offsets->begin(), offsets->end()));
   auto current = equivalences_.begin();
   for (auto& src : *offsets) {
@@ -310,7 +310,7 @@
 }
 
 void OffsetMapper::PruneEquivalencesAndSortBySource(
-    std::vector<Equivalence>* equivalences) {
+    std::deque<Equivalence>* equivalences) {
   std::sort(equivalences->begin(), equivalences->end(),
             [](const Equivalence& a, const Equivalence& b) {
               return a.src_offset < b.src_offset;
@@ -368,6 +368,7 @@
   base::EraseIf(*equivalences, [](const Equivalence& equivalence) {
     return equivalence.length == 0;
   });
+  equivalences->shrink_to_fit();
 }
 
 /******** EquivalenceMap ********/
diff --git a/equivalence_map.h b/equivalence_map.h
index 8b716a1..af99ac4 100644
--- a/equivalence_map.h
+++ b/equivalence_map.h
@@ -7,6 +7,7 @@
 
 #include <stddef.h>
 
+#include <deque>
 #include <limits>
 #include <vector>
 
@@ -82,13 +83,13 @@
 // bytes in |old_image| and |new_image|) one-to-one.
 class OffsetMapper {
  public:
-  using const_iterator = std::vector<Equivalence>::const_iterator;
+  using const_iterator = std::deque<Equivalence>::const_iterator;
 
   // Constructors for various data sources. "Old" and "new" image sizes are
   // needed for bounds checks and to handle dangling targets.
   // - From a list of |equivalences|, already sorted (by |src_offset|) and
   //   pruned, useful for tests.
-  OffsetMapper(std::vector<Equivalence>&& equivalences,
+  OffsetMapper(std::deque<Equivalence>&& equivalences,
                offset_t old_image_size,
                offset_t new_image_size);
   // - From a generator, useful for Zucchini-apply.
@@ -128,10 +129,10 @@
   // Given sorted |offsets|, applies a projection in-place of all offsets that
   // are part of a pruned equivalence from |old_image| to |new_image|. Other
   // offsets are removed from |offsets|.
-  void ForwardProjectAll(std::vector<offset_t>* offsets) const;
+  void ForwardProjectAll(std::deque<offset_t>* offsets) const;
 
   // Accessor for testing.
-  const std::vector<Equivalence> equivalences() const { return equivalences_; }
+  const std::deque<Equivalence> equivalences() const { return equivalences_; }
 
   // Sorts |equivalences| by |src_offset| and removes all source overlaps; so a
   // source location that was covered by some Equivalence would become covered
@@ -140,12 +141,12 @@
   // of a tie, the Equivalence with minimal |src_offset|. |equivalences| may
   // change in size since empty Equivalences are removed.
   static void PruneEquivalencesAndSortBySource(
-      std::vector<Equivalence>* equivalences);
+      std::deque<Equivalence>* equivalences);
 
  private:
   // |equivalences_| is pruned, i.e., no "old" blocks overlap (and no "new"
   // block overlaps). Also, it is sorted by "old" offsets.
-  std::vector<Equivalence> equivalences_;
+  std::deque<Equivalence> equivalences_;
   const offset_t old_image_size_;
   const offset_t new_image_size_;
 };
diff --git a/equivalence_map_unittest.cc b/equivalence_map_unittest.cc
index b3a4ea4..6b826d1 100644
--- a/equivalence_map_unittest.cc
+++ b/equivalence_map_unittest.cc
@@ -22,7 +22,7 @@
 
 namespace {
 
-using OffsetVector = std::vector<offset_t>;
+using OffsetDeque = std::deque<offset_t>;
 
 // Make all references 2 bytes long.
 constexpr offset_t kReferenceSize = 2;
@@ -252,28 +252,28 @@
 
 TEST(EquivalenceMapTest, PruneEquivalencesAndSortBySource) {
   auto PruneEquivalencesAndSortBySourceTest =
-      [](std::vector<Equivalence>&& equivalences) {
+      [](std::deque<Equivalence>&& equivalences) {
         OffsetMapper::PruneEquivalencesAndSortBySource(&equivalences);
         return std::move(equivalences);
       };
 
-  EXPECT_EQ(std::vector<Equivalence>(),
+  EXPECT_EQ(std::deque<Equivalence>(),
             PruneEquivalencesAndSortBySourceTest({}));
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}}),
             PruneEquivalencesAndSortBySourceTest({{0, 10, 1}}));
-  EXPECT_EQ(std::vector<Equivalence>(),
+  EXPECT_EQ(std::deque<Equivalence>(),
             PruneEquivalencesAndSortBySourceTest({{0, 10, 0}}));
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}, {1, 11, 1}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}, {1, 11, 1}}),
             PruneEquivalencesAndSortBySourceTest({{0, 10, 1}, {1, 11, 1}}));
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}, {2, 13, 1}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}, {2, 13, 1}}),
             PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 2}}));
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}}),
             PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 1}}));
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}, {2, 14, 1}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}, {2, 14, 1}}),
             PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 13, 2}}));
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}, {1, 12, 3}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}, {1, 12, 3}}),
             PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 3}}));
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, 3}, {3, 16, 2}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, 3}, {3, 16, 2}}),
             PruneEquivalencesAndSortBySourceTest(
                 {{0, 10, 3}, {1, 13, 3}, {3, 16, 2}}));  // Pruning is greedy
 
@@ -288,9 +288,9 @@
   //                 ***************
   // This test case makes sure the function does not stall on a large instance
   // of this pattern.
-  EXPECT_EQ(std::vector<Equivalence>({{0, 10, +300000}, {300000, 30, +300000}}),
+  EXPECT_EQ(std::deque<Equivalence>({{0, 10, +300000}, {300000, 30, +300000}}),
             PruneEquivalencesAndSortBySourceTest([] {
-              std::vector<Equivalence> equivalenses;
+              std::deque<Equivalence> equivalenses;
               equivalenses.push_back({0, 10, +300000});
               for (offset_t i = 0; i < 100000; ++i)
                 equivalenses.push_back({200000 + i, 20, +200000 - 2 * i});
@@ -302,7 +302,7 @@
 TEST(EquivalenceMapTest, NaiveExtendedForwardProject) {
   constexpr size_t kOldImageSize = 1000U;
   constexpr size_t kNewImageSize = 1000U;
-  OffsetMapper offset_mapper(std::vector<Equivalence>(), kOldImageSize,
+  OffsetMapper offset_mapper(std::deque<Equivalence>(), kOldImageSize,
                              kNewImageSize);
 
   // Convenience function to declutter.
@@ -417,7 +417,7 @@
   // - '.' are "new" offsets that appear as output.
   // - '(' and ')' surround a single "new" location that are repeated as output.
   int case_no = 0;
-  auto run_test = [&case_no](std::vector<Equivalence>&& equivalences,
+  auto run_test = [&case_no](std::deque<Equivalence>&& equivalences,
                              const std::string& old_spec,
                              const std::string& new_spec) {
     const size_t old_size = old_spec.length();
@@ -504,7 +504,7 @@
 TEST(EquivalenceMapTest, ForwardProjectAll) {
   auto ForwardProjectAllTest = [](const OffsetMapper& offset_mapper,
                                   std::initializer_list<offset_t> offsets) {
-    OffsetVector offsets_vec(offsets);
+    std::deque<offset_t> offsets_vec(offsets);
     offset_mapper.ForwardProjectAll(&offsets_vec);
     return offsets_vec;
   };
@@ -512,29 +512,29 @@
   // [0,2) --> [10,12), [2,3) --> [13,14), [4,6) --> [16,18).
   OffsetMapper offset_mapper1({{0, 10, +2}, {2, 13, +1}, {4, 16, +2}}, 100U,
                               100U);
-  EXPECT_EQ(OffsetVector({10}), ForwardProjectAllTest(offset_mapper1, {0}));
-  EXPECT_EQ(OffsetVector({13}), ForwardProjectAllTest(offset_mapper1, {2}));
-  EXPECT_EQ(OffsetVector({}), ForwardProjectAllTest(offset_mapper1, {3}));
-  EXPECT_EQ(OffsetVector({10, 13}),
+  EXPECT_EQ(OffsetDeque({10}), ForwardProjectAllTest(offset_mapper1, {0}));
+  EXPECT_EQ(OffsetDeque({13}), ForwardProjectAllTest(offset_mapper1, {2}));
+  EXPECT_EQ(OffsetDeque({}), ForwardProjectAllTest(offset_mapper1, {3}));
+  EXPECT_EQ(OffsetDeque({10, 13}),
             ForwardProjectAllTest(offset_mapper1, {0, 2}));
-  EXPECT_EQ(OffsetVector({11, 13, 17}),
+  EXPECT_EQ(OffsetDeque({11, 13, 17}),
             ForwardProjectAllTest(offset_mapper1, {1, 2, 5}));
-  EXPECT_EQ(OffsetVector({11, 17}),
+  EXPECT_EQ(OffsetDeque({11, 17}),
             ForwardProjectAllTest(offset_mapper1, {1, 3, 5}));
-  EXPECT_EQ(OffsetVector({10, 11, 13, 16, 17}),
+  EXPECT_EQ(OffsetDeque({10, 11, 13, 16, 17}),
             ForwardProjectAllTest(offset_mapper1, {0, 1, 2, 3, 4, 5, 6}));
 
   // [0,2) --> [10,12), [13,14) --> [2,3), [16,18) --> [4,6).
   OffsetMapper offset_mapper2({{0, 10, +2}, {13, 2, +1}, {16, 4, +2}}, 100U,
                               100U);
-  EXPECT_EQ(OffsetVector({2}), ForwardProjectAllTest(offset_mapper2, {13}));
-  EXPECT_EQ(OffsetVector({10, 2}),
+  EXPECT_EQ(OffsetDeque({2}), ForwardProjectAllTest(offset_mapper2, {13}));
+  EXPECT_EQ(OffsetDeque({10, 2}),
             ForwardProjectAllTest(offset_mapper2, {0, 13}));
-  EXPECT_EQ(OffsetVector({11, 2, 5}),
+  EXPECT_EQ(OffsetDeque({11, 2, 5}),
             ForwardProjectAllTest(offset_mapper2, {1, 13, 17}));
-  EXPECT_EQ(OffsetVector({11, 5}),
+  EXPECT_EQ(OffsetDeque({11, 5}),
             ForwardProjectAllTest(offset_mapper2, {1, 14, 17}));
-  EXPECT_EQ(OffsetVector({10, 11, 2, 4, 5}),
+  EXPECT_EQ(OffsetDeque({10, 11, 2, 4, 5}),
             ForwardProjectAllTest(offset_mapper2, {0, 1, 13, 14, 16, 17, 18}));
 }
 
diff --git a/fuzzers/testdata/patch_fuzzer/empty.zuc b/fuzzers/testdata/patch_fuzzer/empty.zuc
index 64eacf5..1bda1e9 100644
--- a/fuzzers/testdata/patch_fuzzer/empty.zuc
+++ b/fuzzers/testdata/patch_fuzzer/empty.zuc
Binary files differ
diff --git a/main_utils.cc b/main_utils.cc
index 8c47c91..b499817 100644
--- a/main_utils.cc
+++ b/main_utils.cc
@@ -22,6 +22,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "components/zucchini/io_utils.h"
+#include "components/zucchini/patch_utils.h"
 #include "components/zucchini/zucchini_commands.h"
 
 #if defined(OS_WIN)
@@ -69,6 +70,7 @@
      3, &MainGen},
     {"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3,
      &MainApply},
+    {"verify", "-verify <patch_file>", 1, &MainVerify},
     {"read", "-read <exe> [-dump]", 1, &MainRead},
     {"detect", "-detect <archive_file>", 1, &MainDetect},
     {"match", "-match <old_file> <new_file> [-impose=#+#=#+#,#+#=#+#,...]", 2,
@@ -206,6 +208,8 @@
 
 // Prints main Zucchini usage text.
 void PrintUsage(std::ostream& err) {
+  err << "Version: " << zucchini::kMajorVersion << "."
+      << zucchini::kMinorVersion << std::endl;
   err << "Usage:" << std::endl;
   for (const Command& command : kCommands)
     err << "  zucchini " << command.usage << std::endl;
diff --git a/patch_read_write_unittest.cc b/patch_read_write_unittest.cc
index 627513c..25e1fb0 100644
--- a/patch_read_write_unittest.cc
+++ b/patch_read_write_unittest.cc
@@ -63,6 +63,7 @@
       0x03, 0, 0, 0,       // new_offset
       0x13, 0, 0, 0,       // new_length
       'P', 'x', '8', '6',  // exe_type = EXE_TYPE_WIN32_X86
+      0x01, 0x00,          // element version
       // EquivalenceSource
       1, 0, 0, 0,  // src_skip size
       0x10,        // src_skip content
@@ -95,11 +96,12 @@
 ByteVector CreateElementMatch() {
   return {
       // PatchElementHeader
-      0x01, 0,   0,   0,    // old_offset
-      0x02, 0,   0,   0,    // old_length
-      0x03, 0,   0,   0,    // new_offset
-      0x04, 0,   0,   0,    // new_length
-      'D',  'E', 'X', ' ',  // exe_type = kExeTypeDex
+      0x01, 0,    0,   0,    // old_offset
+      0x02, 0,    0,   0,    // old_length
+      0x03, 0,    0,   0,    // new_offset
+      0x04, 0,    0,   0,    // new_length
+      'D',  'E',  'X', ' ',  // exe_type = kExeTypeDex
+      0x01, 0x00,            // element version
   };
 }
 
@@ -586,10 +588,26 @@
   }
 }
 
+TEST(PatchElementTest, WrongVersion) {
+  // Bump element version to 2.
+  {
+    ByteVector data = CreatePatchElement();
+    ModifyByte(offsetof(PatchElementHeader, version), 0x01, 0x02, &data);
+    TestInvalidInitialize<PatchElementReader>(&data);
+  }
+  // Bump element version to 0.
+  {
+    ByteVector data = CreatePatchElement();
+    ModifyByte(offsetof(PatchElementHeader, version), 0x01, 0x00, &data);
+    TestInvalidInitialize<PatchElementReader>(&data);
+  }
+}
+
 TEST(EnsemblePatchTest, RawPatch) {
   ByteVector data = {
       // PatchHeader
-      0x5A, 0x75, 0x63, 0x00,  // magic
+      0x5A, 0x75, 0x63, 0x63,  // magic
+      0x01, 0x00, 0x00, 0x00,  // major/minor version
       0x10, 0x32, 0x54, 0x76,  // old_size
       0x00, 0x11, 0x22, 0x33,  // old_crc
       0x01, 0, 0, 0,           // new_size
@@ -602,7 +620,8 @@
       0x02, 0, 0, 0,       // old_length
       0x00, 0, 0, 0,       // new_offset
       0x01, 0, 0, 0,       // new_length
-      'P', 'x', '8', '6',  // exe_type = EXE_TYPE_WIN32_X86
+      'P', 'x', '8', '6',  // exe_type = EXE_TYPE_WIN32_X8
+      0x01, 0x00,          // element version
       // EquivalenceSource
       0, 0, 0, 0,  // src_skip size
       0, 0, 0, 0,  // dst_skip size
@@ -624,6 +643,8 @@
 
   PatchHeader header = ensemble_patch_reader.header();
   EXPECT_EQ(PatchHeader::kMagic, header.magic);
+  EXPECT_EQ(kMajorVersion, header.major_version);
+  EXPECT_EQ(kMinorVersion, header.minor_version);
   EXPECT_EQ(0x76543210U, header.old_size);
   EXPECT_EQ(0x33221100U, header.old_crc);
   EXPECT_EQ(0x01U, header.new_size);
@@ -647,7 +668,8 @@
 TEST(EnsemblePatchTest, CheckFile) {
   ByteVector data = {
       // PatchHeader
-      0x5A, 0x75, 0x63, 0x00,  // magic
+      0x5A, 0x75, 0x63, 0x63,  // magic
+      0x01, 0x00, 0x00, 0x00,  // major/minor version
       0x05, 0x00, 0x00, 0x00,  // old_size
       0xDF, 0x13, 0xE4, 0x10,  // old_crc
       0x03, 0x00, 0x00, 0x00,  // new_size
@@ -661,6 +683,7 @@
       0x00, 0, 0, 0,       // new_offset
       0x03, 0, 0, 0,       // new_length
       'P', 'x', '8', '6',  // exe_type = EXE_TYPE_WIN32_X86
+      0x01, 0x00,          // element version
       // EquivalenceSource
       0, 0, 0, 0,  // src_skip size
       0, 0, 0, 0,  // dst_skip size
@@ -695,7 +718,8 @@
 TEST(EnsemblePatchTest, InvalidMagic) {
   ByteVector data = {
       // PatchHeader
-      0x42, 0x42, 0x42, 0x00,  // magic
+      0x42, 0x42, 0x42, 0x42,  // magic
+      0x01, 0x00, 0x00, 0x00,  // major/minor version
       0x10, 0x32, 0x54, 0x76,  // old_size
       0x00, 0x11, 0x22, 0x33,  // old_crc
       0x03, 0x00, 0x00, 0x00,  // new_size
@@ -709,6 +733,44 @@
       0x00, 0, 0, 0,       // new_offset
       0x03, 0, 0, 0,       // new_length
       'P', 'x', '8', '6',  // exe_type = EXE_TYPE_WIN32_X86
+      0x01, 0x00,          // element version
+      // EquivalenceSource
+      0, 0, 0, 0,  // src_skip size
+      0, 0, 0, 0,  // dst_skip size
+      0, 0, 0, 0,  // copy_count size
+      // ExtraDataSource
+      0, 0, 0, 0,  // extra_data size
+      // RawDeltaSource
+      0, 0, 0, 0,  // raw_delta_skip size
+      0, 0, 0, 0,  // raw_delta_diff size
+      // ReferenceDeltaSource
+      0, 0, 0, 0,  // reference_delta size
+      // PatchElementReader
+      0, 0, 0, 0,  // pool count
+  };
+
+  TestInvalidInitialize<EnsemblePatchReader>(&data);
+}
+
+TEST(EnsemblePatchTest, InvalidVersion) {
+  ByteVector data = {
+      // PatchHeader
+      0x5A, 0x75, 0x63, 0x63,  // magic
+      0x02, 0x01, 0x00, 0x00,  // major/minor version
+      0x10, 0x32, 0x54, 0x76,  // old_size
+      0x00, 0x11, 0x22, 0x33,  // old_crc
+      0x03, 0x00, 0x00, 0x00,  // new_size
+      0x44, 0x55, 0x66, 0x77,  // new_crc
+
+      1, 0, 0, 0,  // number of element
+
+      // PatchElementHeader
+      0x01, 0, 0, 0,       // old_offset
+      0x02, 0, 0, 0,       // old_length
+      0x00, 0, 0, 0,       // new_offset
+      0x03, 0, 0, 0,       // new_length
+      'P', 'x', '8', '6',  // exe_type = EXE_TYPE_WIN32_X86
+      0x01, 0x00,          // element version
       // EquivalenceSource
       0, 0, 0, 0,  // src_skip size
       0, 0, 0, 0,  // dst_skip size
diff --git a/patch_reader.cc b/patch_reader.cc
index 99951da..8fd9b57 100644
--- a/patch_reader.cc
+++ b/patch_reader.cc
@@ -10,6 +10,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "components/zucchini/algorithm.h"
 #include "components/zucchini/crc32.h"
+#include "components/zucchini/element_detection.h"
 
 namespace zucchini {
 
@@ -27,6 +28,12 @@
     LOG(ERROR) << "Invalid ExecutableType found.";
     return false;
   }
+  uint16_t element_version = DisassemblerVersionOfType(exe_type);
+  if (element_version != unsafe_element_header.version) {
+    LOG(ERROR) << "Element version doesn't match. Expected: " << element_version
+               << ", Actual:" << unsafe_element_header.version;
+    return false;
+  }
   if (!unsafe_element_header.old_length || !unsafe_element_header.new_length) {
     LOG(ERROR) << "Empty patch element found.";
     return false;
@@ -334,6 +341,11 @@
     LOG(ERROR) << "Patch contains invalid magic.";
     return false;
   }
+  if (header_.major_version != kMajorVersion) {
+    LOG(ERROR) << "Patch major version doesn't match. Expected: "
+               << kMajorVersion << ", Actual:" << header_.major_version;
+    return false;
+  }
   // |header_| is assumed to be safe from this point forward.
 
   uint32_t element_count = 0;
diff --git a/patch_utils.h b/patch_utils.h
index 5f49195..822fedc 100644
--- a/patch_utils.h
+++ b/patch_utils.h
@@ -14,6 +14,17 @@
 
 namespace zucchini {
 
+// A change in major version indicates breaking changes such that a patch
+// definitely cannot be applied by a zucchini binary whose major version doesn't
+// match.
+enum : uint16_t { kMajorVersion = 1 };
+// A change in minor version indicates possibly breaking changes at the element
+// level, such that it may not be possible to apply a patch whose minor version
+// doesn't match this version. To determine if a given patch may be applied with
+// this version, VerifyPatch() should be called.
+enum : uint16_t { kMinorVersion = 0 };
+enum : uint16_t { kInvalidVersion = 0xffff };
+
 // A Zucchini 'ensemble' patch is the concatenation of a patch header with a
 // list of patch 'elements', each containing data for patching individual
 // elements.
@@ -24,9 +35,11 @@
 // Header for a Zucchini patch, found at the beginning of an ensemble patch.
 struct PatchHeader {
   // Magic signature at the beginning of a Zucchini patch file.
-  enum : uint32_t { kMagic = 'Z' | ('u' << 8) | ('c' << 16) };
+  enum : uint32_t { kMagic = 'Z' | ('u' << 8) | ('c' << 16) | ('c' << 24) };
 
   uint32_t magic = 0;
+  uint16_t major_version = kInvalidVersion;
+  uint16_t minor_version = kInvalidVersion;
   uint32_t old_size = 0;
   uint32_t old_crc = 0;
   uint32_t new_size = 0;
@@ -34,7 +47,7 @@
 };
 
 // Sanity check.
-static_assert(sizeof(PatchHeader) == 20, "PatchHeader must be 20 bytes");
+static_assert(sizeof(PatchHeader) == 24, "PatchHeader must be 24 bytes");
 
 // Header for a patch element, found at the beginning of every patch element.
 struct PatchElementHeader {
@@ -43,11 +56,12 @@
   uint32_t new_offset;
   uint32_t new_length;
   uint32_t exe_type;  // ExecutableType.
+  uint16_t version = kInvalidVersion;
 };
 
 // Sanity check.
-static_assert(sizeof(PatchElementHeader) == 20,
-              "PatchElementHeader must be 20 bytes");
+static_assert(sizeof(PatchElementHeader) == 22,
+              "PatchElementHeader must be 22 bytes");
 
 #pragma pack(pop)
 
diff --git a/patch_writer.cc b/patch_writer.cc
index 1206208..04f3244 100644
--- a/patch_writer.cc
+++ b/patch_writer.cc
@@ -10,6 +10,7 @@
 #include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
 #include "components/zucchini/crc32.h"
+#include "components/zucchini/element_detection.h"
 
 namespace zucchini {
 
@@ -30,6 +31,7 @@
   element_header.new_length =
       base::checked_cast<uint32_t>(element_match.new_element.size);
   element_header.exe_type = element_match.exe_type();
+  element_header.version = DisassemblerVersionOfType(element_match.exe_type());
 
   return sink->PutValue<PatchElementHeader>(element_header);
 }
@@ -248,11 +250,15 @@
 EnsemblePatchWriter::EnsemblePatchWriter(const PatchHeader& header)
     : header_(header) {
   DCHECK_EQ(header_.magic, PatchHeader::kMagic);
+  DCHECK_EQ(header_.major_version, kMajorVersion);
+  DCHECK_EQ(header_.minor_version, kMinorVersion);
 }
 
 EnsemblePatchWriter::EnsemblePatchWriter(ConstBufferView old_image,
                                          ConstBufferView new_image) {
   header_.magic = PatchHeader::kMagic;
+  header_.major_version = kMajorVersion;
+  header_.minor_version = kMinorVersion;
   header_.old_size = base::checked_cast<uint32_t>(old_image.size());
   header_.old_crc = CalculateCrc32(old_image.begin(), old_image.end());
   header_.new_size = base::checked_cast<uint32_t>(new_image.size());
diff --git a/target_pool.cc b/target_pool.cc
index 23551fd..e15d0b9 100644
--- a/target_pool.cc
+++ b/target_pool.cc
@@ -16,7 +16,7 @@
 
 TargetPool::TargetPool() = default;
 
-TargetPool::TargetPool(std::vector<offset_t>&& targets) {
+TargetPool::TargetPool(std::deque<offset_t>&& targets) {
   DCHECK(targets_.empty());
   DCHECK(std::is_sorted(targets.begin(), targets.end()));
   targets_ = std::move(targets);
diff --git a/target_pool.h b/target_pool.h
index 27884d6..fb462b2 100644
--- a/target_pool.h
+++ b/target_pool.h
@@ -7,6 +7,7 @@
 
 #include <stddef.h>
 
+#include <deque>
 #include <vector>
 
 #include "components/zucchini/image_utils.h"
@@ -21,11 +22,11 @@
 // with a list of associated reference types, only used during patch generation.
 class TargetPool {
  public:
-  using const_iterator = std::vector<offset_t>::const_iterator;
+  using const_iterator = std::deque<offset_t>::const_iterator;
 
   TargetPool();
   // Initializes the object with given sorted and unique |targets|.
-  explicit TargetPool(std::vector<offset_t>&& targets);
+  explicit TargetPool(std::deque<offset_t>&& targets);
   TargetPool(TargetPool&&);
   TargetPool(const TargetPool&);
   ~TargetPool();
@@ -62,7 +63,7 @@
   void FilterAndProject(const OffsetMapper& offset_mapper);
 
   // Accessors for testing.
-  const std::vector<offset_t>& targets() const { return targets_; }
+  const std::deque<offset_t>& targets() const { return targets_; }
   const std::vector<TypeTag>& types() const { return types_; }
 
   // Returns the number of targets.
@@ -72,7 +73,7 @@
 
  private:
   std::vector<TypeTag> types_;     // Enumerates type_tag for this pool.
-  std::vector<offset_t> targets_;  // Targets for pool in ascending order.
+  std::deque<offset_t> targets_;   // Targets for pool in ascending order.
 };
 
 }  // namespace zucchini
diff --git a/target_pool_unittest.cc b/target_pool_unittest.cc
index 4c3efec..9a779b0 100644
--- a/target_pool_unittest.cc
+++ b/target_pool_unittest.cc
@@ -5,9 +5,9 @@
 #include "components/zucchini/target_pool.h"
 
 #include <cmath>
+#include <deque>
 #include <string>
 #include <utility>
-#include <vector>
 
 #include "components/zucchini/image_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -16,29 +16,29 @@
 
 namespace {
 
-using OffsetVector = std::vector<offset_t>;
+using OffsetDeque = std::deque<offset_t>;
 
 }  // namespace
 
 TEST(TargetPoolTest, InsertTargetsFromReferences) {
-  auto test_insert = [](std::vector<Reference>&& references) -> OffsetVector {
+  auto test_insert = [](std::vector<Reference>&& references) -> OffsetDeque {
     TargetPool target_pool;
     target_pool.InsertTargets(references);
     // Return copy since |target_pool| goes out of scope.
     return target_pool.targets();
   };
 
-  EXPECT_EQ(OffsetVector(), test_insert({}));
-  EXPECT_EQ(OffsetVector({0, 1}), test_insert({{0, 0}, {10, 1}}));
-  EXPECT_EQ(OffsetVector({0, 1}), test_insert({{0, 1}, {10, 0}}));
-  EXPECT_EQ(OffsetVector({0, 1, 2}), test_insert({{0, 1}, {10, 0}, {20, 2}}));
-  EXPECT_EQ(OffsetVector({0}), test_insert({{0, 0}, {10, 0}}));
-  EXPECT_EQ(OffsetVector({0, 1}), test_insert({{0, 0}, {10, 0}, {20, 1}}));
+  EXPECT_EQ(OffsetDeque(), test_insert({}));
+  EXPECT_EQ(OffsetDeque({0, 1}), test_insert({{0, 0}, {10, 1}}));
+  EXPECT_EQ(OffsetDeque({0, 1}), test_insert({{0, 1}, {10, 0}}));
+  EXPECT_EQ(OffsetDeque({0, 1, 2}), test_insert({{0, 1}, {10, 0}, {20, 2}}));
+  EXPECT_EQ(OffsetDeque({0}), test_insert({{0, 0}, {10, 0}}));
+  EXPECT_EQ(OffsetDeque({0, 1}), test_insert({{0, 0}, {10, 0}, {20, 1}}));
 }
 
 TEST(TargetPoolTest, KeyOffset) {
   auto test_key_offset = [](const std::string& nearest_offsets_key,
-                            OffsetVector&& targets) {
+                            OffsetDeque&& targets) {
     TargetPool target_pool(std::move(targets));
     for (offset_t offset : target_pool.targets()) {
       offset_t key = target_pool.KeyForOffset(offset);
diff --git a/targets_affinity.cc b/targets_affinity.cc
index d083787..b9a4877 100644
--- a/targets_affinity.cc
+++ b/targets_affinity.cc
@@ -21,8 +21,8 @@
 
 void TargetsAffinity::InferFromSimilarities(
     const EquivalenceMap& equivalences,
-    const std::vector<offset_t>& old_targets,
-    const std::vector<offset_t>& new_targets) {
+    const std::deque<offset_t>& old_targets,
+    const std::deque<offset_t>& new_targets) {
   forward_association_.assign(old_targets.size(), {});
   backward_association_.assign(new_targets.size(), {});
 
diff --git a/targets_affinity.h b/targets_affinity.h
index dff1741..163b015 100644
--- a/targets_affinity.h
+++ b/targets_affinity.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <deque>
 #include <vector>
 
 #include "components/zucchini/image_utils.h"
@@ -30,8 +31,8 @@
   // affinity scores. Both |old_targets| and |new_targets| are targets in the
   // same pool and are sorted in ascending order.
   void InferFromSimilarities(const EquivalenceMap& equivalence_map,
-                             const std::vector<offset_t>& old_targets,
-                             const std::vector<offset_t>& new_targets);
+                             const std::deque<offset_t>& old_targets,
+                             const std::deque<offset_t>& new_targets);
 
   // Assigns labels to targets based on associations previously inferred, using
   // |min_affinity| to reject associations with weak |affinity|. Label 0 is
diff --git a/targets_affinity_unittest.cc b/targets_affinity_unittest.cc
index 86182f9..abcbd3f 100644
--- a/targets_affinity_unittest.cc
+++ b/targets_affinity_unittest.cc
@@ -25,8 +25,8 @@
 
   auto test_affinity = [&targets_affinity](
                            const EquivalenceMap& equivalence_map,
-                           const std::vector<offset_t>& old_targets,
-                           const std::vector<offset_t>& new_targets) {
+                           const std::deque<offset_t>& old_targets,
+                           const std::deque<offset_t>& new_targets) {
     targets_affinity.InferFromSimilarities(equivalence_map, old_targets,
                                            new_targets);
     AffinityVector affinities(old_targets.size());
@@ -81,8 +81,8 @@
 
   auto test_labels_assignment =
       [&targets_affinity](const EquivalenceMap& equivalence_map,
-                          const std::vector<offset_t>& old_targets,
-                          const std::vector<offset_t>& new_targets,
+                          const std::deque<offset_t>& old_targets,
+                          const std::deque<offset_t>& new_targets,
                           double min_affinity,
                           const std::vector<uint32_t>& expected_old_labels,
                           const std::vector<uint32_t>& expected_new_labels) {
diff --git a/testdata/all.smali b/testdata/all.smali
new file mode 100644
index 0000000..7a1d272
--- /dev/null
+++ b/testdata/all.smali
@@ -0,0 +1,628 @@
+# Tests most/all DEX behaviors as of version 37.
+# Disassembled from dexdump test files.
+# Repo: https://android.googlesource.com/platform/art/
+# File: test/dexdump/all.dex
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble all.smali --api 25
+
+.class public LA;
+.super Ljava/lang/Object;
+
+
+# static fields
+.field private static sB:B
+
+.field private static sC:C
+
+.field private static sI:I
+
+.field private static sJ:J
+
+.field private static sO:LA;
+
+.field private static sS:S
+
+.field private static sZ:Z
+
+
+# instance fields
+.field private mB:B
+
+.field private mC:C
+
+.field private mI:I
+
+.field private mJ:J
+
+.field private mO:LA;
+
+.field private mS:S
+
+.field private mZ:Z
+
+
+# direct methods
+.method public constructor <init>()V
+    .registers 1
+
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+
+    return-void
+.end method
+
+.method public static arrays()V
+    .registers 3
+
+    aget v0, v1, v2
+
+    aget-wide v0, v1, v2
+
+    aget-object v0, v1, v2
+
+    aget-boolean v0, v1, v2
+
+    aget-byte v0, v1, v2
+
+    aget-char v0, v1, v2
+
+    aget-short v0, v1, v2
+
+    aput v0, v1, v2
+
+    aput-wide v0, v1, v2
+
+    aput-object v0, v1, v2
+
+    aput-boolean v0, v1, v2
+
+    aput-byte v0, v1, v2
+
+    aput-char v0, v1, v2
+
+    aput-short v0, v1, v2
+
+    return-void
+.end method
+
+.method public static binary_ops()V
+    .registers 3
+
+    add-int v0, v1, v2
+
+    sub-int v0, v1, v2
+
+    mul-int v0, v1, v2
+
+    div-int v0, v1, v2
+
+    rem-int v0, v1, v2
+
+    and-int v0, v1, v2
+
+    or-int v0, v1, v2
+
+    xor-int v0, v1, v2
+
+    shl-int v0, v1, v2
+
+    shr-int v0, v1, v2
+
+    ushr-int v0, v1, v2
+
+    add-long v0, v1, v2
+
+    sub-long v0, v1, v2
+
+    mul-long v0, v1, v2
+
+    div-long v0, v1, v2
+
+    rem-long v0, v1, v2
+
+    and-long v0, v1, v2
+
+    or-long v0, v1, v2
+
+    xor-long v0, v1, v2
+
+    shl-long v0, v1, v2
+
+    shr-long v0, v1, v2
+
+    ushr-long v0, v1, v2
+
+    add-float v0, v1, v2
+
+    sub-float v0, v1, v2
+
+    mul-float v0, v1, v2
+
+    div-float v0, v1, v2
+
+    rem-float v0, v1, v2
+
+    add-double v0, v1, v2
+
+    sub-double v0, v1, v2
+
+    mul-double v0, v1, v2
+
+    div-double v0, v1, v2
+
+    rem-double v0, v1, v2
+
+    return-void
+.end method
+
+.method public static binary_ops_2addr()V
+    .registers 2
+
+    add-int/2addr v0, v1
+
+    sub-int/2addr v0, v1
+
+    mul-int/2addr v0, v1
+
+    div-int/2addr v0, v1
+
+    rem-int/2addr v0, v1
+
+    and-int/2addr v0, v1
+
+    or-int/2addr v0, v1
+
+    xor-int/2addr v0, v1
+
+    shl-int/2addr v0, v1
+
+    shr-int/2addr v0, v1
+
+    ushr-int/2addr v0, v1
+
+    add-long/2addr v0, v1
+
+    sub-long/2addr v0, v1
+
+    mul-long/2addr v0, v1
+
+    div-long/2addr v0, v1
+
+    rem-long/2addr v0, v1
+
+    and-long/2addr v0, v1
+
+    or-long/2addr v0, v1
+
+    xor-long/2addr v0, v1
+
+    shl-long/2addr v0, v1
+
+    shr-long/2addr v0, v1
+
+    ushr-long/2addr v0, v1
+
+    add-float/2addr v0, v1
+
+    sub-float/2addr v0, v1
+
+    mul-float/2addr v0, v1
+
+    div-float/2addr v0, v1
+
+    rem-float/2addr v0, v1
+
+    add-double/2addr v0, v1
+
+    sub-double/2addr v0, v1
+
+    mul-double/2addr v0, v1
+
+    div-double/2addr v0, v1
+
+    rem-double/2addr v0, v1
+
+    return-void
+.end method
+
+.method public static binary_ops_lit16()V
+    .registers 2
+
+    add-int/lit16 v0, v1, 0x1234
+
+    rsub-int v0, v1, 0x1234
+
+    mul-int/lit16 v0, v1, 0x1234
+
+    div-int/lit16 v0, v1, 0x1234
+
+    rem-int/lit16 v0, v1, 0x1234
+
+    and-int/lit16 v0, v1, 0x1234
+
+    or-int/lit16 v0, v1, 0x1234
+
+    xor-int/lit16 v0, v1, 0x1234
+
+    return-void
+.end method
+
+.method public static binary_ops_lit8()V
+    .registers 2
+
+    add-int/lit8 v0, v1, 0x12
+
+    rsub-int/lit8 v0, v1, 0x12
+
+    mul-int/lit8 v0, v1, 0x12
+
+    div-int/lit8 v0, v1, 0x12
+
+    rem-int/lit8 v0, v1, 0x12
+
+    and-int/lit8 v0, v1, 0x12
+
+    or-int/lit8 v0, v1, 0x12
+
+    xor-int/lit8 v0, v1, 0x12
+
+    shl-int/lit8 v0, v1, 0x12
+
+    shr-int/lit8 v0, v1, 0x12
+
+    ushr-int/lit8 v0, v1, 0x12
+
+    return-void
+.end method
+
+.method public static compares()V
+    .registers 3
+
+    cmpl-float v0, v1, v2
+
+    cmpg-float v0, v1, v2
+
+    cmpl-double v0, v1, v2
+
+    cmpg-double v0, v1, v2
+
+    cmp-long v0, v1, v2
+
+    return-void
+.end method
+
+.method public static conditionals()V
+    .registers 2
+
+    if-eq v0, v1, :cond_18
+
+    if-ne v0, v1, :cond_18
+
+    if-lt v0, v1, :cond_18
+
+    if-ge v0, v1, :cond_18
+
+    if-gt v0, v1, :cond_18
+
+    if-le v0, v1, :cond_18
+
+    if-eqz v0, :cond_18
+
+    if-nez v0, :cond_18
+
+    if-ltz v0, :cond_18
+
+    if-gez v0, :cond_18
+
+    if-gtz v0, :cond_18
+
+    if-lez v0, :cond_18
+
+    :cond_18
+    return-void
+.end method
+
+.method public static constants()V
+    .registers 1
+
+    const/4 v0, 0x1
+
+    const/16 v0, 0x1234
+
+    const v0, 0x12345678
+
+    const/high16 v0, 0x12340000
+
+    const-wide/16 v0, 0x1234
+
+    const-wide/32 v0, 0x12345678
+
+    const-wide v0, 0x1234567890abcdefL    # 5.626349108908516E-221
+
+    const-wide/high16 v0, 0x1234000000000000L
+
+    const-string v0, "string"
+
+    const-string/jumbo v0, "string"
+
+    const-class v0, Ljava/lang/Object;
+
+    return-void
+.end method
+
+.method public static misc()V
+    .registers 5
+
+    nop
+
+    monitor-enter v0
+
+    monitor-exit v0
+
+    check-cast v0, Ljava/lang/Object;
+
+    instance-of v0, v1, Ljava/lang/Object;
+
+    array-length v0, v1
+
+    new-instance v0, Ljava/lang/Object;
+
+    new-array v0, v1, Ljava/lang/Object;
+
+    filled-new-array {v0, v1, v2, v3, v4}, [Ljava/lang/Object;
+
+    filled-new-array/range {v0 .. v4}, [Ljava/lang/Object;
+
+    fill-array-data v0, :array_1e
+
+    throw v0
+
+    goto :goto_1c
+
+    goto/16 :goto_1c
+
+    goto/32 :goto_1c
+
+    :goto_1c
+    return-void
+
+    nop
+
+    :array_1e
+    .array-data 4
+        0x1
+        0x2
+        0x3
+        0x4
+        0x5
+        0x6
+        0x7
+        0x8
+        0x9
+        0x0
+    .end array-data
+.end method
+
+.method public static moves()V
+    .registers 2
+
+    move v0, v1
+
+    move/from16 v0, v1
+
+    move/16 v0, v1
+
+    move-wide v0, v1
+
+    move-wide/from16 v0, v1
+
+    move-wide/16 v0, v1
+
+    move-object v0, v1
+
+    move-object/from16 v0, v1
+
+    move-object/16 v0, v1
+
+    move-result v0
+
+    move-result-wide v0
+
+    move-result-object v0
+
+    move-exception v0
+
+    return-void
+.end method
+
+.method public static packed_switch()V
+    .registers 1
+
+    packed-switch v0, :pswitch_data_8
+
+    :goto_3
+    return-void
+
+    goto :goto_3
+
+    :pswitch_5
+    goto :goto_3
+
+    :pswitch_6
+    goto :goto_3
+
+    nop
+
+    :pswitch_data_8
+    .packed-switch 0x7ffffffe
+        :pswitch_5
+        :pswitch_6
+    .end packed-switch
+.end method
+
+.method public static return32()I
+    .registers 1
+
+    return v0
+.end method
+
+.method public static return64()I
+    .registers 2
+
+    return-wide v0
+.end method
+
+.method public static return_object()Ljava/lang/Object;
+    .registers 1
+
+    return-object v0
+.end method
+
+.method public static sparse_switch()V
+    .registers 2
+
+    sparse-switch v0, :sswitch_data_4
+
+    :sswitch_3
+    return-void
+
+    :sswitch_data_4
+    .sparse-switch
+        0x1111 -> :sswitch_3
+        0x2222 -> :sswitch_3
+        0x3333 -> :sswitch_3
+        0x4444 -> :sswitch_3
+    .end sparse-switch
+.end method
+
+.method public static static_fields()V
+    .registers 1
+
+    sget v0, LA;->sI:I
+
+    sget-wide v0, LA;->sJ:J
+
+    sget-object v0, LA;->sO:LA;
+
+    sget-boolean v0, LA;->sZ:Z
+
+    sget-byte v0, LA;->sB:B
+
+    sget-char v0, LA;->sC:C
+
+    sget-short v0, LA;->sS:S
+
+    sput v0, LA;->sI:I
+
+    sput-wide v0, LA;->sJ:J
+
+    sput-object v0, LA;->sO:LA;
+
+    sput-boolean v0, LA;->sZ:Z
+
+    sput-byte v0, LA;->sB:B
+
+    sput-char v0, LA;->sC:C
+
+    sput-short v0, LA;->mS:S
+
+    return-void
+.end method
+
+.method public static unary_ops()V
+    .registers 2
+
+    neg-int v0, v1
+
+    not-int v0, v1
+
+    neg-long v0, v1
+
+    not-long v0, v1
+
+    neg-float v0, v1
+
+    neg-double v0, v1
+
+    int-to-long v0, v1
+
+    int-to-float v0, v1
+
+    int-to-double v0, v1
+
+    long-to-int v0, v1
+
+    long-to-float v0, v1
+
+    long-to-double v0, v1
+
+    float-to-int v0, v1
+
+    float-to-long v0, v1
+
+    float-to-double v0, v1
+
+    double-to-int v0, v1
+
+    double-to-long v0, v1
+
+    double-to-float v0, v1
+
+    int-to-byte v0, v1
+
+    int-to-char v0, v1
+
+    int-to-short v0, v1
+
+    return-void
+.end method
+
+
+# virtual methods
+.method public instance_fields()V
+    .registers 2
+
+    iget v0, p0, LA;->sI:I
+
+    iget-wide v0, p0, LA;->sJ:J
+
+    iget-object v0, p0, LA;->sO:LA;
+
+    iget-boolean v0, p0, LA;->sZ:Z
+
+    iget-byte v0, p0, LA;->sB:B
+
+    iget-char v0, p0, LA;->sC:C
+
+    iget-short v0, p0, LA;->sS:S
+
+    iput v0, p0, LA;->sI:I
+
+    iput-wide v0, p0, LA;->sJ:J
+
+    iput-object v0, p0, LA;->sO:LA;
+
+    iput-boolean v0, p0, LA;->sZ:Z
+
+    iput-byte v0, p0, LA;->sB:B
+
+    iput-char v0, p0, LA;->sC:C
+
+    iput-short v0, p0, LA;->sS:S
+
+    return-void
+.end method
+
+.method public invokes()V
+    .registers 5
+
+    invoke-virtual {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+    invoke-super {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+    invoke-direct {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+    invoke-static {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+    invoke-interface {v0, v1, v2, v3, p0}, LA;->invokes()V
+.end method
diff --git a/testdata/const-method-handle-min.smali b/testdata/const-method-handle-min.smali
new file mode 100644
index 0000000..0bf157f
--- /dev/null
+++ b/testdata/const-method-handle-min.smali
@@ -0,0 +1,14 @@
+# Tests const-method-handle added in DEX version 39.
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble const-method-handle.smali --api 28
+
+.class public LConstMethodHandle;
+.super Ljava/lang/Object;
+
+.method public (I)V
+  .registers 2
+  const-method-handle v1, invoke-static@Ljava/lang/String;->copyValueOf([C)Ljava/lang/String;
+  const-method-handle v0, invoke-instance@Ljava/lang/String;->charAt(I)C
+  return-void
+.end method
diff --git a/testdata/const-method-type-min.smali b/testdata/const-method-type-min.smali
new file mode 100644
index 0000000..8a0f632
--- /dev/null
+++ b/testdata/const-method-type-min.smali
@@ -0,0 +1,16 @@
+# Tests const-method-type added in DEX version 39.
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble const-method-type.smali --api 28
+
+.class public LConstMethodTypeTest;
+.super Ljava/lang/Object;
+
+.method public test(I)V
+    .registers 4
+    const-method-type v0, ()I
+    const-method-type v1, (C)V
+    const-method-type v2, (I)V
+    const-method-type v3, (I)I
+    return-void
+.end method
diff --git a/testdata/invoke-custom-min.smali b/testdata/invoke-custom-min.smali
new file mode 100644
index 0000000..64bccbc
--- /dev/null
+++ b/testdata/invoke-custom-min.smali
@@ -0,0 +1,39 @@
+# Tests invoke-custom added in DEX version 38.
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble invoke-custom-min.smali --api 28
+
+.class public LFoo;
+.super Ljava/lang/Object;
+
+.method public la1(Ljava/util/ArrayList;)V
+    .registers 5
+    .annotation system Ldalvik/annotation/Signature;
+        value = {
+            "(",
+            "Ljava/util/ArrayList",
+            "<",
+            "Ljava/lang/String;",
+            ">;)V"
+        }
+    .end annotation
+
+    .prologue
+    .line 42
+    invoke-virtual {p1}, Ljava/util/ArrayList;->stream()Ljava/util/stream/Stream;
+
+    move-result-object v0
+
+    invoke-custom {}, call_site_1("bar", ()Ljava/util/function/Predicate;, (Ljava/lang/Object;)Z, invoke-static@LFoo;->lambda$la1$1(I)Z, (I)Z)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+
+    move-result-object v1
+
+    invoke-custom {}, call_site_2("test", ()Ljava/util/function/Predicate;, (Ljava/lang/Object;)Z, invoke-static@LFoo;->lambda$la1$1(Ljava/lang/String;)Z, (Ljava/lang/String;)Z)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+
+    move-result-object v2
+
+    invoke-interface {v0, v1, v2}, Ljava/util/stream/Stream;->filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
+
+    .line 50
+    return-void
+.end method
diff --git a/testdata/invoke-polymorphic.smali b/testdata/invoke-polymorphic.smali
new file mode 100644
index 0000000..1ad7246
--- /dev/null
+++ b/testdata/invoke-polymorphic.smali
@@ -0,0 +1,70 @@
+# Tests invoke-polymorphic added in DEX version 38.
+# Disassembled from dexdump test files.
+# Repo: https://android.googlesource.com/platform/art/
+# File: test/dexdump/invoke-polymorphic.dex
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble invoke-polymorphic.smali --api 28
+
+.class public LMain;
+.super Ljava/lang/Object;
+.source "Main.java"
+
+
+# direct methods
+.method public constructor <init>()V
+    .registers 1
+
+    .prologue
+    .line 9
+    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+
+    return-void
+.end method
+
+.method public static main([Ljava/lang/String;)V
+    .registers 10
+    .param p0, "args"    # [Ljava/lang/String;
+    .annotation system Ldalvik/annotation/Throws;
+        value = {
+            Ljava/lang/Throwable;
+        }
+    .end annotation
+
+    .prologue
+    const-wide v2, 0x400199999999999aL    # 2.2
+
+    const/4 v4, 0x1
+
+    .line 31
+    const/4 v0, 0x0
+
+    .line 32
+    .local v0, "handle":Ljava/lang/invoke/MethodHandle;
+    const/4 v5, 0x0
+
+    .line 33
+    .local v5, "o":Ljava/lang/Object;
+    const-string/jumbo v1, "a"
+
+    move v6, v4
+
+    invoke-polymorphic/range {v0 .. v6}, Ljava/lang/invoke/MethodHandle;->invoke([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;DILjava/lang/Object;I)Ljava/lang/String;
+
+    move-result-object v7
+
+    .line 34
+    .local v7, "s":Ljava/lang/String;
+    invoke-polymorphic {v0, v2, v3, v4}, Ljava/lang/invoke/MethodHandle;->invokeExact([Ljava/lang/Object;)Ljava/lang/Object;, (DI)I
+
+    move-result v8
+
+    .line 35
+    .local v8, "x":I
+    const-string/jumbo v1, "a"
+
+    invoke-polymorphic {v0, v1, v2, v3, v4}, Ljava/lang/invoke/MethodHandle;->invoke([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;DI)V
+
+    .line 56
+    return-void
+.end method
diff --git a/type_dex.h b/type_dex.h
index 432a031..ee61ecd 100644
--- a/type_dex.h
+++ b/type_dex.h
@@ -12,11 +12,11 @@
 // Contains types that models DEX executable format data structures.
 // See https://source.android.com/devices/tech/dalvik/dex-format
 
-// The supported versions are 035 and 037.
+// The supported versions are 035, 037, 038, and 039.
 
 enum class FormatId : uint8_t {
   b,  // 22b.
-  c,  // 21c, 22c, 31c, 35c, 3rc.
+  c,  // 21c, 22c, 31c, 35c, 3rc, 45cc, 4rcc.
   h,  // 21h.
   i,  // 31i.
   l,  // 51l.
@@ -110,6 +110,12 @@
     {0xD0, 2, FormatId::s, 8},
     {0xD8, 2, FormatId::b, 11},
     // {0xE3, 1, FormatId::x, 29}, unused
+    {0xFA, 4, FormatId::c},
+    {0xFB, 4, FormatId::c},
+    {0xFC, 3, FormatId::c},
+    {0xFD, 3, FormatId::c},
+    {0xFE, 2, FormatId::c},
+    {0xFF, 2, FormatId::c},
 };
 
 // Supported by MSVC, g++, and clang++. Ensures no gaps in packing.
@@ -185,6 +191,36 @@
   uint32_t static_values_off;
 };
 
+// call_site_id_item: Call site identifiers list.
+struct CallSiteIdItem {
+  uint32_t call_site_off;
+};
+
+// method_handle_type: Determines the behavior of the MethodHandleItem.
+enum class MethodHandleType : uint16_t {
+  // FieldId
+  kStaticPut = 0x00,
+  kStaticGet = 0x01,
+  kInstancePut = 0x02,
+  kInstanceGet = 0x03,
+  // MethodId
+  kInvokeStatic = 0x04,
+  kInvokeInstance = 0x05,
+  kInvokeConstructor = 0x06,
+  kInvokeDirect = 0x07,
+  kInvokeInterface = 0x08,
+  // Sentinel. If new types are added put them before this and increment.
+  kMaxMethodHandleType = 0x09
+};
+
+// method_handle_item: Method handles referred within the Dex file.
+struct MethodHandleItem {
+  uint16_t method_handle_type;
+  uint16_t unused_1;
+  uint16_t field_or_method_id;
+  uint16_t unused_2;
+};
+
 // code_item: Header of a code item.
 struct CodeItem {
   uint16_t registers_size;
@@ -196,7 +232,31 @@
   // Variable length data follow for complete code item.
 };
 
-constexpr uint32_t kMaxItemListSize = 18;
+// Number of valid type codes for map_item elements in map_list.
+// See: https://source.android.com/devices/tech/dalvik/dex-format#type-codes
+constexpr uint32_t kMaxItemListSize = 21;
+
+constexpr uint16_t kTypeHeaderItem = 0x0000;
+constexpr uint16_t kTypeStringIdItem = 0x0001;
+constexpr uint16_t kTypeTypeIdItem = 0x0002;
+constexpr uint16_t kTypeProtoIdItem = 0x0003;
+constexpr uint16_t kTypeFieldIdItem = 0x0004;
+constexpr uint16_t kTypeMethodIdItem = 0x0005;
+constexpr uint16_t kTypeClassDefItem = 0x0006;
+constexpr uint16_t kTypeCallSiteIdItem = 0x0007;
+constexpr uint16_t kTypeMethodHandleItem = 0x0008;
+constexpr uint16_t kTypeMapList = 0x1000;
+constexpr uint16_t kTypeTypeList = 0x1001;
+constexpr uint16_t kTypeAnnotationSetRefList = 0x1002;
+constexpr uint16_t kTypeAnnotationSetItem = 0x1003;
+constexpr uint16_t kTypeClassDataItem = 0x2000;
+constexpr uint16_t kTypeCodeItem = 0x2001;
+constexpr uint16_t kTypeStringDataItem = 0x2002;
+constexpr uint16_t kTypeDebugInfoItem = 0x2003;
+constexpr uint16_t kTypeAnnotationItem = 0x2004;
+constexpr uint16_t kTypeEncodedArrayItem = 0x2005;
+constexpr uint16_t kTypeAnnotationsDirectoryItem = 0x2006;
+constexpr uint16_t kTypeHiddenApiClassDataItem = 0xF000;
 
 // map_item
 struct MapItem {
@@ -264,25 +324,6 @@
   uint16_t handler_off;
 };
 
-constexpr uint16_t kTypeHeaderItem = 0x0000;
-constexpr uint16_t kTypeStringIdItem = 0x0001;
-constexpr uint16_t kTypeTypeIdItem = 0x0002;
-constexpr uint16_t kTypeProtoIdItem = 0x0003;
-constexpr uint16_t kTypeFieldIdItem = 0x0004;
-constexpr uint16_t kTypeMethodIdItem = 0x0005;
-constexpr uint16_t kTypeClassDefItem = 0x0006;
-constexpr uint16_t kTypeMapList = 0x1000;
-constexpr uint16_t kTypeTypeList = 0x1001;
-constexpr uint16_t kTypeAnnotationSetRefList = 0x1002;
-constexpr uint16_t kTypeAnnotationSetItem = 0x1003;
-constexpr uint16_t kTypeClassDataItem = 0x2000;
-constexpr uint16_t kTypeCodeItem = 0x2001;
-constexpr uint16_t kTypeStringDataItem = 0x2002;
-constexpr uint16_t kTypeDebugInfoItem = 0x2003;
-constexpr uint16_t kTypeAnnotationItem = 0x2004;
-constexpr uint16_t kTypeEncodedArrayItem = 0x2005;
-constexpr uint16_t kTypeAnnotationsDirectoryItem = 0x2006;
-
 #pragma pack(pop)
 
 }  // namespace dex
diff --git a/zucchini_commands.cc b/zucchini_commands.cc
index 93929bd..0699cbe 100644
--- a/zucchini_commands.cc
+++ b/zucchini_commands.cc
@@ -51,6 +51,11 @@
                          params.command_line.HasSwitch(kSwitchKeep));
 }
 
+zucchini::status::Code MainVerify(MainParams params) {
+  CHECK_EQ(1U, params.file_paths.size());
+  return zucchini::VerifyPatch(params.file_paths[0]);
+}
+
 zucchini::status::Code MainRead(MainParams params) {
   CHECK_EQ(1U, params.file_paths.size());
   base::File input_file(params.file_paths[0],
diff --git a/zucchini_commands.h b/zucchini_commands.h
index cef18dc..91c2ef8 100644
--- a/zucchini_commands.h
+++ b/zucchini_commands.h
@@ -36,6 +36,9 @@
 // Command Function: Patch application.
 zucchini::status::Code MainApply(MainParams params);
 
+// Command Function: Verify patch format and compatibility.
+zucchini::status::Code MainVerify(MainParams params);
+
 // Command Function: Read and dump references from an executable.
 zucchini::status::Code MainRead(MainParams params);
 
diff --git a/zucchini_gen_unittest.cc b/zucchini_gen_unittest.cc
index 3a6d2cb..bc37bf6 100644
--- a/zucchini_gen_unittest.cc
+++ b/zucchini_gen_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <stdint.h>
 
+#include <deque>
 #include <utility>
 #include <vector>
 
@@ -30,8 +31,8 @@
 std::vector<int32_t> GenerateReferencesDeltaTest(
     std::vector<Reference>&& old_references,
     std::vector<Reference>&& new_references,
-    std::vector<offset_t>&& exp_old_targets,
-    std::vector<offset_t>&& exp_projected_old_targets,
+    std::deque<offset_t>&& exp_old_targets,
+    std::deque<offset_t>&& exp_projected_old_targets,
     EquivalenceMap&& equivalence_map) {
   // OffsetMapper needs image sizes for forward-projection overflow check. These
   // are tested elsewhere, so just use arbitrary large value.
diff --git a/zucchini_integration.cc b/zucchini_integration.cc
index ff7e792..bf28b3c 100644
--- a/zucchini_integration.cc
+++ b/zucchini_integration.cc
@@ -146,6 +146,22 @@
   return status::kStatusSuccess;
 }
 
+status::Code VerifyPatchCommon(base::File patch_file,
+                               base::FilePath patch_name) {
+  MappedFileReader mapped_patch(std::move(patch_file));
+  if (mapped_patch.HasError()) {
+    LOG(ERROR) << "Error with file " << patch_name.value() << ": "
+               << mapped_patch.error();
+    return status::kStatusFileReadError;
+  }
+  auto patch_reader = EnsemblePatchReader::Create(mapped_patch.region());
+  if (!patch_reader.has_value()) {
+    LOG(ERROR) << "Error reading patch header.";
+    return status::kStatusPatchReadError;
+  }
+  return status::kStatusSuccess;
+}
+
 }  // namespace
 
 status::Code Generate(base::File old_file,
@@ -206,4 +222,15 @@
                      std::move(new_file), file_names, force_keep);
 }
 
+status::Code VerifyPatch(base::File patch_file) {
+  return VerifyPatchCommon(std::move(patch_file), base::FilePath());
+}
+
+status::Code VerifyPatch(const base::FilePath& patch_path) {
+  using base::File;
+  File patch_file(patch_path, File::FLAG_OPEN | File::FLAG_READ |
+                                  base::File::FLAG_SHARE_DELETE);
+  return VerifyPatchCommon(std::move(patch_file), patch_path);
+}
+
 }  // namespace zucchini
diff --git a/zucchini_integration.h b/zucchini_integration.h
index 2ae6091..2b6287b 100644
--- a/zucchini_integration.h
+++ b/zucchini_integration.h
@@ -63,6 +63,16 @@
                    const base::FilePath& new_path,
                    bool force_keep = false);
 
+// Verifies the patch format in |patch_file| and returns
+// Code::kStatusPatchReadError if the patch is malformed or version is
+// unsupported. Since this uses memory mapped files, crashes are expected in
+// case of I/O errors.
+status::Code VerifyPatch(base::File patch_file);
+
+// Alternative VerifyPatch() interface that takes base::FilePath as arguments.
+// Performs proper cleanup in Windows and UNIX if failure occurs.
+status::Code VerifyPatch(const base::FilePath& patch_path);
+
 }  // namespace zucchini
 
 #endif  // COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_