| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * Header file of an in-memory representation of DEX files. |
| */ |
| |
| #include <stdint.h> |
| #include <vector> |
| |
| #include "dex_ir_builder.h" |
| |
| namespace art { |
| namespace dex_ir { |
| |
| namespace { |
| |
| static uint64_t ReadVarWidth(const uint8_t** data, uint8_t length, bool sign_extend) { |
| uint64_t value = 0; |
| for (uint32_t i = 0; i <= length; i++) { |
| value |= static_cast<uint64_t>(*(*data)++) << (i * 8); |
| } |
| if (sign_extend) { |
| int shift = (7 - length) * 8; |
| return (static_cast<int64_t>(value) << shift) >> shift; |
| } |
| return value; |
| } |
| |
| // Prototype to break cyclic dependency. |
| void ReadPayloadUnion(Header& header, |
| const uint8_t** data, |
| uint8_t type, |
| uint8_t length, |
| ArrayItem::PayloadUnion* item); |
| |
| ArrayItem* ReadArrayItem(Header& header, const uint8_t** data, uint8_t type, uint8_t length) { |
| ArrayItem* item = new ArrayItem(type); |
| ReadPayloadUnion(header, data, type, length, item->GetPayloadUnion()); |
| return item; |
| } |
| |
| ArrayItem* ReadArrayItem(Header& header, const uint8_t** data) { |
| const uint8_t encoded_value = *(*data)++; |
| const uint8_t type = encoded_value & 0x1f; |
| ArrayItem* item = new ArrayItem(type); |
| ReadPayloadUnion(header, data, type, encoded_value >> 5, item->GetPayloadUnion()); |
| return item; |
| } |
| |
| void ReadPayloadUnion(Header& header, |
| const uint8_t** data, |
| uint8_t type, |
| uint8_t length, |
| ArrayItem::PayloadUnion* item) { |
| switch (type) { |
| case DexFile::kDexAnnotationByte: |
| item->byte_val_ = static_cast<int8_t>(ReadVarWidth(data, length, false)); |
| break; |
| case DexFile::kDexAnnotationShort: |
| item->short_val_ = static_cast<int16_t>(ReadVarWidth(data, length, true)); |
| break; |
| case DexFile::kDexAnnotationChar: |
| item->char_val_ = static_cast<uint16_t>(ReadVarWidth(data, length, false)); |
| break; |
| case DexFile::kDexAnnotationInt: |
| item->int_val_ = static_cast<int32_t>(ReadVarWidth(data, length, true)); |
| break; |
| case DexFile::kDexAnnotationLong: |
| item->long_val_ = static_cast<int64_t>(ReadVarWidth(data, length, true)); |
| break; |
| case DexFile::kDexAnnotationFloat: { |
| // Fill on right. |
| union { |
| float f; |
| uint32_t data; |
| } conv; |
| conv.data = static_cast<uint32_t>(ReadVarWidth(data, length, false)) << (3 - length) * 8; |
| item->float_val_ = conv.f; |
| break; |
| } |
| case DexFile::kDexAnnotationDouble: { |
| // Fill on right. |
| union { |
| double d; |
| uint64_t data; |
| } conv; |
| conv.data = ReadVarWidth(data, length, false) << (7 - length) * 8; |
| item->double_val_ = conv.d; |
| break; |
| } |
| case DexFile::kDexAnnotationString: { |
| const uint32_t string_index = static_cast<uint32_t>(ReadVarWidth(data, length, false)); |
| item->string_val_ = header.StringIds()[string_index].get(); |
| break; |
| } |
| case DexFile::kDexAnnotationType: { |
| const uint32_t string_index = static_cast<uint32_t>(ReadVarWidth(data, length, false)); |
| item->string_val_ = header.TypeIds()[string_index]->GetStringId(); |
| break; |
| } |
| case DexFile::kDexAnnotationField: |
| case DexFile::kDexAnnotationEnum: { |
| const uint32_t field_index = static_cast<uint32_t>(ReadVarWidth(data, length, false)); |
| item->field_val_ = header.FieldIds()[field_index].get(); |
| break; |
| } |
| case DexFile::kDexAnnotationMethod: { |
| const uint32_t method_index = static_cast<uint32_t>(ReadVarWidth(data, length, false)); |
| item->method_val_ = header.MethodIds()[method_index].get(); |
| break; |
| } |
| case DexFile::kDexAnnotationArray: { |
| item->annotation_array_val_ = new ArrayItemVector(); |
| // Decode all elements. |
| const uint32_t size = DecodeUnsignedLeb128(data); |
| for (uint32_t i = 0; i < size; i++) { |
| item->annotation_array_val_->push_back( |
| std::unique_ptr<ArrayItem>(ReadArrayItem(header, data))); |
| } |
| break; |
| } |
| case DexFile::kDexAnnotationAnnotation: { |
| const uint32_t type_idx = DecodeUnsignedLeb128(data); |
| item->annotation_annotation_val_.string_ = header.TypeIds()[type_idx]->GetStringId(); |
| item->annotation_annotation_val_.array_ = |
| new std::vector<std::unique_ptr<ArrayItem::NameValuePair>>(); |
| // Decode all name=value pairs. |
| const uint32_t size = DecodeUnsignedLeb128(data); |
| for (uint32_t i = 0; i < size; i++) { |
| const uint32_t name_index = DecodeUnsignedLeb128(data); |
| item->annotation_annotation_val_.array_->push_back( |
| std::unique_ptr<ArrayItem::NameValuePair>( |
| new ArrayItem::NameValuePair(header.StringIds()[name_index].get(), |
| ReadArrayItem(header, data)))); |
| } |
| break; |
| } |
| case DexFile::kDexAnnotationNull: |
| break; |
| case DexFile::kDexAnnotationBoolean: |
| item->bool_val_ = (length != 0); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static bool GetPositionsCb(void* context, const DexFile::PositionInfo& entry) { |
| DebugInfoItem* debug_info = reinterpret_cast<DebugInfoItem*>(context); |
| PositionInfoVector& positions = debug_info->GetPositionInfo(); |
| positions.push_back(std::unique_ptr<PositionInfo>(new PositionInfo(entry.address_, entry.line_))); |
| return false; |
| } |
| |
| static void GetLocalsCb(void* context, const DexFile::LocalInfo& entry) { |
| DebugInfoItem* debug_info = reinterpret_cast<DebugInfoItem*>(context); |
| LocalInfoVector& locals = debug_info->GetLocalInfo(); |
| const char* name = entry.name_ != nullptr ? entry.name_ : "(null)"; |
| const char* signature = entry.signature_ != nullptr ? entry.signature_ : ""; |
| locals.push_back(std::unique_ptr<LocalInfo>( |
| new LocalInfo(name, entry.descriptor_, signature, entry.start_address_, |
| entry.end_address_, entry.reg_))); |
| } |
| |
| CodeItem* ReadCodeItem(const DexFile& dex_file, |
| const DexFile::CodeItem& disk_code_item, |
| Header& header) { |
| uint16_t registers_size = disk_code_item.registers_size_; |
| uint16_t ins_size = disk_code_item.ins_size_; |
| uint16_t outs_size = disk_code_item.outs_size_; |
| uint32_t tries_size = disk_code_item.tries_size_; |
| |
| const uint8_t* debug_info_stream = dex_file.GetDebugInfoStream(&disk_code_item); |
| DebugInfoItem* debug_info = nullptr; |
| if (debug_info_stream != nullptr) { |
| debug_info = new DebugInfoItem(); |
| } |
| |
| uint32_t insns_size = disk_code_item.insns_size_in_code_units_; |
| uint16_t* insns = new uint16_t[insns_size]; |
| memcpy(insns, disk_code_item.insns_, insns_size * sizeof(uint16_t)); |
| |
| TryItemVector* tries = nullptr; |
| if (tries_size > 0) { |
| tries = new TryItemVector(); |
| for (uint32_t i = 0; i < tries_size; ++i) { |
| const DexFile::TryItem* disk_try_item = dex_file.GetTryItems(disk_code_item, i); |
| uint32_t start_addr = disk_try_item->start_addr_; |
| uint16_t insn_count = disk_try_item->insn_count_; |
| CatchHandlerVector* handlers = new CatchHandlerVector(); |
| for (CatchHandlerIterator it(disk_code_item, *disk_try_item); it.HasNext(); it.Next()) { |
| const uint16_t type_index = it.GetHandlerTypeIndex(); |
| const TypeId* type_id = header.GetTypeIdOrNullPtr(type_index); |
| handlers->push_back(std::unique_ptr<const CatchHandler>( |
| new CatchHandler(type_id, it.GetHandlerAddress()))); |
| } |
| TryItem* try_item = new TryItem(start_addr, insn_count, handlers); |
| tries->push_back(std::unique_ptr<const TryItem>(try_item)); |
| } |
| } |
| return new CodeItem(registers_size, ins_size, outs_size, debug_info, insns_size, insns, tries); |
| } |
| |
| MethodItem* GenerateMethodItem(const DexFile& dex_file, |
| dex_ir::Header& header, |
| ClassDataItemIterator& cdii) { |
| MethodId* method_item = header.MethodIds()[cdii.GetMemberIndex()].get(); |
| uint32_t access_flags = cdii.GetRawMemberAccessFlags(); |
| const DexFile::CodeItem* disk_code_item = cdii.GetMethodCodeItem(); |
| CodeItem* code_item = nullptr; |
| DebugInfoItem* debug_info = nullptr; |
| if (disk_code_item != nullptr) { |
| code_item = ReadCodeItem(dex_file, *disk_code_item, header); |
| code_item->SetOffset(cdii.GetMethodCodeItemOffset()); |
| debug_info = code_item->DebugInfo(); |
| } |
| if (debug_info != nullptr) { |
| bool is_static = (access_flags & kAccStatic) != 0; |
| dex_file.DecodeDebugLocalInfo( |
| disk_code_item, is_static, cdii.GetMemberIndex(), GetLocalsCb, debug_info); |
| dex_file.DecodeDebugPositionInfo(disk_code_item, GetPositionsCb, debug_info); |
| } |
| return new MethodItem(access_flags, method_item, code_item); |
| } |
| |
| AnnotationSetItem* ReadAnnotationSetItem(const DexFile& dex_file, |
| const DexFile::AnnotationSetItem& disk_annotations_item, |
| Header& header) { |
| if (disk_annotations_item.size_ == 0) { |
| return nullptr; |
| } |
| AnnotationItemVector* items = new AnnotationItemVector(); |
| for (uint32_t i = 0; i < disk_annotations_item.size_; ++i) { |
| const DexFile::AnnotationItem* annotation = |
| dex_file.GetAnnotationItem(&disk_annotations_item, i); |
| if (annotation == nullptr) { |
| continue; |
| } |
| uint8_t visibility = annotation->visibility_; |
| const uint8_t* annotation_data = annotation->annotation_; |
| ArrayItem* array_item = |
| ReadArrayItem(header, &annotation_data, DexFile::kDexAnnotationAnnotation, 0); |
| items->push_back(std::unique_ptr<AnnotationItem>(new AnnotationItem(visibility, array_item))); |
| } |
| return new AnnotationSetItem(items); |
| } |
| |
| ParameterAnnotation* ReadParameterAnnotation( |
| const DexFile& dex_file, |
| MethodId* method_id, |
| const DexFile::AnnotationSetRefList* annotation_set_ref_list, |
| Header& header) { |
| AnnotationSetItemVector* annotations = new AnnotationSetItemVector(); |
| for (uint32_t i = 0; i < annotation_set_ref_list->size_; ++i) { |
| const DexFile::AnnotationSetItem* annotation_set_item = |
| dex_file.GetSetRefItemItem(&annotation_set_ref_list->list_[i]); |
| annotations->push_back(std::unique_ptr<AnnotationSetItem>( |
| ReadAnnotationSetItem(dex_file, *annotation_set_item, header))); |
| } |
| return new ParameterAnnotation(method_id, annotations); |
| } |
| |
| AnnotationsDirectoryItem* ReadAnnotationsDirectoryItem( |
| const DexFile& dex_file, |
| const DexFile::AnnotationsDirectoryItem* disk_annotations_item, |
| Header& header) { |
| const DexFile::AnnotationSetItem* class_set_item = |
| dex_file.GetClassAnnotationSet(disk_annotations_item); |
| AnnotationSetItem* class_annotation = nullptr; |
| if (class_set_item != nullptr) { |
| class_annotation = ReadAnnotationSetItem(dex_file, *class_set_item, header); |
| } |
| const DexFile::FieldAnnotationsItem* fields = |
| dex_file.GetFieldAnnotations(disk_annotations_item); |
| FieldAnnotationVector* field_annotations = nullptr; |
| if (fields != nullptr) { |
| field_annotations = new FieldAnnotationVector(); |
| for (uint32_t i = 0; i < disk_annotations_item->fields_size_; ++i) { |
| FieldId* field_id = header.FieldIds()[fields[i].field_idx_].get(); |
| const DexFile::AnnotationSetItem* field_set_item = |
| dex_file.GetFieldAnnotationSetItem(fields[i]); |
| AnnotationSetItem* annotation_set_item = |
| ReadAnnotationSetItem(dex_file, *field_set_item, header); |
| field_annotations->push_back(std::unique_ptr<FieldAnnotation>( |
| new FieldAnnotation(field_id, annotation_set_item))); |
| } |
| } |
| const DexFile::MethodAnnotationsItem* methods = |
| dex_file.GetMethodAnnotations(disk_annotations_item); |
| MethodAnnotationVector* method_annotations = nullptr; |
| if (methods != nullptr) { |
| method_annotations = new MethodAnnotationVector(); |
| for (uint32_t i = 0; i < disk_annotations_item->methods_size_; ++i) { |
| MethodId* method_id = header.MethodIds()[methods[i].method_idx_].get(); |
| const DexFile::AnnotationSetItem* method_set_item = |
| dex_file.GetMethodAnnotationSetItem(methods[i]); |
| AnnotationSetItem* annotation_set_item = |
| ReadAnnotationSetItem(dex_file, *method_set_item, header); |
| method_annotations->push_back(std::unique_ptr<MethodAnnotation>( |
| new MethodAnnotation(method_id, annotation_set_item))); |
| } |
| } |
| const DexFile::ParameterAnnotationsItem* parameters = |
| dex_file.GetParameterAnnotations(disk_annotations_item); |
| ParameterAnnotationVector* parameter_annotations = nullptr; |
| if (parameters != nullptr) { |
| parameter_annotations = new ParameterAnnotationVector(); |
| for (uint32_t i = 0; i < disk_annotations_item->parameters_size_; ++i) { |
| MethodId* method_id = header.MethodIds()[parameters[i].method_idx_].get(); |
| const DexFile::AnnotationSetRefList* list = |
| dex_file.GetParameterAnnotationSetRefList(¶meters[i]); |
| parameter_annotations->push_back(std::unique_ptr<ParameterAnnotation>( |
| ReadParameterAnnotation(dex_file, method_id, list, header))); |
| } |
| } |
| |
| return new AnnotationsDirectoryItem(class_annotation, |
| field_annotations, |
| method_annotations, |
| parameter_annotations); |
| } |
| |
| ClassDef* ReadClassDef(const DexFile& dex_file, |
| const DexFile::ClassDef& disk_class_def, |
| Header& header) { |
| const TypeId* class_type = header.TypeIds()[disk_class_def.class_idx_].get(); |
| uint32_t access_flags = disk_class_def.access_flags_; |
| const TypeId* superclass = header.GetTypeIdOrNullPtr(disk_class_def.superclass_idx_); |
| |
| TypeIdVector* interfaces = nullptr; |
| const DexFile::TypeList* type_list = dex_file.GetInterfacesList(disk_class_def); |
| uint32_t interfaces_offset = disk_class_def.interfaces_off_; |
| if (type_list != nullptr) { |
| interfaces = new TypeIdVector(); |
| for (uint32_t index = 0; index < type_list->Size(); ++index) { |
| interfaces->push_back(header.TypeIds()[type_list->GetTypeItem(index).type_idx_].get()); |
| } |
| } |
| const StringId* source_file = header.GetStringIdOrNullPtr(disk_class_def.source_file_idx_); |
| // Annotations. |
| AnnotationsDirectoryItem* annotations = nullptr; |
| const DexFile::AnnotationsDirectoryItem* disk_annotations_directory_item = |
| dex_file.GetAnnotationsDirectory(disk_class_def); |
| if (disk_annotations_directory_item != nullptr) { |
| annotations = ReadAnnotationsDirectoryItem(dex_file, disk_annotations_directory_item, header); |
| annotations->SetOffset(disk_class_def.annotations_off_); |
| } |
| // Static field initializers. |
| ArrayItemVector* static_values = nullptr; |
| const uint8_t* static_data = dex_file.GetEncodedStaticFieldValuesArray(disk_class_def); |
| if (static_data != nullptr) { |
| uint32_t static_value_count = static_data == nullptr ? 0 : DecodeUnsignedLeb128(&static_data); |
| if (static_value_count > 0) { |
| static_values = new ArrayItemVector(); |
| for (uint32_t i = 0; i < static_value_count; ++i) { |
| static_values->push_back(std::unique_ptr<ArrayItem>(ReadArrayItem(header, &static_data))); |
| } |
| } |
| } |
| // Read the fields and methods defined by the class, resolving the circular reference from those |
| // to classes by setting class at the same time. |
| const uint8_t* encoded_data = dex_file.GetClassData(disk_class_def); |
| ClassData* class_data = nullptr; |
| if (encoded_data != nullptr) { |
| uint32_t offset = disk_class_def.class_data_off_; |
| ClassDataItemIterator cdii(dex_file, encoded_data); |
| // Static fields. |
| FieldItemVector* static_fields = new FieldItemVector(); |
| for (uint32_t i = 0; cdii.HasNextStaticField(); i++, cdii.Next()) { |
| FieldId* field_item = header.FieldIds()[cdii.GetMemberIndex()].get(); |
| uint32_t access_flags = cdii.GetRawMemberAccessFlags(); |
| static_fields->push_back(std::unique_ptr<FieldItem>(new FieldItem(access_flags, field_item))); |
| } |
| // Instance fields. |
| FieldItemVector* instance_fields = new FieldItemVector(); |
| for (uint32_t i = 0; cdii.HasNextInstanceField(); i++, cdii.Next()) { |
| FieldId* field_item = header.FieldIds()[cdii.GetMemberIndex()].get(); |
| uint32_t access_flags = cdii.GetRawMemberAccessFlags(); |
| instance_fields->push_back( |
| std::unique_ptr<FieldItem>(new FieldItem(access_flags, field_item))); |
| } |
| // Direct methods. |
| MethodItemVector* direct_methods = new MethodItemVector(); |
| for (uint32_t i = 0; cdii.HasNextDirectMethod(); i++, cdii.Next()) { |
| direct_methods->push_back( |
| std::unique_ptr<MethodItem>(GenerateMethodItem(dex_file, header, cdii))); |
| } |
| // Virtual methods. |
| MethodItemVector* virtual_methods = new MethodItemVector(); |
| for (uint32_t i = 0; cdii.HasNextVirtualMethod(); i++, cdii.Next()) { |
| virtual_methods->push_back( |
| std::unique_ptr<MethodItem>(GenerateMethodItem(dex_file, header, cdii))); |
| } |
| class_data = new ClassData(static_fields, instance_fields, direct_methods, virtual_methods); |
| class_data->SetOffset(offset); |
| } |
| return new ClassDef(class_type, |
| access_flags, |
| superclass, |
| interfaces, |
| interfaces_offset, |
| source_file, |
| annotations, |
| static_values, |
| class_data); |
| } |
| |
| } // namespace |
| |
| Header* DexIrBuilder(const DexFile& dex_file) { |
| const DexFile::Header& disk_header = dex_file.GetHeader(); |
| Header* header = new Header(disk_header.magic_, |
| disk_header.checksum_, |
| disk_header.signature_, |
| disk_header.endian_tag_, |
| disk_header.file_size_, |
| disk_header.header_size_, |
| disk_header.link_size_, |
| disk_header.link_off_, |
| disk_header.data_size_, |
| disk_header.data_off_); |
| // Walk the rest of the header fields. |
| // StringId table. |
| std::vector<std::unique_ptr<StringId>>& string_ids = header->StringIds(); |
| header->SetStringIdsOffset(disk_header.string_ids_off_); |
| for (uint32_t i = 0; i < dex_file.NumStringIds(); ++i) { |
| const DexFile::StringId& disk_string_id = dex_file.GetStringId(i); |
| StringId* string_id = new StringId(dex_file.GetStringData(disk_string_id)); |
| string_id->SetOffset(i); |
| string_ids.push_back(std::unique_ptr<StringId>(string_id)); |
| } |
| // TypeId table. |
| std::vector<std::unique_ptr<TypeId>>& type_ids = header->TypeIds(); |
| header->SetTypeIdsOffset(disk_header.type_ids_off_); |
| for (uint32_t i = 0; i < dex_file.NumTypeIds(); ++i) { |
| const DexFile::TypeId& disk_type_id = dex_file.GetTypeId(i); |
| TypeId* type_id = new TypeId(header->StringIds()[disk_type_id.descriptor_idx_].get()); |
| type_id->SetOffset(i); |
| type_ids.push_back(std::unique_ptr<TypeId>(type_id)); |
| } |
| // ProtoId table. |
| std::vector<std::unique_ptr<ProtoId>>& proto_ids = header->ProtoIds(); |
| header->SetProtoIdsOffset(disk_header.proto_ids_off_); |
| for (uint32_t i = 0; i < dex_file.NumProtoIds(); ++i) { |
| const DexFile::ProtoId& disk_proto_id = dex_file.GetProtoId(i); |
| // Build the parameter type vector. |
| TypeIdVector* parameters = new TypeIdVector(); |
| DexFileParameterIterator dfpi(dex_file, disk_proto_id); |
| while (dfpi.HasNext()) { |
| parameters->push_back(header->TypeIds()[dfpi.GetTypeIdx()].get()); |
| dfpi.Next(); |
| } |
| ProtoId* proto_id = new ProtoId(header->StringIds()[disk_proto_id.shorty_idx_].get(), |
| header->TypeIds()[disk_proto_id.return_type_idx_].get(), |
| parameters); |
| proto_id->SetOffset(i); |
| proto_ids.push_back(std::unique_ptr<ProtoId>(proto_id)); |
| } |
| // FieldId table. |
| std::vector<std::unique_ptr<FieldId>>& field_ids = header->FieldIds(); |
| header->SetFieldIdsOffset(disk_header.field_ids_off_); |
| for (uint32_t i = 0; i < dex_file.NumFieldIds(); ++i) { |
| const DexFile::FieldId& disk_field_id = dex_file.GetFieldId(i); |
| FieldId* field_id = new FieldId(header->TypeIds()[disk_field_id.class_idx_].get(), |
| header->TypeIds()[disk_field_id.type_idx_].get(), |
| header->StringIds()[disk_field_id.name_idx_].get()); |
| field_id->SetOffset(i); |
| field_ids.push_back(std::unique_ptr<FieldId>(field_id)); |
| } |
| // MethodId table. |
| std::vector<std::unique_ptr<MethodId>>& method_ids = header->MethodIds(); |
| header->SetMethodIdsOffset(disk_header.method_ids_off_); |
| for (uint32_t i = 0; i < dex_file.NumMethodIds(); ++i) { |
| const DexFile::MethodId& disk_method_id = dex_file.GetMethodId(i); |
| MethodId* method_id = new MethodId(header->TypeIds()[disk_method_id.class_idx_].get(), |
| header->ProtoIds()[disk_method_id.proto_idx_].get(), |
| header->StringIds()[disk_method_id.name_idx_].get()); |
| method_id->SetOffset(i); |
| method_ids.push_back(std::unique_ptr<MethodId>(method_id)); |
| } |
| // ClassDef table. |
| std::vector<std::unique_ptr<ClassDef>>& class_defs = header->ClassDefs(); |
| header->SetClassDefsOffset(disk_header.class_defs_off_); |
| for (uint32_t i = 0; i < dex_file.NumClassDefs(); ++i) { |
| const DexFile::ClassDef& disk_class_def = dex_file.GetClassDef(i); |
| ClassDef* class_def = ReadClassDef(dex_file, disk_class_def, *header); |
| class_def->SetOffset(i); |
| class_defs.push_back(std::unique_ptr<ClassDef>(class_def)); |
| } |
| |
| return header; |
| } |
| |
| } // namespace dex_ir |
| } // namespace art |