| // Copyright 2011 Google Inc. All Rights Reserved. |
| |
| #include "dex_verifier.h" |
| |
| #include <iostream> |
| |
| #include "class_linker.h" |
| #include "dex_cache.h" |
| #include "dex_file.h" |
| #include "dex_instruction.h" |
| #include "dex_instruction_visitor.h" |
| #include "dex_verifier.h" |
| #include "logging.h" |
| #include "runtime.h" |
| #include "stringpiece.h" |
| |
| namespace art { |
| |
| #define k_ kRegTypeUnknown |
| #define kU kRegTypeUninit |
| #define kX kRegTypeConflict |
| #define k0 kRegTypeZero |
| #define k1 kRegTypeOne |
| #define kZ kRegTypeBoolean |
| #define ky kRegTypeConstPosByte |
| #define kY kRegTypeConstByte |
| #define kh kRegTypeConstPosShort |
| #define kH kRegTypeConstShort |
| #define kc kRegTypeConstChar |
| #define ki kRegTypeConstInteger |
| #define kb kRegTypePosByte |
| #define kB kRegTypeByte |
| #define ks kRegTypePosShort |
| #define kS kRegTypeShort |
| #define kC kRegTypeChar |
| #define kI kRegTypeInteger |
| #define kF kRegTypeFloat |
| #define kN kRegTypeConstLo |
| #define kn kRegTypeConstHi |
| #define kJ kRegTypeLongLo |
| #define kj kRegTypeLongHi |
| #define kD kRegTypeDoubleLo |
| #define kd kRegTypeDoubleHi |
| |
| const char DexVerifier::merge_table_[kRegTypeMAX][kRegTypeMAX] = |
| { |
| /* chk: _ U X 0 1 Z y Y h H c i b B s S C I F N n J j D d */ |
| { /*_*/ k_,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX }, |
| { /*U*/ kX,kU,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX }, |
| { /*X*/ kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX }, |
| { /*0*/ kX,kX,kX,k0,kZ,kZ,ky,kY,kh,kH,kc,ki,kb,kB,ks,kS,kC,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*1*/ kX,kX,kX,kZ,k1,kZ,ky,kY,kh,kH,kc,ki,kb,kB,ks,kS,kC,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*Z*/ kX,kX,kX,kZ,kZ,kZ,ky,kY,kh,kH,kc,ki,kb,kB,ks,kS,kC,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*y*/ kX,kX,kX,ky,ky,ky,ky,kY,kh,kH,kc,ki,kb,kB,ks,kS,kC,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*Y*/ kX,kX,kX,kY,kY,kY,kY,kY,kh,kH,kc,ki,kB,kB,kS,kS,kI,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*h*/ kX,kX,kX,kh,kh,kh,kh,kh,kh,kH,kc,ki,ks,kS,ks,kS,kC,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*H*/ kX,kX,kX,kH,kH,kH,kH,kH,kH,kH,kc,ki,kS,kS,kS,kS,kI,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*c*/ kX,kX,kX,kc,kc,kc,kc,kc,kc,kc,kc,ki,kC,kI,kC,kI,kC,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*i*/ kX,kX,kX,ki,ki,ki,ki,ki,ki,ki,ki,ki,kI,kI,kI,kI,kI,kI,kF,kX,kX,kX,kX,kX,kX }, |
| { /*b*/ kX,kX,kX,kb,kb,kb,kb,kB,ks,kS,kC,kI,kb,kB,ks,kS,kC,kI,kX,kX,kX,kX,kX,kX,kX }, |
| { /*B*/ kX,kX,kX,kB,kB,kB,kB,kB,kS,kS,kI,kI,kB,kB,kS,kS,kI,kI,kX,kX,kX,kX,kX,kX,kX }, |
| { /*s*/ kX,kX,kX,ks,ks,ks,ks,kS,ks,kS,kC,kI,ks,kS,ks,kS,kC,kI,kX,kX,kX,kX,kX,kX,kX }, |
| { /*S*/ kX,kX,kX,kS,kS,kS,kS,kS,kS,kS,kI,kI,kS,kS,kS,kS,kI,kI,kX,kX,kX,kX,kX,kX,kX }, |
| { /*C*/ kX,kX,kX,kC,kC,kC,kC,kI,kC,kI,kC,kI,kC,kI,kC,kI,kC,kI,kX,kX,kX,kX,kX,kX,kX }, |
| { /*I*/ kX,kX,kX,kI,kI,kI,kI,kI,kI,kI,kI,kI,kI,kI,kI,kI,kI,kI,kX,kX,kX,kX,kX,kX,kX }, |
| { /*F*/ kX,kX,kX,kF,kF,kF,kF,kF,kF,kF,kF,kF,kX,kX,kX,kX,kX,kX,kF,kX,kX,kX,kX,kX,kX }, |
| { /*N*/ kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kN,kX,kJ,kX,kD,kX }, |
| { /*n*/ kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kn,kX,kj,kX,kd }, |
| { /*J*/ kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kJ,kX,kJ,kX,kX,kX }, |
| { /*j*/ kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kj,kX,kj,kX,kX }, |
| { /*D*/ kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kD,kX,kX,kX,kD,kX }, |
| { /*d*/ kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kX,kd,kX,kX,kX,kd }, |
| }; |
| |
| #undef k_ |
| #undef kU |
| #undef kX |
| #undef k0 |
| #undef k1 |
| #undef kZ |
| #undef ky |
| #undef kY |
| #undef kh |
| #undef kH |
| #undef kc |
| #undef ki |
| #undef kb |
| #undef kB |
| #undef ks |
| #undef kS |
| #undef kC |
| #undef kI |
| #undef kF |
| #undef kN |
| #undef kn |
| #undef kJ |
| #undef kj |
| #undef kD |
| #undef kd |
| |
| bool DexVerifier::VerifyClass(Class* klass) { |
| if (klass->IsVerified()) { |
| return true; |
| } |
| for (size_t i = 0; i < klass->NumDirectMethods(); ++i) { |
| Method* method = klass->GetDirectMethod(i); |
| if (!VerifyMethod(method)) { |
| LOG(ERROR) << "Verifier rejected class " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| return false; |
| } |
| } |
| for (size_t i = 0; i < klass->NumVirtualMethods(); ++i) { |
| Method* method = klass->GetVirtualMethod(i); |
| if (!VerifyMethod(method)) { |
| LOG(ERROR) << "Verifier rejected class " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool DexVerifier::VerifyMethod(Method* method) { |
| const DexCache* dex_cache = method->GetDeclaringClass()->GetDexCache(); |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); |
| const DexFile& dex_file = class_linker->FindDexFile(dex_cache); |
| const DexFile::CodeItem* code_item = |
| dex_file.GetCodeItem(method->GetCodeItemOffset()); |
| |
| /* |
| * Construct the verifier state container object. |
| */ |
| VerifierData vdata(method, &dex_file, code_item); |
| |
| /* |
| * If there aren't any instructions, make sure that's expected, then |
| * exit successfully. |
| */ |
| if (code_item == NULL) { |
| if (!method->IsNative() && !method->IsAbstract()) { |
| LOG(ERROR) << "VFY: zero-length code in concrete non-native method"; |
| return false; |
| } |
| return true; |
| } |
| |
| /* |
| * Sanity-check the register counts. ins + locals = registers, so make |
| * sure that ins <= registers. |
| */ |
| if (code_item->ins_size_ > code_item->registers_size_) { |
| LOG(ERROR) << "VFY: bad register counts (ins=" << code_item->ins_size_ |
| << " regs=" << code_item->registers_size_; |
| return false; |
| } |
| |
| /* |
| * Allocate and initialize an array to hold instruction data. |
| */ |
| vdata.insn_flags_.reset(new InsnFlags[code_item->insns_size_]()); |
| |
| /* |
| * Run through the instructions and see if the width checks out. |
| */ |
| if (!ComputeWidthsAndCountOps(&vdata)) { |
| return false; |
| } |
| |
| /* |
| * Flag instructions guarded by a "try" block and check exception handlers. |
| */ |
| if (!ScanTryCatchBlocks(&vdata)) { |
| return false; |
| } |
| |
| /* |
| * Perform static instruction verification. |
| */ |
| if (!VerifyInstructions(&vdata)) { |
| return false; |
| } |
| |
| /* |
| * Perform code flow analysis. |
| */ |
| if (!VerifyCodeFlow(&vdata)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::VerifyInstructions(VerifierData* vdata) { |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| InsnFlags* insn_flags = vdata->insn_flags_.get(); |
| const byte* ptr = reinterpret_cast<const byte*>(code_item->insns_); |
| const Instruction* inst = Instruction::At(ptr); |
| |
| /* Flag the start of the method as a branch target. */ |
| InsnSetBranchTarget(insn_flags, 0); |
| |
| uint32_t width = 0; |
| uint32_t insns_size = code_item->insns_size_; |
| |
| while (width < insns_size) { |
| if (!VerifyInstruction(vdata, inst, width)) { |
| LOG(ERROR) << "VFY: rejecting opcode 0x" << std::hex |
| << (int) inst->Opcode() << " at 0x" << width << std::dec; |
| return false; |
| } |
| |
| /* Flag instructions that are garbage collection points */ |
| if (inst->IsBranch() || inst->IsSwitch() || inst->IsThrow() || |
| inst->IsReturn()) { |
| InsnSetGcPoint(insn_flags, width); |
| } |
| |
| width += inst->Size(); |
| inst = inst->Next(); |
| } |
| return true; |
| } |
| |
| bool DexVerifier::VerifyInstruction(VerifierData* vdata, |
| const Instruction* inst, uint32_t code_offset) { |
| const DexFile* dex_file = vdata->dex_file_; |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| InsnFlags* insn_flags = vdata->insn_flags_.get(); |
| Instruction::DecodedInstruction dec_insn(inst); |
| bool result = true; |
| |
| int argumentA = inst->GetVerifyTypeArgumentA(); |
| int argumentB = inst->GetVerifyTypeArgumentB(); |
| int argumentC = inst->GetVerifyTypeArgumentC(); |
| int extra_flags = inst->GetVerifyExtraFlags(); |
| |
| switch (argumentA) { |
| case Instruction::kVerifyRegA: |
| result &= CheckRegisterIndex(code_item, dec_insn.vA_); |
| break; |
| case Instruction::kVerifyRegAWide: |
| result &= CheckWideRegisterIndex(code_item, dec_insn.vA_); |
| break; |
| } |
| |
| switch (argumentB) { |
| case Instruction::kVerifyRegB: |
| result &= CheckRegisterIndex(code_item, dec_insn.vB_); |
| break; |
| case Instruction::kVerifyRegBField: |
| result &= CheckFieldIndex(dex_file, dec_insn.vB_); |
| break; |
| case Instruction::kVerifyRegBMethod: |
| result &= CheckMethodIndex(dex_file, dec_insn.vB_); |
| break; |
| case Instruction::kVerifyRegBNewInstance: |
| result &= CheckNewInstance(dex_file, dec_insn.vB_); |
| break; |
| case Instruction::kVerifyRegBString: |
| result &= CheckStringIndex(dex_file, dec_insn.vB_); |
| break; |
| case Instruction::kVerifyRegBType: |
| result &= CheckTypeIndex(dex_file, dec_insn.vB_); |
| break; |
| case Instruction::kVerifyRegBWide: |
| result &= CheckWideRegisterIndex(code_item, dec_insn.vB_); |
| break; |
| } |
| |
| switch (argumentC) { |
| case Instruction::kVerifyRegC: |
| result &= CheckRegisterIndex(code_item, dec_insn.vC_); |
| break; |
| case Instruction::kVerifyRegCField: |
| result &= CheckFieldIndex(dex_file, dec_insn.vC_); |
| break; |
| case Instruction::kVerifyRegCNewArray: |
| result &= CheckNewArray(dex_file, dec_insn.vC_); |
| break; |
| case Instruction::kVerifyRegCType: |
| result &= CheckTypeIndex(dex_file, dec_insn.vC_); |
| break; |
| case Instruction::kVerifyRegCWide: |
| result &= CheckWideRegisterIndex(code_item, dec_insn.vC_); |
| break; |
| } |
| |
| switch (extra_flags) { |
| case Instruction::kVerifyArrayData: |
| result &= CheckArrayData(code_item, code_offset); |
| break; |
| case Instruction::kVerifyBranchTarget: |
| result &= CheckBranchTarget(code_item, insn_flags, code_offset); |
| break; |
| case Instruction::kVerifySwitchTargets: |
| result &= CheckSwitchTargets(code_item, insn_flags, code_offset); |
| break; |
| case Instruction::kVerifyVarArg: |
| result &= CheckVarArgRegs(code_item, dec_insn.vA_, dec_insn.arg_); |
| break; |
| case Instruction::kVerifyVarArgRange: |
| result &= CheckVarArgRangeRegs(code_item, dec_insn.vA_, dec_insn.vC_); |
| break; |
| case Instruction::kVerifyError: |
| LOG(ERROR) << "VFY: unexpected opcode " << std::hex |
| << (int) dec_insn.opcode_ << std::dec; |
| result = false; |
| break; |
| } |
| |
| return result; |
| } |
| |
| bool DexVerifier::VerifyCodeFlow(VerifierData* vdata) { |
| Method* method = vdata->method_; |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| uint16_t registers_size = code_item->registers_size_; |
| uint32_t insns_size = code_item->insns_size_; |
| RegisterTable reg_table; |
| |
| if (registers_size * insns_size > 4*1024*1024) { |
| LOG(ERROR) << "VFY: warning: method is huge (regs=" << registers_size |
| << " insns_size=" << insns_size << ")"; |
| } |
| |
| /* Create and initialize register lists. */ |
| if (!InitRegisterTable(vdata, ®_table, kTrackRegsGcPoints)) { |
| return false; |
| } |
| |
| vdata->register_lines_ = reg_table.register_lines_.get(); |
| |
| /* Allocate a map to hold the classes of uninitialized instances. */ |
| vdata->uninit_map_.reset(CreateUninitInstanceMap(vdata)); |
| |
| /* Initialize register types of method arguments. */ |
| if (!SetTypesFromSignature(vdata, reg_table.register_lines_[0].reg_types_.get())) { |
| LOG(ERROR) << "VFY: bad signature '" |
| << method->GetSignature()->ToModifiedUtf8() << "' for " |
| << method->GetDeclaringClass()->GetDescriptor()->ToModifiedUtf8() |
| << "." << method->GetName()->ToModifiedUtf8(); |
| return false; |
| } |
| |
| /* Perform code flow verification. */ |
| if (!CodeFlowVerifyMethod(vdata, ®_table)) { |
| return false; |
| } |
| |
| /* Generate a register map and add it to the method. */ |
| UniquePtr<RegisterMap> map(GenerateRegisterMapV(vdata)); |
| ByteArray* header = ByteArray::Alloc(sizeof(RegisterMapHeader)); |
| ByteArray* data = ByteArray::Alloc(ComputeRegisterMapSize(map.get())); |
| |
| memcpy(header->GetData(), map.get()->header_, sizeof(RegisterMapHeader)); |
| memcpy(data->GetData(), map.get()->data_, ComputeRegisterMapSize(map.get())); |
| |
| method->SetRegisterMapHeader(header); |
| method->SetRegisterMapData(data); |
| |
| return true; |
| } |
| |
| bool DexVerifier::ComputeWidthsAndCountOps(VerifierData* vdata) { |
| const uint16_t* insns = vdata->code_item_->insns_; |
| uint32_t insns_size = vdata->code_item_->insns_size_; |
| InsnFlags* insn_flags = vdata->insn_flags_.get(); |
| const byte* ptr = reinterpret_cast<const byte*>(insns); |
| const Instruction* inst = Instruction::At(ptr); |
| size_t new_instance_count = 0; |
| size_t monitor_enter_count = 0; |
| size_t width = 0; |
| |
| while (width < insns_size) { |
| Instruction::Code opcode = inst->Opcode(); |
| if (opcode == Instruction::NEW_INSTANCE) { |
| new_instance_count++; |
| } else if (opcode == Instruction::MONITOR_ENTER) { |
| monitor_enter_count++; |
| } |
| |
| insn_flags[width] |= inst->Size(); |
| width += inst->Size(); |
| inst = inst->Next(); |
| } |
| |
| if (width != insns_size) { |
| LOG(ERROR) << "VFY: code did not end where expected (" << width << " vs. " |
| << insns_size << ")"; |
| return false; |
| } |
| |
| vdata->new_instance_count_ = new_instance_count; |
| vdata->monitor_enter_count_ = monitor_enter_count; |
| return true; |
| } |
| |
| bool DexVerifier::ScanTryCatchBlocks(VerifierData* vdata) { |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| InsnFlags* insn_flags = vdata->insn_flags_.get(); |
| uint32_t insns_size = code_item->insns_size_; |
| uint32_t tries_size = code_item->tries_size_; |
| |
| if (tries_size == 0) { |
| return true; |
| } |
| |
| const DexFile::TryItem* tries = DexFile::dexGetTryItems(*code_item, 0); |
| |
| for (uint32_t idx = 0; idx < tries_size; idx++) { |
| const DexFile::TryItem* try_item = &tries[idx]; |
| uint32_t start = try_item->start_addr_; |
| uint32_t end = start + try_item->insn_count_; |
| |
| if ((start >= end) || (start >= insns_size) || (end > insns_size)) { |
| LOG(ERROR) << "VFY: bad exception entry: startAddr=" << start |
| << " endAddr=" << end << " (size=" << insns_size << ")"; |
| return false; |
| } |
| |
| if (InsnGetWidth(insn_flags, start) == 0) { |
| LOG(ERROR) << "VFY: 'try' block starts inside an instruction (" |
| << start << ")"; |
| return false; |
| } |
| |
| uint32_t addr; |
| for (addr = start; addr < end; addr += InsnGetWidth(insn_flags, addr)) { |
| InsnSetInTry(insn_flags, addr); |
| } |
| } |
| |
| /* Iterate over each of the handlers to verify target addresses. */ |
| const byte* handlers_ptr = DexFile::dexGetCatchHandlerData(*code_item, 0); |
| uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); |
| for (uint32_t idx = 0; idx < handlers_size; idx++) { |
| DexFile::CatchHandlerIterator iterator(handlers_ptr); |
| |
| for (; !iterator.HasNext(); iterator.Next()) { |
| uint32_t addr = iterator.Get().address_; |
| if (InsnGetWidth(insn_flags, addr) == 0) { |
| LOG(ERROR) << "VFY: exception handler starts at bad address (" |
| << addr << ")"; |
| return false; |
| } |
| |
| InsnSetBranchTarget(insn_flags, addr); |
| } |
| |
| handlers_ptr = iterator.GetData(); |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::GetBranchOffset(const DexFile::CodeItem* code_item, |
| const InsnFlags insn_flags[], uint32_t cur_offset, int32_t* pOffset, |
| bool* pConditional, bool* selfOkay) { |
| const uint16_t* insns = code_item->insns_ + cur_offset; |
| |
| switch (*insns & 0xff) { |
| case Instruction::GOTO: |
| *pOffset = ((int16_t) *insns) >> 8; |
| *pConditional = false; |
| *selfOkay = false; |
| break; |
| case Instruction::GOTO_32: |
| *pOffset = insns[1] | (((uint32_t) insns[2]) << 16); |
| *pConditional = false; |
| *selfOkay = true; |
| break; |
| case Instruction::GOTO_16: |
| *pOffset = (int16_t) insns[1]; |
| *pConditional = false; |
| *selfOkay = false; |
| break; |
| case Instruction::IF_EQ: |
| case Instruction::IF_NE: |
| case Instruction::IF_LT: |
| case Instruction::IF_GE: |
| case Instruction::IF_GT: |
| case Instruction::IF_LE: |
| case Instruction::IF_EQZ: |
| case Instruction::IF_NEZ: |
| case Instruction::IF_LTZ: |
| case Instruction::IF_GEZ: |
| case Instruction::IF_GTZ: |
| case Instruction::IF_LEZ: |
| *pOffset = (int16_t) insns[1]; |
| *pConditional = true; |
| *selfOkay = false; |
| break; |
| default: |
| return false; |
| break; |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CheckArrayData(const DexFile::CodeItem* code_item, |
| uint32_t cur_offset) { |
| const uint32_t insn_count = code_item->insns_size_; |
| const uint16_t* insns = code_item->insns_ + cur_offset; |
| const uint16_t* array_data; |
| int32_t array_data_offset; |
| |
| DCHECK_LT(cur_offset, insn_count); |
| |
| /* make sure the start of the array data table is in range */ |
| array_data_offset = insns[1] | (((int32_t) insns[2]) << 16); |
| if ((int32_t) cur_offset + array_data_offset < 0 || |
| cur_offset + array_data_offset + 2 >= insn_count) |
| { |
| LOG(ERROR) << "VFY: invalid array data start: at " << cur_offset |
| << ", data offset " << array_data_offset << ", count " |
| << insn_count; |
| return false; |
| } |
| |
| /* offset to array data table is a relative branch-style offset */ |
| array_data = insns + array_data_offset; |
| |
| /* make sure the table is 32-bit aligned */ |
| if ((((uint32_t) array_data) & 0x03) != 0) { |
| LOG(ERROR) << "VFY: unaligned array data table: at " << cur_offset |
| << ", data offset " << array_data_offset; |
| return false; |
| } |
| |
| uint32_t value_width = array_data[1]; |
| uint32_t value_count = *(uint32_t*) (&array_data[2]); |
| uint32_t table_size = 4 + (value_width * value_count + 1) / 2; |
| |
| /* make sure the end of the switch is in range */ |
| if (cur_offset + array_data_offset + table_size > insn_count) { |
| LOG(ERROR) << "VFY: invalid array data end: at " << cur_offset |
| << ", data offset " << array_data_offset << ", end " |
| << cur_offset + array_data_offset + table_size << ", count " |
| << insn_count; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CheckNewInstance(const DexFile* dex_file, uint32_t idx) { |
| if (idx >= dex_file->GetHeader().type_ids_size_) { |
| LOG(ERROR) << "VFY: bad type index " << idx << " (max " |
| << dex_file->GetHeader().type_ids_size_ << ")"; |
| return false; |
| } |
| |
| const char* descriptor = dex_file->dexStringByTypeIdx(idx); |
| if (descriptor[0] != 'L') { |
| LOG(ERROR) << "VFY: can't call new-instance on type '" |
| << descriptor << "'"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CheckNewArray(const DexFile* dex_file, uint32_t idx) { |
| if (idx >= dex_file->GetHeader().type_ids_size_) { |
| LOG(ERROR) << "VFY: bad type index " << idx << " (max " |
| << dex_file->GetHeader().type_ids_size_ << ")"; |
| return false; |
| } |
| |
| int bracket_count = 0; |
| const char* descriptor = dex_file->dexStringByTypeIdx(idx); |
| const char* cp = descriptor; |
| while (*cp++ == '[') |
| bracket_count++; |
| |
| if (bracket_count == 0) { |
| /* The given class must be an array type. */ |
| LOG(ERROR) << "VFY: can't new-array class '" << descriptor |
| << "' (not an array)"; |
| return false; |
| } else if (bracket_count > 255) { |
| /* It is illegal to create an array of more than 255 dimensions. */ |
| LOG(ERROR) << "VFY: can't new-array class '" << descriptor |
| << "' (exceeds limit)"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CheckTypeIndex(const DexFile* dex_file, uint32_t idx) { |
| if (idx >= dex_file->GetHeader().type_ids_size_) { |
| LOG(ERROR) << "VFY: bad type index " << idx << " (max " |
| << dex_file->GetHeader().type_ids_size_ << ")"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool DexVerifier::CheckFieldIndex(const DexFile* dex_file, uint32_t idx) { |
| if (idx >= dex_file->GetHeader().field_ids_size_) { |
| LOG(ERROR) << "VFY: bad field index " << idx << " (max " |
| << dex_file->GetHeader().field_ids_size_ << ")"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool DexVerifier::CheckMethodIndex(const DexFile* dex_file, uint32_t idx) { |
| if (idx >= dex_file->GetHeader().method_ids_size_) { |
| LOG(ERROR) << "VFY: bad method index " << idx << " (max " |
| << dex_file->GetHeader().method_ids_size_ << ")"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool DexVerifier::CheckStringIndex(const DexFile* dex_file, uint32_t idx) { |
| if (idx >= dex_file->GetHeader().string_ids_size_) { |
| LOG(ERROR) << "VFY: bad string index " << idx << " (max " |
| << dex_file->GetHeader().string_ids_size_ << ")"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool DexVerifier::CheckRegisterIndex(const DexFile::CodeItem* code_item, |
| uint32_t idx) { |
| if (idx >= code_item->registers_size_) { |
| LOG(ERROR) << "VFY: register index out of range (" << idx << " >= " |
| << code_item->registers_size_ << ")"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool DexVerifier::CheckWideRegisterIndex(const DexFile::CodeItem* code_item, |
| uint32_t idx) { |
| if (idx + 1 >= code_item->registers_size_) { |
| LOG(ERROR) << "VFY: wide register index out of range (" << idx |
| << "+1 >= " << code_item->registers_size_ << ")"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool DexVerifier::CheckVarArgRegs(const DexFile::CodeItem* code_item, |
| uint32_t vA, uint32_t arg[]) { |
| uint16_t registers_size = code_item->registers_size_; |
| uint32_t idx; |
| |
| if (vA > 5) { |
| LOG(ERROR) << "VFY: invalid arg count (" << vA << ") in non-range invoke)"; |
| return false; |
| } |
| |
| for (idx = 0; idx < vA; idx++) { |
| if (arg[idx] > registers_size) { |
| LOG(ERROR) << "VFY: invalid reg index (" << arg[idx] |
| << ") in non-range invoke (> " << registers_size << ")"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CheckVarArgRangeRegs(const DexFile::CodeItem* code_item, |
| uint32_t vA, uint32_t vC) { |
| uint16_t registers_size = code_item->registers_size_; |
| |
| /* |
| * vA/vC are unsigned 8-bit/16-bit quantities for /range instructions, |
| * so there's no risk of integer overflow when adding them here. |
| */ |
| if (vA + vC > registers_size) { |
| LOG(ERROR) << "VFY: invalid reg index " << vA << "+" << vC |
| << " in range invoke (> " << registers_size << ")"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CheckSwitchTargets(const DexFile::CodeItem* code_item, |
| InsnFlags insn_flags[], uint32_t cur_offset) { |
| const uint32_t insn_count = code_item->insns_size_; |
| const uint16_t* insns = code_item->insns_ + cur_offset; |
| const uint16_t* switch_insns; |
| uint16_t expected_signature; |
| uint32_t switch_count, table_size; |
| int32_t switch_offset, keys_offset, targets_offset; |
| int32_t offset, abs_offset; |
| uint32_t targ; |
| |
| /* make sure the start of the switch is in range */ |
| switch_offset = insns[1] | ((int32_t) insns[2]) << 16; |
| if ((int32_t) cur_offset + switch_offset < 0 || |
| cur_offset + switch_offset + 2 >= insn_count) { |
| LOG(ERROR) << "VFY: invalid switch start: at " << cur_offset |
| << ", switch offset " << switch_offset << ", count " |
| << insn_count; |
| return false; |
| } |
| |
| /* offset to switch table is a relative branch-style offset */ |
| switch_insns = insns + switch_offset; |
| |
| /* make sure the table is 32-bit aligned */ |
| if ((((uint32_t) switch_insns) & 0x03) != 0) { |
| LOG(ERROR) << "VFY: unaligned switch table: at " << cur_offset |
| << ", switch offset " << switch_offset; |
| return false; |
| } |
| |
| switch_count = switch_insns[1]; |
| |
| if ((*insns & 0xff) == Instruction::PACKED_SWITCH) { |
| /* 0=sig, 1=count, 2/3=firstKey */ |
| targets_offset = 4; |
| keys_offset = -1; |
| expected_signature = Instruction::kPackedSwitchSignature; |
| } else { |
| /* 0=sig, 1=count, 2..count*2 = keys */ |
| keys_offset = 2; |
| targets_offset = 2 + 2 * switch_count; |
| expected_signature = Instruction::kSparseSwitchSignature; |
| } |
| table_size = targets_offset + switch_count * 2; |
| |
| if (switch_insns[0] != expected_signature) { |
| LOG(ERROR) << "VFY: wrong signature for switch table (0x" << std::hex |
| << switch_insns[0] << ", wanted 0x" << expected_signature << ")" |
| << std::dec; |
| return false; |
| } |
| |
| /* make sure the end of the switch is in range */ |
| if (cur_offset + switch_offset + table_size > (uint32_t) insn_count) { |
| LOG(ERROR) << "VFY: invalid switch end: at " << cur_offset |
| << ", switch offset " << switch_offset << ", end " |
| << cur_offset + switch_offset + table_size << ", count " |
| << insn_count; |
| return false; |
| } |
| |
| /* for a sparse switch, verify the keys are in ascending order */ |
| if (keys_offset > 0 && switch_count > 1) { |
| int32_t last_key; |
| |
| last_key = switch_insns[keys_offset] | |
| (switch_insns[keys_offset + 1] << 16); |
| for (targ = 1; targ < switch_count; targ++) { |
| int32_t key = (int32_t) switch_insns[keys_offset + targ * 2] | |
| (int32_t) (switch_insns[keys_offset + targ * 2 + 1] << 16); |
| if (key <= last_key) { |
| LOG(ERROR) << "VFY: invalid packed switch: last key=" << last_key |
| << ", this=" << key; |
| return false; |
| } |
| |
| last_key = key; |
| } |
| } |
| |
| /* verify each switch target */ |
| for (targ = 0; targ < switch_count; targ++) { |
| offset = (int32_t) switch_insns[targets_offset + targ * 2] | |
| (int32_t) (switch_insns[targets_offset + targ * 2 + 1] << 16); |
| abs_offset = cur_offset + offset; |
| |
| if (abs_offset < 0 || abs_offset >= (int32_t) insn_count || |
| !InsnIsOpcode(insn_flags, abs_offset)) { |
| LOG(ERROR) << "VFY: invalid switch target " << offset << " (-> " |
| << std::hex << abs_offset << ") at " << cur_offset << std::dec |
| << "[" << targ << "]"; |
| return false; |
| } |
| InsnSetBranchTarget(insn_flags, abs_offset); |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CheckBranchTarget(const DexFile::CodeItem* code_item, |
| InsnFlags insn_flags[], uint32_t cur_offset) { |
| const uint32_t insn_count = code_item->insns_size_; |
| int32_t offset, abs_offset; |
| bool isConditional, selfOkay; |
| |
| if (!GetBranchOffset(code_item, insn_flags, cur_offset, &offset, |
| &isConditional, &selfOkay)) |
| return false; |
| |
| if (!selfOkay && offset == 0) { |
| LOG(ERROR) << "VFY: branch offset of zero not allowed at" << std::hex |
| << cur_offset << std::dec; |
| return false; |
| } |
| |
| /* |
| * Check for 32-bit overflow. This isn't strictly necessary if we can |
| * depend on the VM to have identical "wrap-around" behavior, but |
| * it's unwise to depend on that. |
| */ |
| if (((int64_t) cur_offset + (int64_t) offset) != |
| (int64_t) (cur_offset + offset)) { |
| LOG(ERROR) << "VFY: branch target overflow " << std::hex << cur_offset |
| << std::dec << " +" << offset; |
| return false; |
| } |
| abs_offset = cur_offset + offset; |
| if (abs_offset < 0 || (uint32_t) abs_offset >= insn_count || |
| !InsnIsOpcode(insn_flags, abs_offset)) |
| { |
| LOG(ERROR) << "VFY: invalid branch target " << offset << " (-> " |
| << std::hex << abs_offset << ") at " << cur_offset << std::dec; |
| return false; |
| } |
| InsnSetBranchTarget(insn_flags, abs_offset); |
| |
| return true; |
| } |
| |
| bool DexVerifier::InitRegisterTable(VerifierData* vdata, |
| RegisterTable* reg_table, RegisterTrackingMode track_regs_for) { |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| InsnFlags* insn_flags = vdata->insn_flags_.get(); |
| uint16_t registers_size = code_item->registers_size_; |
| uint32_t insns_size = code_item->insns_size_; |
| uint32_t i; |
| |
| /* |
| * Every address gets a RegisterLine struct. This is wasteful, but |
| * not so much that it's worth chasing through an extra level of |
| * indirection. |
| */ |
| reg_table->insn_reg_count_plus_ = registers_size + kExtraRegs; |
| reg_table->register_lines_.reset(new RegisterLine[insns_size]()); |
| |
| DCHECK_GT(insns_size, 0U); |
| |
| bool track_monitors; |
| //if (gDvm.monitorVerification) { |
| //track_monitors = (vdata->monitor_enter_count_ != 0); |
| //} else { |
| track_monitors = false; |
| //} |
| |
| /* |
| * Allocate entries in the sparse register line table. |
| * |
| * There is a RegisterLine associated with every address, but not |
| * every RegisterLine has non-NULL pointers to storage for its fields. |
| */ |
| for (i = 0; i < insns_size; i++) { |
| bool interesting; |
| |
| switch (track_regs_for) { |
| case kTrackRegsAll: |
| interesting = InsnIsOpcode(insn_flags, i); |
| break; |
| case kTrackRegsGcPoints: |
| interesting = InsnIsGcPoint(insn_flags, i) || |
| InsnIsBranchTarget(insn_flags, i); |
| break; |
| case kTrackRegsBranches: |
| interesting = InsnIsBranchTarget(insn_flags, i); |
| break; |
| default: |
| return false; |
| } |
| |
| if (interesting) { |
| reg_table->register_lines_[i].Alloc(reg_table->insn_reg_count_plus_, |
| track_monitors); |
| } |
| } |
| |
| /* |
| * Allocate space for our "temporary" register lines. |
| */ |
| reg_table->work_line_.Alloc(reg_table->insn_reg_count_plus_, track_monitors); |
| reg_table->saved_line_.Alloc(reg_table->insn_reg_count_plus_, track_monitors); |
| |
| return true; |
| } |
| |
| DexVerifier::UninitInstanceMap* DexVerifier::CreateUninitInstanceMap( |
| VerifierData* vdata) { |
| Method* method = vdata->method_; |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| size_t new_instance_count = vdata->new_instance_count_; |
| |
| if (IsInitMethod(method)) { |
| new_instance_count++; |
| } |
| |
| /* |
| * Allocate the header and map as a single unit. |
| * |
| * TODO: consider having a static instance so we can avoid allocations. |
| * I don't think the verifier is guaranteed to be single-threaded when |
| * running in the VM (rather than dexopt), so that must be taken into |
| * account. |
| */ |
| UninitInstanceMap* uninit_map = new UninitInstanceMap(new_instance_count); |
| |
| size_t idx = 0; |
| if (IsInitMethod(method)) { |
| uninit_map->map_[idx++].addr_ = kUninitThisArgAddr; |
| } |
| |
| /* |
| * Run through and find the new-instance instructions. |
| */ |
| uint32_t addr = 0; |
| uint32_t insns_size = code_item->insns_size_; |
| const byte* ptr = reinterpret_cast<const byte*>(code_item->insns_); |
| const Instruction* inst = Instruction::At(ptr); |
| while (addr < insns_size) { |
| Instruction::Code opcode = inst->Opcode(); |
| if (opcode == Instruction::NEW_INSTANCE) { |
| uninit_map->map_[idx++].addr_ = addr; |
| } |
| |
| addr += inst->Size(); |
| inst = inst->Next(); |
| } |
| |
| CHECK_EQ(idx, new_instance_count); |
| return uninit_map; |
| } |
| |
| bool DexVerifier::IsInitMethod(const Method* method) { |
| return (method->GetName()->Equals("<init>")); |
| } |
| |
| Class* DexVerifier::LookupClassByDescriptor(const Method* method, |
| const char* descriptor, VerifyError* failure) { |
| /* |
| * The compiler occasionally puts references to nonexistent classes in |
| * signatures. For example, if you have a non-static inner class with no |
| * constructor, the compiler provides a private <init> for you. |
| * Constructing the class requires <init>(parent), but the outer class can't |
| * call that because the method is private. So the compiler generates a |
| * package-scope <init>(parent,bogus) method that just calls the regular |
| * <init> (the "bogus" part being necessary to distinguish the signature of |
| * the synthetic method). Treating the bogus class as an instance of |
| * java.lang.Object allows the verifier to process the class successfully. |
| */ |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); |
| const ClassLoader* class_loader = |
| method->GetDeclaringClass()->GetClassLoader(); |
| Class* klass = class_linker->FindClass(descriptor, class_loader); |
| |
| if (klass == NULL) { |
| Thread::Current()->ClearException(); |
| if (strchr(descriptor, '$') != NULL) { |
| LOG(INFO) << "VFY: unable to find class referenced in signature (" |
| << descriptor << ")"; |
| } else { |
| LOG(ERROR) << "VFY: unable to find class referenced in signature (" |
| << descriptor << ")"; |
| } |
| |
| /* Check if the descriptor is an array. */ |
| if (descriptor[0] == '[' && descriptor[1] != '\0') { |
| /* |
| * There should never be a problem loading primitive arrays. |
| */ |
| if (descriptor[1] != 'L' && descriptor[1] != '[') { |
| LOG(ERROR) << "VFY: invalid char in signature in '" << descriptor |
| << "'"; |
| *failure = VERIFY_ERROR_GENERIC; |
| } |
| |
| /* |
| * Try to continue with base array type. This will let us pass basic |
| * stuff (e.g. get array len) that wouldn't fly with an Object. This |
| * is NOT correct if the missing type is a primitive array, but we |
| * should never have a problem loading those. (I'm not convinced this |
| * is correct or even useful. Just use Object here?) |
| */ |
| klass = class_linker->FindClass("[Ljava/lang/Object;", class_loader); |
| } else if (descriptor[0] == 'L') { |
| /* |
| * We are looking at a non-array reference descriptor; |
| * try to continue with base reference type. |
| */ |
| klass = class_linker->FindSystemClass("Ljava/lang/Object;"); |
| } else { |
| /* We are looking at a primitive type. */ |
| LOG(ERROR) << "VFY: invalid char in signature in '" << descriptor << "'"; |
| *failure = VERIFY_ERROR_GENERIC; |
| } |
| |
| if (klass == NULL) { |
| *failure = VERIFY_ERROR_GENERIC; |
| } |
| } |
| |
| if (klass->IsPrimitive()) { |
| LOG(ERROR) << "VFY: invalid use of primitive type '" << descriptor << "'"; |
| *failure = VERIFY_ERROR_GENERIC; |
| klass = NULL; |
| } |
| |
| return klass; |
| } |
| |
| Class* DexVerifier::LookupSignatureClass(const Method* method, std::string sig, |
| VerifyError* failure) { |
| DCHECK_EQ(sig[0], 'L'); |
| size_t end = sig.find(';'); |
| |
| if (end == std::string::npos) { |
| LOG(ERROR) << "VFY: bad signature component '" << sig << "' (missing ';')"; |
| *failure = VERIFY_ERROR_GENERIC; |
| return NULL; |
| } |
| |
| return LookupClassByDescriptor(method, sig.substr(0, end + 1).c_str(), |
| failure); |
| } |
| |
| Class* DexVerifier::LookupSignatureArrayClass(const Method* method, |
| std::string sig, VerifyError* failure) { |
| DCHECK_EQ(sig[0], '['); |
| size_t end = 0; |
| |
| while (sig[end] == '[') |
| end++; |
| |
| if (sig[end] == 'L') { |
| end = sig.find(';'); |
| if (end == std::string::npos) { |
| LOG(ERROR) << "VFY: bad signature component '" << sig |
| << "' (missing ';')"; |
| *failure = VERIFY_ERROR_GENERIC; |
| return NULL; |
| } |
| } |
| |
| return LookupClassByDescriptor(method, sig.substr(0, end + 1).c_str(), |
| failure); |
| } |
| |
| bool DexVerifier::SetTypesFromSignature(VerifierData* vdata, RegType* reg_types) |
| { |
| Method* method = vdata->method_; |
| const DexFile* dex_file = vdata->dex_file_; |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| UninitInstanceMap* uninit_map = vdata->uninit_map_.get(); |
| |
| int arg_start = code_item->registers_size_ - code_item->ins_size_; |
| int expected_args = code_item->ins_size_; /* long/double count as two */ |
| int actual_args = 0; |
| |
| DCHECK_GE(arg_start, 0); /* should have been verified earlier */ |
| |
| /* |
| * Include the "this" pointer. |
| */ |
| if (!method->IsStatic()) { |
| /* |
| * If this is a constructor for a class other than java.lang.Object, |
| * mark the first ("this") argument as uninitialized. This restricts |
| * field access until the superclass constructor is called. |
| */ |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); |
| Class* klass_object = class_linker->FindSystemClass("Ljava/lang/Object;"); |
| if (IsInitMethod(method) && method->GetDeclaringClass() != klass_object) { |
| int idx = SetUninitInstance(uninit_map, kUninitThisArgAddr, |
| method->GetDeclaringClass()); |
| DCHECK_EQ(idx, 0); |
| reg_types[arg_start + actual_args] = RegTypeFromUninitIndex(idx); |
| } else { |
| reg_types[arg_start + actual_args] = |
| RegTypeFromClass(method->GetDeclaringClass()); |
| } |
| actual_args++; |
| } |
| |
| const DexFile::ProtoId& proto_id = |
| dex_file->GetProtoId(method->GetProtoIdx()); |
| DexFile::ParameterIterator iterator(*dex_file, proto_id); |
| VerifyError failure = VERIFY_ERROR_NONE; |
| |
| for (; iterator.HasNext(); iterator.Next()) { |
| const char* descriptor = iterator.GetDescriptor(); |
| |
| if (descriptor == NULL) { |
| break; |
| } |
| |
| if (actual_args >= expected_args) { |
| LOG(ERROR) << "VFY: expected " << expected_args << " args, found more (" |
| << descriptor << ")"; |
| return false; |
| } |
| |
| switch (*descriptor) { |
| case 'L': |
| case '[': |
| /* |
| * We assume that reference arguments are initialized. The only way |
| * it could be otherwise (assuming the caller was verified) is if |
| * the current method is <init>, but in that case it's effectively |
| * considered initialized the instant we reach here (in the sense |
| * that we can return without doing anything or call virtual methods). |
| */ |
| { |
| Class* klass = |
| LookupClassByDescriptor(method, descriptor, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| return false; |
| reg_types[arg_start + actual_args] = RegTypeFromClass(klass); |
| } |
| actual_args++; |
| break; |
| case 'Z': |
| reg_types[arg_start + actual_args] = kRegTypeBoolean; |
| actual_args++; |
| break; |
| case 'C': |
| reg_types[arg_start + actual_args] = kRegTypeChar; |
| actual_args++; |
| break; |
| case 'B': |
| reg_types[arg_start + actual_args] = kRegTypeByte; |
| actual_args++; |
| break; |
| case 'I': |
| reg_types[arg_start + actual_args] = kRegTypeInteger; |
| actual_args++; |
| break; |
| case 'S': |
| reg_types[arg_start + actual_args] = kRegTypeShort; |
| actual_args++; |
| break; |
| case 'F': |
| reg_types[arg_start + actual_args] = kRegTypeFloat; |
| actual_args++; |
| break; |
| case 'D': |
| reg_types[arg_start + actual_args] = kRegTypeDoubleLo; |
| reg_types[arg_start + actual_args +1] = kRegTypeDoubleHi; |
| actual_args += 2; |
| break; |
| case 'J': |
| reg_types[arg_start + actual_args] = kRegTypeLongLo; |
| reg_types[arg_start + actual_args +1] = kRegTypeLongHi; |
| actual_args += 2; |
| break; |
| default: |
| LOG(ERROR) << "VFY: unexpected signature type char '" << descriptor |
| << "'"; |
| return false; |
| } |
| } |
| |
| if (actual_args != expected_args) { |
| LOG(ERROR) << "VFY: expected " << expected_args << " args, found " |
| << actual_args; |
| return false; |
| } |
| |
| const char* descriptor = dex_file->GetReturnTypeDescriptor(proto_id); |
| |
| /* |
| * Validate return type. We don't do the type lookup; just want to make |
| * sure that it has the right format. Only major difference from the |
| * method argument format is that 'V' is supported. |
| */ |
| switch (*descriptor) { |
| case 'I': |
| case 'C': |
| case 'S': |
| case 'B': |
| case 'Z': |
| case 'V': |
| case 'F': |
| case 'D': |
| case 'J': |
| if (*(descriptor + 1) != '\0') |
| return false; |
| break; |
| case '[': |
| /* single/multi, object/primitive */ |
| while (*++descriptor == '[') |
| ; |
| if (*descriptor == 'L') { |
| while (*++descriptor != ';' && *descriptor != '\0') |
| ; |
| if (*descriptor != ';') |
| return false; |
| } else { |
| if (*(descriptor+1) != '\0') |
| return false; |
| } |
| break; |
| case 'L': |
| /* could be more thorough here, but shouldn't be required */ |
| while (*++descriptor != ';' && *descriptor != '\0') |
| ; |
| if (*descriptor != ';') |
| return false; |
| break; |
| default: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int DexVerifier::SetUninitInstance(UninitInstanceMap* uninit_map, int addr, |
| Class* klass) { |
| int idx; |
| DCHECK(klass != NULL); |
| |
| /* TODO: binary search when num_entries > 8 */ |
| for (idx = uninit_map->num_entries_ - 1; idx >= 0; idx--) { |
| if (uninit_map->map_[idx].addr_ == addr) { |
| if (uninit_map->map_[idx].klass_ != NULL && |
| uninit_map->map_[idx].klass_ != klass) { |
| LOG(ERROR) << "VFY: addr " << addr << " already set to " |
| << (int) uninit_map->map_[idx].klass_ << ", not setting to " |
| << (int) klass; |
| return -1; // already set to something else?? |
| } |
| uninit_map->map_[idx].klass_ = klass; |
| return idx; |
| } |
| } |
| |
| LOG(FATAL) << "VFY: addr " << addr << " not found in uninit map"; |
| return -1; |
| } |
| |
| bool DexVerifier::CodeFlowVerifyMethod(VerifierData* vdata, |
| RegisterTable* reg_table) { |
| const Method* method = vdata->method_; |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| InsnFlags* insn_flags = vdata->insn_flags_.get(); |
| const uint16_t* insns = code_item->insns_; |
| uint32_t insns_size = code_item->insns_size_; |
| size_t insn_idx, start_guess; |
| |
| /* Begin by marking the first instruction as "changed". */ |
| InsnSetChanged(insn_flags, 0, true); |
| |
| start_guess = 0; |
| |
| /* Continue until no instructions are marked "changed". */ |
| while (true) { |
| /* |
| * Find the first marked one. Use "start_guess" as a way to find |
| * one quickly. |
| */ |
| for (insn_idx = start_guess; insn_idx < insns_size; insn_idx++) { |
| if (InsnIsChanged(insn_flags, insn_idx)) |
| break; |
| } |
| |
| if (insn_idx == insns_size) { |
| if (start_guess != 0) { |
| /* try again, starting from the top */ |
| start_guess = 0; |
| continue; |
| } else { |
| /* all flags are clear */ |
| break; |
| } |
| } |
| |
| /* |
| * We carry the working set of registers from instruction to instruction. |
| * If this address can be the target of a branch (or throw) instruction, |
| * or if we're skipping around chasing "changed" flags, we need to load |
| * the set of registers from the table. |
| * |
| * Because we always prefer to continue on to the next instruction, we |
| * should never have a situation where we have a stray "changed" flag set |
| * on an instruction that isn't a branch target. |
| */ |
| if (InsnIsBranchTarget(insn_flags, insn_idx)) { |
| RegisterLine* work_line = ®_table->work_line_; |
| CopyLineFromTable(work_line, reg_table, insn_idx); |
| } else { |
| #ifndef NDEBUG |
| /* |
| * Sanity check: retrieve the stored register line (assuming |
| * a full table) and make sure it actually matches. |
| */ |
| RegisterLine* register_line = GetRegisterLine(reg_table, insn_idx); |
| if (register_line->reg_types_.get() != NULL && CompareLineToTable(reg_table, |
| insn_idx, ®_table->work_line_) != 0) { |
| Class* klass = method->GetDeclaringClass(); |
| LOG(ERROR) << "HUH? work_line diverged in " |
| << klass->GetDescriptor()->ToModifiedUtf8() << "." |
| << method->GetName()->ToModifiedUtf8() << " " |
| << method->GetSignature()->ToModifiedUtf8(); |
| } |
| #endif |
| } |
| |
| if (!CodeFlowVerifyInstruction(vdata, reg_table, insn_idx, &start_guess)) { |
| Class* klass = method->GetDeclaringClass(); |
| LOG(ERROR) << "VFY: failure to verify " |
| << klass->GetDescriptor()->ToModifiedUtf8() << "." |
| << method->GetName()->ToModifiedUtf8() << " " |
| << method->GetSignature()->ToModifiedUtf8(); |
| return false; |
| } |
| |
| /* Clear "changed" and mark as visited. */ |
| InsnSetVisited(insn_flags, insn_idx, true); |
| InsnSetChanged(insn_flags, insn_idx, false); |
| } |
| |
| if (DEAD_CODE_SCAN && ((method->GetAccessFlags() & kAccWritable) == 0)) { |
| /* |
| * Scan for dead code. There's nothing "evil" about dead code |
| * (besides the wasted space), but it indicates a flaw somewhere |
| * down the line, possibly in the verifier. |
| * |
| * If we've substituted "always throw" instructions into the stream, |
| * we are almost certainly going to have some dead code. |
| */ |
| int dead_start = -1; |
| for (insn_idx = 0; insn_idx < insns_size; |
| insn_idx += InsnGetWidth(insn_flags, insn_idx)) { |
| /* |
| * Switch-statement data doesn't get "visited" by scanner. It |
| * may or may not be preceded by a padding NOP (for alignment). |
| */ |
| if (insns[insn_idx] == Instruction::kPackedSwitchSignature || |
| insns[insn_idx] == Instruction::kSparseSwitchSignature || |
| insns[insn_idx] == Instruction::kArrayDataSignature || |
| (insns[insn_idx] == Instruction::NOP && |
| (insns[insn_idx + 1] == Instruction::kPackedSwitchSignature || |
| insns[insn_idx + 1] == Instruction::kSparseSwitchSignature || |
| insns[insn_idx + 1] == Instruction::kArrayDataSignature))) { |
| InsnSetVisited(insn_flags, insn_idx, true); |
| } |
| |
| if (!InsnIsVisited(insn_flags, insn_idx)) { |
| if (dead_start < 0) |
| dead_start = insn_idx; |
| } else if (dead_start >= 0) { |
| Class* klass = method->GetDeclaringClass(); |
| LOG(INFO) << "VFY: dead code 0x" << std::hex << dead_start << "-" |
| << insn_idx - 1 << std::dec << " in " |
| << klass->GetDescriptor()->ToModifiedUtf8() << "." |
| << method->GetName()->ToModifiedUtf8() << " " |
| << method->GetSignature()->ToModifiedUtf8(); |
| dead_start = -1; |
| } |
| } |
| if (dead_start >= 0) { |
| Class* klass = method->GetDeclaringClass(); |
| LOG(INFO) << "VFY: dead code 0x" << std::hex << dead_start << "-" |
| << insn_idx - 1 << std::dec << " in " |
| << klass->GetDescriptor()->ToModifiedUtf8() << "." |
| << method->GetName()->ToModifiedUtf8() << " " |
| << method->GetSignature()->ToModifiedUtf8(); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool DexVerifier::CodeFlowVerifyInstruction(VerifierData* vdata, |
| RegisterTable* reg_table, uint32_t insn_idx, size_t* start_guess) { |
| const Method* method = vdata->method_; |
| Class* klass = method->GetDeclaringClass(); |
| const DexFile::CodeItem* code_item = vdata->code_item_; |
| InsnFlags* insn_flags = vdata->insn_flags_.get(); |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); |
| UninitInstanceMap* uninit_map = vdata->uninit_map_.get(); |
| const ClassLoader* class_loader = |
| method->GetDeclaringClass()->GetClassLoader(); |
| const uint16_t* insns = code_item->insns_ + insn_idx; |
| uint32_t insns_size = code_item->insns_size_; |
| uint32_t registers_size = code_item->registers_size_; |
| |
| #ifdef VERIFIER_STATS |
| if (InsnIsVisited(insn_flags, insn_idx)) { |
| gDvm.verifierStats.instrsReexamined++; |
| } else { |
| gDvm.verifierStats.instrsExamined++; |
| } |
| #endif |
| |
| /* |
| * Once we finish decoding the instruction, we need to figure out where |
| * we can go from here. There are three possible ways to transfer |
| * control to another statement: |
| * |
| * (1) Continue to the next instruction. Applies to all but |
| * unconditional branches, method returns, and exception throws. |
| * (2) Branch to one or more possible locations. Applies to branches |
| * and switch statements. |
| * (3) Exception handlers. Applies to any instruction that can |
| * throw an exception that is handled by an encompassing "try" |
| * block. |
| * |
| * We can also return, in which case there is no successor instruction |
| * from this point. |
| * |
| * The behavior can be determined from the OpcodeFlags. |
| */ |
| RegisterLine* work_line = ®_table->work_line_; |
| const DexFile* dex_file = vdata->dex_file_; |
| const byte* ptr = reinterpret_cast<const byte*>(insns); |
| const Instruction* inst = Instruction::At(ptr); |
| Instruction::DecodedInstruction dec_insn(inst); |
| int opcode_flag = inst->Flag(); |
| |
| Class* res_class; |
| int32_t branch_target = 0; |
| RegType tmp_type; |
| bool just_set_result = false; |
| VerifyError failure = VERIFY_ERROR_NONE; |
| |
| /* |
| * Make a copy of the previous register state. If the instruction |
| * can throw an exception, we will copy/merge this into the "catch" |
| * address rather than work_line, because we don't want the result |
| * from the "successful" code path (e.g. a check-cast that "improves" |
| * a type) to be visible to the exception handler. |
| */ |
| if ((opcode_flag & Instruction::kThrow) != 0 && |
| InsnIsInTry(insn_flags, insn_idx)) { |
| CopyRegisterLine(®_table->saved_line_, work_line, |
| reg_table->insn_reg_count_plus_); |
| } else { |
| #ifndef NDEBUG |
| memset(reg_table->saved_line_.reg_types_.get(), 0xdd, |
| reg_table->insn_reg_count_plus_ * sizeof(RegType)); |
| #endif |
| } |
| |
| switch (dec_insn.opcode_) { |
| case Instruction::NOP: |
| /* |
| * A "pure" NOP has no effect on anything. Data tables start with |
| * a signature that looks like a NOP; if we see one of these in |
| * the course of executing code then we have a problem. |
| */ |
| if (dec_insn.vA_ != 0) { |
| LOG(ERROR) << "VFY: encountered data table in instruction stream"; |
| failure = VERIFY_ERROR_GENERIC; |
| } |
| break; |
| |
| case Instruction::MOVE: |
| case Instruction::MOVE_FROM16: |
| case Instruction::MOVE_16: |
| CopyRegister1(work_line, dec_insn.vA_, dec_insn.vB_, kTypeCategory1nr, |
| &failure); |
| break; |
| case Instruction::MOVE_WIDE: |
| case Instruction::MOVE_WIDE_FROM16: |
| case Instruction::MOVE_WIDE_16: |
| CopyRegister2(work_line, dec_insn.vA_, dec_insn.vB_, &failure); |
| break; |
| case Instruction::MOVE_OBJECT: |
| case Instruction::MOVE_OBJECT_FROM16: |
| case Instruction::MOVE_OBJECT_16: |
| CopyRegister1(work_line, dec_insn.vA_, dec_insn.vB_, kTypeCategoryRef, |
| &failure); |
| break; |
| |
| /* |
| * The move-result instructions copy data out of a "pseudo-register" |
| * with the results from the last method invocation. In practice we |
| * might want to hold the result in an actual CPU register, so the |
| * Dalvik spec requires that these only appear immediately after an |
| * invoke or filled-new-array. |
| * |
| * These calls invalidate the "result" register. (This is now |
| * redundant with the reset done below, but it can make the debug info |
| * easier to read in some cases.) |
| */ |
| case Instruction::MOVE_RESULT: |
| CopyResultRegister1(work_line, registers_size, dec_insn.vA_, |
| kTypeCategory1nr, &failure); |
| break; |
| case Instruction::MOVE_RESULT_WIDE: |
| CopyResultRegister2(work_line, registers_size, dec_insn.vA_, &failure); |
| break; |
| case Instruction::MOVE_RESULT_OBJECT: |
| CopyResultRegister1(work_line, registers_size, dec_insn.vA_, |
| kTypeCategoryRef, &failure); |
| break; |
| |
| case Instruction::MOVE_EXCEPTION: |
| /* |
| * This statement can only appear as the first instruction in an |
| * exception handler (though not all exception handlers need to |
| * have one of these). We verify that as part of extracting the |
| * exception type from the catch block list. |
| * |
| * "res_class" will hold the closest common superclass of all |
| * exceptions that can be handled here. |
| */ |
| res_class = GetCaughtExceptionType(vdata, insn_idx, &failure); |
| if (res_class == NULL) { |
| DCHECK(failure != VERIFY_ERROR_NONE); |
| } else { |
| SetRegisterType(work_line, dec_insn.vA_, RegTypeFromClass(res_class)); |
| } |
| break; |
| |
| case Instruction::RETURN_VOID: |
| if (!CheckConstructorReturn(method, work_line, registers_size)) { |
| failure = VERIFY_ERROR_GENERIC; |
| } else if (GetMethodReturnType(dex_file, method) != kRegTypeUnknown) { |
| LOG(ERROR) << "VFY: return-void not expected"; |
| failure = VERIFY_ERROR_GENERIC; |
| } |
| break; |
| case Instruction::RETURN: |
| if (!CheckConstructorReturn(method, work_line, registers_size)) { |
| failure = VERIFY_ERROR_GENERIC; |
| } else { |
| /* check the method signature */ |
| RegType return_type = GetMethodReturnType(dex_file, method); |
| CheckTypeCategory(return_type, kTypeCategory1nr, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| LOG(ERROR) << "VFY: return-1nr not expected"; |
| |
| /* |
| * compiler may generate synthetic functions that write byte |
| * values into boolean fields. Also, it may use integer values |
| * for boolean, byte, short, and character return types. |
| */ |
| RegType src_type = GetRegisterType(work_line, dec_insn.vA_); |
| if ((return_type == kRegTypeBoolean && src_type == kRegTypeByte) || |
| ((return_type == kRegTypeBoolean || return_type == kRegTypeByte || |
| return_type == kRegTypeShort || return_type == kRegTypeChar) && |
| src_type == kRegTypeInteger)) |
| return_type = src_type; |
| |
| /* check the register contents */ |
| VerifyRegisterType(work_line, dec_insn.vA_, return_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) { |
| LOG(ERROR) << "VFY: return-1nr on invalid register v" << dec_insn.vA_; |
| } |
| } |
| break; |
| case Instruction::RETURN_WIDE: |
| if (!CheckConstructorReturn(method, work_line, registers_size)) { |
| failure = VERIFY_ERROR_GENERIC; |
| } else { |
| RegType return_type; |
| |
| /* check the method signature */ |
| return_type = GetMethodReturnType(dex_file, method); |
| CheckTypeCategory(return_type, kTypeCategory2, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| LOG(ERROR) << "VFY: return-wide not expected"; |
| |
| /* check the register contents */ |
| VerifyRegisterType(work_line, dec_insn.vA_, return_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) { |
| LOG(ERROR) << "VFY: return-wide on invalid register pair v" |
| << dec_insn.vA_; |
| } |
| } |
| break; |
| case Instruction::RETURN_OBJECT: |
| if (!CheckConstructorReturn(method, work_line, registers_size)) { |
| failure = VERIFY_ERROR_GENERIC; |
| } else { |
| RegType return_type = GetMethodReturnType(dex_file, method); |
| CheckTypeCategory(return_type, kTypeCategoryRef, &failure); |
| if (failure != VERIFY_ERROR_NONE) { |
| LOG(ERROR) << "VFY: return-object not expected"; |
| break; |
| } |
| |
| /* return_type is the *expected* return type, not register value */ |
| DCHECK(return_type != kRegTypeZero); |
| DCHECK(!RegTypeIsUninitReference(return_type)); |
| |
| /* |
| * Verify that the reference in vAA is an instance of the type |
| * in "return_type". The Zero type is allowed here. If the |
| * method is declared to return an interface, then any |
| * initialized reference is acceptable. |
| * |
| * Note GetClassFromRegister fails if the register holds an |
| * uninitialized reference, so we do not allow them to be |
| * returned. |
| */ |
| Class* decl_class = RegTypeInitializedReferenceToClass(return_type); |
| res_class = GetClassFromRegister(work_line, dec_insn.vA_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| if (res_class != NULL) { |
| if (!decl_class->IsInterface() && |
| !decl_class->IsAssignableFrom(res_class)) { |
| LOG(ERROR) << "VFY: returning " << std::hex |
| << res_class->GetDescriptor()->ToModifiedUtf8() |
| << " (cl=0x" << (int) res_class->GetClassLoader() |
| << "), declared " |
| << decl_class->GetDescriptor()->ToModifiedUtf8() |
| << " (cl=0x" << (int) decl_class->GetClassLoader() |
| << ")" << std::dec; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| } |
| } |
| break; |
| |
| case Instruction::CONST_4: |
| case Instruction::CONST_16: |
| case Instruction::CONST: |
| /* could be boolean, int, float, or a null reference */ |
| SetRegisterType(work_line, dec_insn.vA_, |
| DetermineCat1Const((int32_t) dec_insn.vB_)); |
| break; |
| case Instruction::CONST_HIGH16: |
| /* could be boolean, int, float, or a null reference */ |
| SetRegisterType(work_line, dec_insn.vA_, |
| DetermineCat1Const((int32_t) dec_insn.vB_ << 16)); |
| break; |
| case Instruction::CONST_WIDE_16: |
| case Instruction::CONST_WIDE_32: |
| case Instruction::CONST_WIDE: |
| case Instruction::CONST_WIDE_HIGH16: |
| /* could be long or double; resolved upon use */ |
| SetRegisterType(work_line, dec_insn.vA_, kRegTypeConstLo); |
| break; |
| case Instruction::CONST_STRING: |
| case Instruction::CONST_STRING_JUMBO: |
| SetRegisterType(work_line, dec_insn.vA_, RegTypeFromClass( |
| class_linker->FindSystemClass("Ljava/lang/String;"))); |
| break; |
| case Instruction::CONST_CLASS: |
| /* make sure we can resolve the class; access check is important */ |
| res_class = ResolveClassAndCheckAccess(dex_file, dec_insn.vB_, klass, &failure); |
| if (res_class == NULL) { |
| const char* bad_class_desc = dex_file->dexStringByTypeIdx(dec_insn.vB_); |
| LOG(ERROR) << "VFY: unable to resolve const-class " << dec_insn.vB_ |
| << " (" << bad_class_desc << ") in " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| if (failure != VERIFY_ERROR_NONE) { |
| break; |
| } |
| } |
| SetRegisterType(work_line, dec_insn.vA_, RegTypeFromClass( |
| class_linker->FindSystemClass("Ljava/lang/Class;"))); |
| break; |
| |
| case Instruction::MONITOR_ENTER: |
| HandleMonitorEnter(work_line, dec_insn.vA_, insn_idx, &failure); |
| break; |
| case Instruction::MONITOR_EXIT: |
| /* |
| * monitor-exit instructions are odd. They can throw exceptions, |
| * but when they do they act as if they succeeded and the PC is |
| * pointing to the following instruction. (This behavior goes back |
| * to the need to handle asynchronous exceptions, a now-deprecated |
| * feature that Dalvik doesn't support.) |
| * |
| * In practice we don't need to worry about this. The only |
| * exceptions that can be thrown from monitor-exit are for a |
| * null reference and -exit without a matching -enter. If the |
| * structured locking checks are working, the former would have |
| * failed on the -enter instruction, and the latter is impossible. |
| * |
| * This is fortunate, because issue 3221411 prevents us from |
| * chasing the "can throw" path when monitor verification is |
| * enabled. If we can fully verify the locking we can ignore |
| * some catch blocks (which will show up as "dead" code when |
| * we skip them here); if we can't, then the code path could be |
| * "live" so we still need to check it. |
| */ |
| if (work_line->monitor_entries_.get() != NULL) |
| opcode_flag &= ~Instruction::kThrow; |
| HandleMonitorExit(work_line, dec_insn.vA_, insn_idx, &failure); |
| break; |
| |
| case Instruction::CHECK_CAST: |
| /* |
| * If this instruction succeeds, we will promote register vA to |
| * the type in vB. (This could be a demotion -- not expected, so |
| * we don't try to address it.) |
| * |
| * If it fails, an exception is thrown, which we deal with later |
| * by ignoring the update to dec_insn.vA_ when branching to a handler. |
| */ |
| res_class = ResolveClassAndCheckAccess(dex_file, dec_insn.vB_, klass, &failure); |
| if (res_class == NULL) { |
| const char* bad_class_desc = dex_file->dexStringByTypeIdx(dec_insn.vB_); |
| LOG(ERROR) << "VFY: unable to resolve check-cast " << dec_insn.vB_ |
| << " (" << bad_class_desc << ") in " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| if (failure != VERIFY_ERROR_NONE) { |
| break; |
| } |
| /* if the class is unresolvable, treat it as an object */ |
| res_class = class_linker->FindClass("Ljava/lang/Object;", class_loader); |
| } |
| RegType orig_type; |
| |
| orig_type = GetRegisterType(work_line, dec_insn.vA_); |
| if (!RegTypeIsReference(orig_type)) { |
| LOG(ERROR) << "VFY: check-cast on non-reference in v" << dec_insn.vA_; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| SetRegisterType(work_line, dec_insn.vA_, RegTypeFromClass(res_class)); |
| break; |
| case Instruction::INSTANCE_OF: |
| /* make sure we're checking a reference type */ |
| tmp_type = GetRegisterType(work_line, dec_insn.vB_); |
| if (!RegTypeIsReference(tmp_type)) { |
| LOG(ERROR) << "VFY: vB not a reference (" << tmp_type << ")"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| /* make sure we can resolve the class; access check is important */ |
| res_class = ResolveClassAndCheckAccess(dex_file, dec_insn.vC_, klass, &failure); |
| if (res_class == NULL) { |
| const char* bad_class_desc = dex_file->dexStringByTypeIdx(dec_insn.vC_); |
| LOG(ERROR) << "VFY: unable to resolve instanceof " << dec_insn.vC_ |
| << " (" << bad_class_desc << ") in " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| if (failure != VERIFY_ERROR_NONE) { |
| break; |
| } |
| } |
| /* result is boolean */ |
| SetRegisterType(work_line, dec_insn.vA_, kRegTypeBoolean); |
| break; |
| |
| case Instruction::ARRAY_LENGTH: |
| res_class = GetClassFromRegister(work_line, dec_insn.vB_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| if (res_class != NULL && !res_class->IsArrayClass()) { |
| LOG(ERROR) << "VFY: array-length on non-array"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| SetRegisterType(work_line, dec_insn.vA_, kRegTypeInteger); |
| break; |
| |
| case Instruction::NEW_INSTANCE: |
| { |
| res_class = ResolveClassAndCheckAccess(dex_file, dec_insn.vB_, klass, &failure); |
| if (res_class == NULL) { |
| const char* bad_class_desc = dex_file->dexStringByTypeIdx(dec_insn.vB_); |
| LOG(ERROR) << "VFY: unable to resolve new-instance " << dec_insn.vB_ |
| << " (" << bad_class_desc << ") in " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| if (failure != VERIFY_ERROR_NONE) { |
| break; |
| } |
| /* if the class is unresolvable, treat it as an object */ |
| res_class = class_linker->FindClass("Ljava/lang/Object;", class_loader); |
| } |
| RegType uninit_type; |
| |
| /* can't create an instance of an interface or abstract class */ |
| if (res_class->IsAbstract() || res_class->IsInterface()) { |
| LOG(ERROR) << "VFY: new-instance on interface or abstract class" |
| << res_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_INSTANTIATION; |
| break; |
| } |
| |
| /* add resolved class to uninit map if not already there */ |
| int uidx = SetUninitInstance(uninit_map, insn_idx, res_class); |
| DCHECK_GE(uidx, 0); |
| uninit_type = RegTypeFromUninitIndex(uidx); |
| |
| /* |
| * Any registers holding previous allocations from this address |
| * that have not yet been initialized must be marked invalid. |
| */ |
| MarkUninitRefsAsInvalid(work_line, registers_size, uninit_map, |
| uninit_type); |
| |
| /* add the new uninitialized reference to the register ste */ |
| SetRegisterType(work_line, dec_insn.vA_, uninit_type); |
| break; |
| } |
| case Instruction::NEW_ARRAY: |
| res_class = ResolveClassAndCheckAccess(dex_file, dec_insn.vC_, klass, &failure); |
| if (res_class == NULL) { |
| const char* bad_class_desc = dex_file->dexStringByTypeIdx(dec_insn.vC_); |
| LOG(ERROR) << "VFY: unable to resolve new-array " << dec_insn.vC_ |
| << " (" << bad_class_desc << ") in " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| if (failure != VERIFY_ERROR_NONE) { |
| break; |
| } |
| /* if the class is unresolvable, treat it as an object array */ |
| res_class = class_linker->FindClass("[Ljava/lang/Object;", class_loader); |
| } |
| if (!res_class->IsArrayClass()) { |
| LOG(ERROR) << "VFY: new-array on non-array class"; |
| failure = VERIFY_ERROR_GENERIC; |
| } else { |
| /* make sure "size" register is valid type */ |
| VerifyRegisterType(work_line, dec_insn.vB_, kRegTypeInteger, &failure); |
| /* set register type to array class */ |
| SetRegisterType(work_line, dec_insn.vA_, RegTypeFromClass(res_class)); |
| } |
| break; |
| case Instruction::FILLED_NEW_ARRAY: |
| case Instruction::FILLED_NEW_ARRAY_RANGE: |
| res_class = ResolveClassAndCheckAccess(dex_file, dec_insn.vB_, klass, &failure); |
| if (res_class == NULL) { |
| const char* bad_class_desc = dex_file->dexStringByTypeIdx(dec_insn.vB_); |
| LOG(ERROR) << "VFY: unable to resolve filled-array " << dec_insn.vB_ |
| << " (" << bad_class_desc << ") in " |
| << klass->GetDescriptor()->ToModifiedUtf8(); |
| if (failure != VERIFY_ERROR_NONE) { |
| break; |
| } |
| /* if the class is unresolvable, treat it as an object array */ |
| res_class = class_linker->FindClass("[Ljava/lang/Object;", class_loader); |
| } |
| if (!res_class->IsArrayClass()) { |
| LOG(ERROR) << "VFY: filled-new-array on non-array class"; |
| failure = VERIFY_ERROR_GENERIC; |
| } else { |
| bool is_range = (dec_insn.opcode_ == Instruction::FILLED_NEW_ARRAY_RANGE); |
| |
| /* check the arguments to the instruction */ |
| VerifyFilledNewArrayRegs(method, work_line, &dec_insn, res_class, |
| is_range, &failure); |
| /* filled-array result goes into "result" register */ |
| SetResultRegisterType(work_line, registers_size, |
| RegTypeFromClass(res_class)); |
| just_set_result = true; |
| } |
| break; |
| |
| case Instruction::CMPL_FLOAT: |
| case Instruction::CMPG_FLOAT: |
| VerifyRegisterType(work_line, dec_insn.vB_, kRegTypeFloat, &failure); |
| VerifyRegisterType(work_line, dec_insn.vC_, kRegTypeFloat, &failure); |
| SetRegisterType(work_line, dec_insn.vA_, kRegTypeBoolean); |
| break; |
| case Instruction::CMPL_DOUBLE: |
| case Instruction::CMPG_DOUBLE: |
| VerifyRegisterType(work_line, dec_insn.vB_, kRegTypeDoubleLo, &failure); |
| VerifyRegisterType(work_line, dec_insn.vC_, kRegTypeDoubleLo, &failure); |
| SetRegisterType(work_line, dec_insn.vA_, kRegTypeBoolean); |
| break; |
| case Instruction::CMP_LONG: |
| VerifyRegisterType(work_line, dec_insn.vB_, kRegTypeLongLo, &failure); |
| VerifyRegisterType(work_line, dec_insn.vC_, kRegTypeLongLo, &failure); |
| SetRegisterType(work_line, dec_insn.vA_, kRegTypeBoolean); |
| break; |
| |
| case Instruction::THROW: |
| res_class = GetClassFromRegister(work_line, dec_insn.vA_, &failure); |
| if (failure == VERIFY_ERROR_NONE && res_class != NULL) { |
| Class* throwable_class = |
| class_linker->FindSystemClass("Ljava/lang/Throwable;"); |
| if (!throwable_class->IsAssignableFrom(res_class)) { |
| LOG(ERROR) << "VFY: thrown class " |
| << res_class->GetDescriptor()->ToModifiedUtf8() |
| << " not instanceof Throwable", |
| failure = VERIFY_ERROR_GENERIC; |
| } |
| } |
| break; |
| |
| case Instruction::GOTO: |
| case Instruction::GOTO_16: |
| case Instruction::GOTO_32: |
| /* no effect on or use of registers */ |
| break; |
| |
| case Instruction::PACKED_SWITCH: |
| case Instruction::SPARSE_SWITCH: |
| /* verify that vAA is an integer, or can be converted to one */ |
| VerifyRegisterType(work_line, dec_insn.vA_, kRegTypeInteger, &failure); |
| break; |
| |
| case Instruction::FILL_ARRAY_DATA: |
| { |
| RegType value_type; |
| const uint16_t *array_data; |
| uint16_t elem_width; |
| |
| /* Similar to the verification done for APUT */ |
| res_class = GetClassFromRegister(work_line, dec_insn.vA_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| /* res_class can be null if the reg type is Zero */ |
| if (res_class == NULL) |
| break; |
| |
| Class::PrimitiveType prim_type = |
| res_class->GetComponentType()->GetPrimitiveType(); |
| if (!res_class->IsArrayClass() || |
| prim_type == Class::kPrimNot || prim_type == Class::kPrimVoid) { |
| LOG(ERROR) << "VFY: invalid fill-array-data on " << |
| res_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| value_type = PrimitiveTypeToRegType(prim_type); |
| DCHECK(value_type != kRegTypeUnknown); |
| |
| /* |
| * Now verify if the element width in the table matches the element |
| * width declared in the array |
| */ |
| array_data = insns + (insns[1] | (((int32_t) insns[2]) << 16)); |
| if (array_data[0] != Instruction::kArrayDataSignature) { |
| LOG(ERROR) << "VFY: invalid magic for array-data"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| switch (prim_type) { |
| case Class::kPrimBoolean: |
| case Class::kPrimByte: |
| elem_width = 1; |
| break; |
| case Class::kPrimChar: |
| case Class::kPrimShort: |
| elem_width = 2; |
| break; |
| case Class::kPrimFloat: |
| case Class::kPrimInt: |
| elem_width = 4; |
| break; |
| case Class::kPrimDouble: |
| case Class::kPrimLong: |
| elem_width = 8; |
| break; |
| default: |
| elem_width = 0; |
| break; |
| } |
| |
| /* |
| * Since we don't compress the data in Dex, expect to see equal |
| * width of data stored in the table and expected from the array |
| * class. |
| */ |
| if (array_data[1] != elem_width) { |
| LOG(ERROR) << "VFY: array-data size mismatch (" << array_data[1] |
| << " vs " << elem_width << ")"; |
| failure = VERIFY_ERROR_GENERIC; |
| } |
| } |
| break; |
| |
| case Instruction::IF_EQ: |
| case Instruction::IF_NE: |
| { |
| RegType type1, type2; |
| |
| type1 = GetRegisterType(work_line, dec_insn.vA_); |
| type2 = GetRegisterType(work_line, dec_insn.vB_); |
| |
| /* both references? */ |
| if (RegTypeIsReference(type1) && RegTypeIsReference(type2)) |
| break; |
| |
| /* both category-1nr? */ |
| CheckTypeCategory(type1, kTypeCategory1nr, &failure); |
| CheckTypeCategory(type2, kTypeCategory1nr, &failure); |
| if (failure != VERIFY_ERROR_NONE) { |
| LOG(ERROR) << "VFY: args to if-eq/if-ne must both be refs or cat1"; |
| break; |
| } |
| } |
| break; |
| case Instruction::IF_LT: |
| case Instruction::IF_GE: |
| case Instruction::IF_GT: |
| case Instruction::IF_LE: |
| tmp_type = GetRegisterType(work_line, dec_insn.vA_); |
| CheckTypeCategory(tmp_type, kTypeCategory1nr, &failure); |
| if (failure != VERIFY_ERROR_NONE) { |
| LOG(ERROR) << "VFY: args to 'if' must be cat-1nr"; |
| break; |
| } |
| tmp_type = GetRegisterType(work_line, dec_insn.vB_); |
| CheckTypeCategory(tmp_type, kTypeCategory1nr, &failure); |
| if (failure != VERIFY_ERROR_NONE) { |
| LOG(ERROR) << "VFY: args to 'if' must be cat-1nr"; |
| break; |
| } |
| break; |
| case Instruction::IF_EQZ: |
| case Instruction::IF_NEZ: |
| tmp_type = GetRegisterType(work_line, dec_insn.vA_); |
| if (RegTypeIsReference(tmp_type)) |
| break; |
| CheckTypeCategory(tmp_type, kTypeCategory1nr, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| LOG(ERROR) << "VFY: expected cat-1 arg to if"; |
| break; |
| case Instruction::IF_LTZ: |
| case Instruction::IF_GEZ: |
| case Instruction::IF_GTZ: |
| case Instruction::IF_LEZ: |
| tmp_type = GetRegisterType(work_line, dec_insn.vA_); |
| CheckTypeCategory(tmp_type, kTypeCategory1nr, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| LOG(ERROR) << "VFY: expected cat-1 arg to if"; |
| break; |
| |
| case Instruction::AGET: |
| tmp_type = kRegTypeConstInteger; |
| goto aget_1nr_common; |
| case Instruction::AGET_BOOLEAN: |
| tmp_type = kRegTypeBoolean; |
| goto aget_1nr_common; |
| case Instruction::AGET_BYTE: |
| tmp_type = kRegTypeByte; |
| goto aget_1nr_common; |
| case Instruction::AGET_CHAR: |
| tmp_type = kRegTypeChar; |
| goto aget_1nr_common; |
| case Instruction::AGET_SHORT: |
| tmp_type = kRegTypeShort; |
| goto aget_1nr_common; |
| aget_1nr_common: |
| { |
| RegType src_type, index_type; |
| |
| index_type = GetRegisterType(work_line, dec_insn.vC_); |
| CheckArrayIndexType(method, index_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| res_class = GetClassFromRegister(work_line, dec_insn.vB_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| if (res_class != NULL) { |
| /* verify the class */ |
| Class::PrimitiveType prim_type = |
| res_class->GetComponentType()->GetPrimitiveType(); |
| if (!res_class->IsArrayClass() || prim_type == Class::kPrimNot) { |
| LOG(ERROR) << "VFY: invalid aget-1nr target " |
| << res_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| /* make sure array type matches instruction */ |
| src_type = PrimitiveTypeToRegType(prim_type); |
| |
| /* differentiate between float and int */ |
| if (src_type == kRegTypeFloat || src_type == kRegTypeInteger) |
| tmp_type = src_type; |
| |
| if (tmp_type != src_type) { |
| LOG(ERROR) << "VFY: invalid aget-1nr, array type=" << src_type |
| << " with inst type=" << tmp_type << " (on " |
| << res_class->GetDescriptor()->ToModifiedUtf8() << ")"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| } |
| SetRegisterType(work_line, dec_insn.vA_, tmp_type); |
| } |
| break; |
| |
| case Instruction::AGET_WIDE: |
| { |
| RegType dst_type, index_type; |
| |
| index_type = GetRegisterType(work_line, dec_insn.vC_); |
| CheckArrayIndexType(method, index_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| res_class = GetClassFromRegister(work_line, dec_insn.vB_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| if (res_class != NULL) { |
| /* verify the class */ |
| Class::PrimitiveType prim_type = |
| res_class->GetComponentType()->GetPrimitiveType(); |
| if (!res_class->IsArrayClass() || prim_type == Class::kPrimNot) { |
| LOG(ERROR) << "VFY: invalid aget-wide target " |
| << res_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| /* try to refine "dst_type" */ |
| switch (prim_type) { |
| case Class::kPrimLong: |
| dst_type = kRegTypeLongLo; |
| break; |
| case Class::kPrimDouble: |
| dst_type = kRegTypeDoubleLo; |
| break; |
| default: |
| LOG(ERROR) << "VFY: invalid aget-wide on " |
| << res_class->GetDescriptor()->ToModifiedUtf8(); |
| dst_type = kRegTypeUnknown; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| } else { |
| /* |
| * Null array ref; this code path will fail at runtime. We |
| * know this is either long or double, so label it const. |
| */ |
| dst_type = kRegTypeConstLo; |
| } |
| SetRegisterType(work_line, dec_insn.vA_, dst_type); |
| } |
| break; |
| |
| case Instruction::AGET_OBJECT: |
| { |
| RegType dst_type, index_type; |
| |
| index_type = GetRegisterType(work_line, dec_insn.vC_); |
| CheckArrayIndexType(method, index_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| /* get the class of the array we're pulling an object from */ |
| res_class = GetClassFromRegister(work_line, dec_insn.vB_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| if (res_class != NULL) { |
| Class* element_class; |
| |
| DCHECK(res_class != NULL); |
| if (!res_class->IsArrayClass()) { |
| LOG(ERROR) << "VFY: aget-object on non-array class"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| DCHECK(res_class->GetComponentType() != NULL); |
| |
| /* |
| * Find the element class. |
| */ |
| element_class = res_class->GetComponentType(); |
| if (element_class->IsPrimitive()) { |
| LOG(ERROR) << "VFY: aget-object on non-ref array class (" |
| << res_class->GetDescriptor()->ToModifiedUtf8() << ")"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| dst_type = RegTypeFromClass(element_class); |
| } else { |
| /* |
| * The array reference is NULL, so the current code path will |
| * throw an exception. For proper merging with later code |
| * paths, and correct handling of "if-eqz" tests on the |
| * result of the array get, we want to treat this as a null |
| * reference. |
| */ |
| dst_type = kRegTypeZero; |
| } |
| SetRegisterType(work_line, dec_insn.vA_, dst_type); |
| } |
| break; |
| case Instruction::APUT: |
| tmp_type = kRegTypeInteger; |
| goto aput_1nr_common; |
| case Instruction::APUT_BOOLEAN: |
| tmp_type = kRegTypeBoolean; |
| goto aput_1nr_common; |
| case Instruction::APUT_BYTE: |
| tmp_type = kRegTypeByte; |
| goto aput_1nr_common; |
| case Instruction::APUT_CHAR: |
| tmp_type = kRegTypeChar; |
| goto aput_1nr_common; |
| case Instruction::APUT_SHORT: |
| tmp_type = kRegTypeShort; |
| goto aput_1nr_common; |
| aput_1nr_common: |
| { |
| RegType src_type, dst_type, index_type; |
| |
| index_type = GetRegisterType(work_line, dec_insn.vC_); |
| CheckArrayIndexType(method, index_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| res_class = GetClassFromRegister(work_line, dec_insn.vB_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| /* res_class can be null if the reg type is Zero */ |
| if (res_class == NULL) |
| break; |
| |
| Class::PrimitiveType prim_type = |
| res_class->GetComponentType()->GetPrimitiveType(); |
| if (!res_class->IsArrayClass() || prim_type == Class::kPrimNot) { |
| LOG(ERROR) << "VFY: invalid aput-1nr on " |
| << res_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| /* verify that instruction matches array */ |
| dst_type = PrimitiveTypeToRegType(prim_type); |
| |
| /* correct if float */ |
| if (dst_type == kRegTypeFloat) |
| tmp_type = kRegTypeFloat; |
| |
| /* make sure the source register has the correct type */ |
| src_type = GetRegisterType(work_line, dec_insn.vA_); |
| if (!CanConvertTo1nr(src_type, tmp_type)) { |
| LOG(ERROR) << "VFY: invalid reg type " << src_type |
| << " on aput instr (need " << tmp_type << ")"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| VerifyRegisterType(work_line, dec_insn.vA_, dst_type, &failure); |
| |
| if (failure != VERIFY_ERROR_NONE || dst_type == kRegTypeUnknown || |
| tmp_type != dst_type) { |
| LOG(ERROR) << "VFY: invalid aput-1nr on " |
| << res_class->GetDescriptor()->ToModifiedUtf8() |
| << " (inst=" << tmp_type << " dst=" << dst_type << ")"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| } |
| break; |
| case Instruction::APUT_WIDE: |
| tmp_type = GetRegisterType(work_line, dec_insn.vC_); |
| CheckArrayIndexType(method, tmp_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| res_class = GetClassFromRegister(work_line, dec_insn.vB_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| if (res_class != NULL) { |
| Class::PrimitiveType prim_type = |
| res_class->GetComponentType()->GetPrimitiveType(); |
| /* verify the class and try to refine "dst_type" */ |
| if (!res_class->IsArrayClass() || prim_type == Class::kPrimNot) |
| { |
| LOG(ERROR) << "VFY: invalid aput-wide on " |
| << res_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| switch (prim_type) { |
| case Class::kPrimLong: |
| VerifyRegisterType(work_line, dec_insn.vA_, kRegTypeLongLo, |
| &failure); |
| break; |
| case Class::kPrimDouble: |
| VerifyRegisterType(work_line, dec_insn.vA_, kRegTypeDoubleLo, |
| &failure); |
| break; |
| default: |
| LOG(ERROR) << "VFY: invalid aput-wide on " |
| << res_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| } |
| break; |
| case Instruction::APUT_OBJECT: |
| tmp_type = GetRegisterType(work_line, dec_insn.vC_); |
| CheckArrayIndexType(method, tmp_type, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| /* get the ref we're storing; Zero is okay, Uninit is not */ |
| res_class = GetClassFromRegister(work_line, dec_insn.vA_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| if (res_class != NULL) { |
| Class* array_class; |
| Class* element_class; |
| |
| /* |
| * Get the array class. If the array ref is null, we won't |
| * have type information (and we'll crash at runtime with a |
| * null pointer exception). |
| */ |
| array_class = GetClassFromRegister(work_line, dec_insn.vB_, &failure); |
| |
| if (array_class != NULL) { |
| /* see if the array holds a compatible type */ |
| if (!array_class->IsArrayClass()) { |
| LOG(ERROR) << "VFY: invalid aput-object on " |
| << array_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| /* |
| * Find the element class. |
| * |
| * All we want to check here is that the element type is a |
| * reference class. We *don't* check instanceof here, because |
| * you can still put a String into a String[] after the latter |
| * has been cast to an Object[]. |
| */ |
| element_class = array_class->GetComponentType(); |
| if (element_class->IsPrimitive()) { |
| LOG(ERROR) << "VFY: invalid aput-object of " |
| << res_class->GetDescriptor()->ToModifiedUtf8() |
| << " into " |
| << array_class->GetDescriptor()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| } |
| } |
| break; |
| |
| case Instruction::IGET: |
| tmp_type = kRegTypeInteger; |
| goto iget_1nr_common; |
| case Instruction::IGET_BOOLEAN: |
| tmp_type = kRegTypeBoolean; |
| goto iget_1nr_common; |
| case Instruction::IGET_BYTE: |
| tmp_type = kRegTypeByte; |
| goto iget_1nr_common; |
| case Instruction::IGET_CHAR: |
| tmp_type = kRegTypeChar; |
| goto iget_1nr_common; |
| case Instruction::IGET_SHORT: |
| tmp_type = kRegTypeShort; |
| goto iget_1nr_common; |
| iget_1nr_common: |
| { |
| Field* inst_field; |
| RegType obj_type, field_type; |
| |
| obj_type = GetRegisterType(work_line, dec_insn.vB_); |
| inst_field = GetInstField(vdata, obj_type, dec_insn.vC_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| /* make sure the field's type is compatible with expectation */ |
| field_type = |
| PrimitiveTypeToRegType(inst_field->GetType()->GetPrimitiveType()); |
| |
| /* correct if float */ |
| if (field_type == kRegTypeFloat) |
| tmp_type = kRegTypeFloat; |
| |
| if (field_type == kRegTypeUnknown || tmp_type != field_type) { |
| Class* inst_field_class = inst_field->GetDeclaringClass(); |
| LOG(ERROR) << "VFY: invalid iget-1nr of " |
| << inst_field_class->GetDescriptor()->ToModifiedUtf8() |
| << "." << inst_field->GetName()->ToModifiedUtf8() |
| << " (inst=" << tmp_type << " field=" << field_type << ")"; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| |
| SetRegisterType(work_line, dec_insn.vA_, tmp_type); |
| } |
| break; |
| case Instruction::IGET_WIDE: |
| { |
| RegType dst_type; |
| Field* inst_field; |
| RegType obj_type; |
| |
| obj_type = GetRegisterType(work_line, dec_insn.vB_); |
| inst_field = GetInstField(vdata, obj_type, dec_insn.vC_, &failure); |
| Class* inst_field_class = inst_field->GetDeclaringClass(); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| /* check the type, which should be prim */ |
| switch (inst_field->GetType()->GetPrimitiveType()) { |
| case Class::kPrimDouble: |
| dst_type = kRegTypeDoubleLo; |
| break; |
| case Class::kPrimLong: |
| dst_type = kRegTypeLongLo; |
| break; |
| default: |
| LOG(ERROR) << "VFY: invalid iget-wide of " |
| << inst_field_class->GetDescriptor()->ToModifiedUtf8() |
| << "." << inst_field->GetName()->ToModifiedUtf8(); |
| dst_type = kRegTypeUnknown; |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| if (failure == VERIFY_ERROR_NONE) { |
| SetRegisterType(work_line, dec_insn.vA_, dst_type); |
| } |
| } |
| break; |
| case Instruction::IGET_OBJECT: |
| { |
| Class* field_class; |
| Field* inst_field; |
| RegType obj_type; |
| |
| obj_type = GetRegisterType(work_line, dec_insn.vB_); |
| inst_field = GetInstField(vdata, obj_type, dec_insn.vC_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| field_class = inst_field->GetType(); |
| if (field_class == NULL) { |
| /* class not found or primitive type */ |
| LOG(ERROR) << "VFY: unable to recover field class from " |
| << inst_field->GetName()->ToModifiedUtf8(); |
| failure = VERIFY_ERROR_GENERIC; |
| break; |
| } |
| if (failure == VERIFY_ERROR_NONE) { |
| DCHECK(!field_class->IsPrimitive()) << PrettyClass(field_class); |
| SetRegisterType(work_line, dec_insn.vA_, |
| RegTypeFromClass(field_class)); |
| } |
| } |
| break; |
| case Instruction::IPUT: |
| tmp_type = kRegTypeInteger; |
| goto iput_1nr_common; |
| case Instruction::IPUT_BOOLEAN: |
| tmp_type = kRegTypeBoolean; |
| goto iput_1nr_common; |
| case Instruction::IPUT_BYTE: |
| tmp_type = kRegTypeByte; |
| goto iput_1nr_common; |
| case Instruction::IPUT_CHAR: |
| tmp_type = kRegTypeChar; |
| goto iput_1nr_common; |
| case Instruction::IPUT_SHORT: |
| tmp_type = kRegTypeShort; |
| goto iput_1nr_common; |
| iput_1nr_common: |
| { |
| RegType src_type, field_type, obj_type; |
| Field* inst_field; |
| |
| obj_type = GetRegisterType(work_line, dec_insn.vB_); |
| inst_field = GetInstField(vdata, obj_type, dec_insn.vC_, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| CheckFinalFieldAccess(method, inst_field, &failure); |
| if (failure != VERIFY_ERROR_NONE) |
| break; |
| |
| /* get type of field we're storing into */ |
| field_type = |
| PrimitiveTypeToRegType(inst_field->GetType()->GetPrimitiveType()); |
| src_type = GetRegisterType(work_line, dec_insn.vA_); |
| |
| /* correct if float */ |
| if (field_type == kRegTypeFloat) |
| tmp_type = kRegTypeFloat; |
| |
| /* |
| * compiler can generate synthetic functions that write byte values |
| * into boolean fields. |
| */ |
| if (tmp_type == kRegTypeBoolean && |