| /* |
| * Copyright (C) 2010 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. |
| */ |
| |
| /* |
| * Liveness analysis for Dalvik bytecode. |
| */ |
| #include "Dalvik.h" |
| #include "analysis/Liveness.h" |
| #include "analysis/CodeVerify.h" |
| |
| static bool processInstruction(VerifierData* vdata, u4 curIdx, |
| BitVector* workBits); |
| static bool markDebugLocals(VerifierData* vdata); |
| static void dumpLiveState(const VerifierData* vdata, u4 curIdx, |
| const BitVector* workBits); |
| |
| |
| /* |
| * Create a table of instruction widths that indicate the width of the |
| * *previous* instruction. The values are copied from the width table |
| * in "vdata", not derived from the instruction stream. |
| * |
| * Caller must free the return value. |
| */ |
| static InstructionWidth* createBackwardWidthTable(VerifierData* vdata) |
| { |
| InstructionWidth* widths; |
| |
| widths = (InstructionWidth*) |
| calloc(vdata->insnsSize, sizeof(InstructionWidth)); |
| if (widths == NULL) |
| return NULL; |
| |
| u4 insnWidth = 0; |
| for (u4 idx = 0; idx < vdata->insnsSize; ) { |
| widths[idx] = insnWidth; |
| insnWidth = dvmInsnGetWidth(vdata->insnFlags, idx); |
| idx += insnWidth; |
| } |
| |
| return widths; |
| } |
| |
| /* |
| * Compute the "liveness" of every register at all GC points. |
| */ |
| bool dvmComputeLiveness(VerifierData* vdata) |
| { |
| const InsnFlags* insnFlags = vdata->insnFlags; |
| InstructionWidth* backwardWidth; |
| VfyBasicBlock* startGuess = NULL; |
| BitVector* workBits; |
| bool result = false; |
| |
| bool verbose = false; //= dvmWantVerboseVerification(vdata->method); |
| if (verbose) { |
| const Method* meth = vdata->method; |
| ALOGI("Computing liveness for %s.%s:%s", |
| meth->clazz->descriptor, meth->name, meth->shorty); |
| } |
| |
| assert(vdata->registerLines != NULL); |
| |
| backwardWidth = createBackwardWidthTable(vdata); |
| if (backwardWidth == NULL) |
| goto bail; |
| |
| /* |
| * Allocate space for intra-block work set. Does not include space |
| * for method result "registers", which aren't visible to the GC. |
| * (They would be made live by move-result and then die on the |
| * instruction immediately before it.) |
| */ |
| workBits = dvmAllocBitVector(vdata->insnRegCount, false); |
| if (workBits == NULL) |
| goto bail; |
| |
| /* |
| * We continue until all blocks have been visited, and no block |
| * requires further attention ("visited" is set and "changed" is |
| * clear). |
| * |
| * TODO: consider creating a "dense" array of basic blocks to make |
| * the walking faster. |
| */ |
| for (int iter = 0;;) { |
| VfyBasicBlock* workBlock = NULL; |
| |
| if (iter++ > 100000) { |
| LOG_VFY_METH(vdata->method, "oh dear"); |
| dvmAbort(); |
| } |
| |
| /* |
| * If a block is marked "changed", we stop and handle it. If it |
| * just hasn't been visited yet, we remember it but keep searching |
| * for one that has been changed. |
| * |
| * The thought here is that this is more likely to let us work |
| * from end to start, which reduces the amount of re-evaluation |
| * required (both by using "changed" as a work list, and by picking |
| * un-visited blocks from the tail end of the method). |
| */ |
| if (startGuess != NULL) { |
| assert(startGuess->changed); |
| workBlock = startGuess; |
| } else { |
| for (u4 idx = 0; idx < vdata->insnsSize; idx++) { |
| VfyBasicBlock* block = vdata->basicBlocks[idx]; |
| if (block == NULL) |
| continue; |
| |
| if (block->changed) { |
| workBlock = block; |
| break; |
| } else if (!block->visited) { |
| workBlock = block; |
| } |
| } |
| } |
| |
| if (workBlock == NULL) { |
| /* all done */ |
| break; |
| } |
| |
| assert(workBlock->changed || !workBlock->visited); |
| startGuess = NULL; |
| |
| /* |
| * Load work bits. These represent the liveness of registers |
| * after the last instruction in the block has finished executing. |
| */ |
| assert(workBlock->liveRegs != NULL); |
| dvmCopyBitVector(workBits, workBlock->liveRegs); |
| if (verbose) { |
| ALOGI("Loaded work bits from last=0x%04x", workBlock->lastAddr); |
| dumpLiveState(vdata, 0xfffd, workBlock->liveRegs); |
| dumpLiveState(vdata, 0xffff, workBits); |
| } |
| |
| /* |
| * Process a single basic block. |
| * |
| * If this instruction is a GC point, we want to save the result |
| * in the RegisterLine. |
| * |
| * We don't break basic blocks on every GC point -- in particular, |
| * instructions that might throw but have no "try" block don't |
| * end a basic block -- so there could be more than one GC point |
| * in a given basic block. |
| * |
| * We could change this, but it turns out to be not all that useful. |
| * At first glance it appears that we could share the liveness bit |
| * vector between the basic block struct and the register line, |
| * but the basic block needs to reflect the state *after* the |
| * instruction has finished, while the GC points need to describe |
| * the state before the instruction starts. |
| */ |
| u4 curIdx = workBlock->lastAddr; |
| while (true) { |
| if (!processInstruction(vdata, curIdx, workBits)) |
| goto bail; |
| |
| if (verbose) { |
| dumpLiveState(vdata, curIdx + 0x8000, workBits); |
| } |
| |
| if (dvmInsnIsGcPoint(insnFlags, curIdx)) { |
| BitVector* lineBits = vdata->registerLines[curIdx].liveRegs; |
| if (lineBits == NULL) { |
| lineBits = vdata->registerLines[curIdx].liveRegs = |
| dvmAllocBitVector(vdata->insnRegCount, false); |
| } |
| dvmCopyBitVector(lineBits, workBits); |
| } |
| |
| if (curIdx == workBlock->firstAddr) |
| break; |
| assert(curIdx >= backwardWidth[curIdx]); |
| curIdx -= backwardWidth[curIdx]; |
| } |
| |
| workBlock->visited = true; |
| workBlock->changed = false; |
| |
| if (verbose) { |
| dumpLiveState(vdata, curIdx, workBits); |
| } |
| |
| /* |
| * Merge changes to all predecessors. If the new bits don't match |
| * the old bits, set the "changed" flag. |
| */ |
| PointerSet* preds = workBlock->predecessors; |
| size_t numPreds = dvmPointerSetGetCount(preds); |
| unsigned int predIdx; |
| |
| for (predIdx = 0; predIdx < numPreds; predIdx++) { |
| VfyBasicBlock* pred = |
| (VfyBasicBlock*) dvmPointerSetGetEntry(preds, predIdx); |
| |
| pred->changed = dvmCheckMergeBitVectors(pred->liveRegs, workBits); |
| if (verbose) { |
| ALOGI("merging cur=%04x into pred last=%04x (ch=%d)", |
| curIdx, pred->lastAddr, pred->changed); |
| dumpLiveState(vdata, 0xfffa, pred->liveRegs); |
| dumpLiveState(vdata, 0xfffb, workBits); |
| } |
| |
| /* |
| * We want to set the "changed" flag on unvisited predecessors |
| * as a way of guiding the verifier through basic blocks in |
| * a reasonable order. We can't count on variable liveness |
| * changing, so we force "changed" to true even if it hasn't. |
| */ |
| if (!pred->visited) |
| pred->changed = true; |
| |
| /* |
| * Keep track of one of the changed blocks so we can start |
| * there instead of having to scan through the list. |
| */ |
| if (pred->changed) |
| startGuess = pred; |
| } |
| } |
| |
| #ifndef NDEBUG |
| /* |
| * Sanity check: verify that all GC point register lines have a |
| * liveness bit vector allocated. Also, we're not expecting non-GC |
| * points to have them. |
| */ |
| u4 checkIdx; |
| for (checkIdx = 0; checkIdx < vdata->insnsSize; ) { |
| if (dvmInsnIsGcPoint(insnFlags, checkIdx)) { |
| if (vdata->registerLines[checkIdx].liveRegs == NULL) { |
| LOG_VFY_METH(vdata->method, |
| "GLITCH: no liveRegs for GC point 0x%04x", checkIdx); |
| dvmAbort(); |
| } |
| } else if (vdata->registerLines[checkIdx].liveRegs != NULL) { |
| LOG_VFY_METH(vdata->method, |
| "GLITCH: liveRegs for non-GC point 0x%04x", checkIdx); |
| dvmAbort(); |
| } |
| u4 insnWidth = dvmInsnGetWidth(insnFlags, checkIdx); |
| checkIdx += insnWidth; |
| } |
| #endif |
| |
| /* |
| * Factor in the debug info, if any. |
| */ |
| if (!markDebugLocals(vdata)) |
| goto bail; |
| |
| result = true; |
| |
| bail: |
| free(backwardWidth); |
| return result; |
| } |
| |
| |
| /* |
| * Add a register to the LIVE set. |
| */ |
| static inline void GEN(BitVector* workBits, u4 regIndex) |
| { |
| dvmSetBit(workBits, regIndex); |
| } |
| |
| /* |
| * Add a register pair to the LIVE set. |
| */ |
| static inline void GENW(BitVector* workBits, u4 regIndex) |
| { |
| dvmSetBit(workBits, regIndex); |
| dvmSetBit(workBits, regIndex+1); |
| } |
| |
| /* |
| * Remove a register from the LIVE set. |
| */ |
| static inline void KILL(BitVector* workBits, u4 regIndex) |
| { |
| dvmClearBit(workBits, regIndex); |
| } |
| |
| /* |
| * Remove a register pair from the LIVE set. |
| */ |
| static inline void KILLW(BitVector* workBits, u4 regIndex) |
| { |
| dvmClearBit(workBits, regIndex); |
| dvmClearBit(workBits, regIndex+1); |
| } |
| |
| /* |
| * Process a single instruction. |
| * |
| * Returns "false" if something goes fatally wrong. |
| */ |
| static bool processInstruction(VerifierData* vdata, u4 insnIdx, |
| BitVector* workBits) |
| { |
| const Method* meth = vdata->method; |
| const u2* insns = meth->insns + insnIdx; |
| DecodedInstruction decInsn; |
| |
| dexDecodeInstruction(insns, &decInsn); |
| |
| /* |
| * Add registers to the "GEN" or "KILL" sets. We want to do KILL |
| * before GEN to handle cases where the source and destination |
| * register is the same. |
| */ |
| switch (decInsn.opcode) { |
| case OP_NOP: |
| case OP_RETURN_VOID: |
| case OP_GOTO: |
| case OP_GOTO_16: |
| case OP_GOTO_32: |
| /* no registers are used */ |
| break; |
| |
| case OP_RETURN: |
| case OP_RETURN_OBJECT: |
| case OP_MONITOR_ENTER: |
| case OP_MONITOR_EXIT: |
| case OP_CHECK_CAST: |
| case OP_THROW: |
| case OP_PACKED_SWITCH: |
| case OP_SPARSE_SWITCH: |
| case OP_FILL_ARRAY_DATA: |
| case OP_IF_EQZ: |
| case OP_IF_NEZ: |
| case OP_IF_LTZ: |
| case OP_IF_GEZ: |
| case OP_IF_GTZ: |
| case OP_IF_LEZ: |
| case OP_SPUT: |
| case OP_SPUT_BOOLEAN: |
| case OP_SPUT_BYTE: |
| case OP_SPUT_CHAR: |
| case OP_SPUT_SHORT: |
| case OP_SPUT_OBJECT: |
| /* action <- vA */ |
| GEN(workBits, decInsn.vA); |
| break; |
| |
| case OP_RETURN_WIDE: |
| case OP_SPUT_WIDE: |
| /* action <- vA(wide) */ |
| GENW(workBits, decInsn.vA); |
| break; |
| |
| case OP_IF_EQ: |
| case OP_IF_NE: |
| case OP_IF_LT: |
| case OP_IF_GE: |
| case OP_IF_GT: |
| case OP_IF_LE: |
| case OP_IPUT: |
| case OP_IPUT_BOOLEAN: |
| case OP_IPUT_BYTE: |
| case OP_IPUT_CHAR: |
| case OP_IPUT_SHORT: |
| case OP_IPUT_OBJECT: |
| /* action <- vA, vB */ |
| GEN(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| break; |
| |
| case OP_IPUT_WIDE: |
| /* action <- vA(wide), vB */ |
| GENW(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| break; |
| |
| case OP_APUT: |
| case OP_APUT_BOOLEAN: |
| case OP_APUT_BYTE: |
| case OP_APUT_CHAR: |
| case OP_APUT_SHORT: |
| case OP_APUT_OBJECT: |
| /* action <- vA, vB, vC */ |
| GEN(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| GEN(workBits, decInsn.vC); |
| break; |
| |
| case OP_APUT_WIDE: |
| /* action <- vA(wide), vB, vC */ |
| GENW(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| GEN(workBits, decInsn.vC); |
| break; |
| |
| case OP_FILLED_NEW_ARRAY: |
| case OP_INVOKE_VIRTUAL: |
| case OP_INVOKE_SUPER: |
| case OP_INVOKE_DIRECT: |
| case OP_INVOKE_STATIC: |
| case OP_INVOKE_INTERFACE: |
| /* action <- vararg */ |
| { |
| unsigned int idx; |
| for (idx = 0; idx < decInsn.vA; idx++) { |
| GEN(workBits, decInsn.arg[idx]); |
| } |
| } |
| break; |
| |
| case OP_FILLED_NEW_ARRAY_RANGE: |
| case OP_INVOKE_VIRTUAL_RANGE: |
| case OP_INVOKE_SUPER_RANGE: |
| case OP_INVOKE_DIRECT_RANGE: |
| case OP_INVOKE_STATIC_RANGE: |
| case OP_INVOKE_INTERFACE_RANGE: |
| /* action <- vararg/range */ |
| { |
| unsigned int idx; |
| for (idx = 0; idx < decInsn.vA; idx++) { |
| GEN(workBits, decInsn.vC + idx); |
| } |
| } |
| break; |
| |
| case OP_MOVE_RESULT: |
| case OP_MOVE_RESULT_WIDE: |
| case OP_MOVE_RESULT_OBJECT: |
| case OP_MOVE_EXCEPTION: |
| case OP_CONST_4: |
| case OP_CONST_16: |
| case OP_CONST: |
| case OP_CONST_HIGH16: |
| case OP_CONST_STRING: |
| case OP_CONST_STRING_JUMBO: |
| case OP_CONST_CLASS: |
| case OP_NEW_INSTANCE: |
| case OP_SGET: |
| case OP_SGET_BOOLEAN: |
| case OP_SGET_BYTE: |
| case OP_SGET_CHAR: |
| case OP_SGET_SHORT: |
| case OP_SGET_OBJECT: |
| /* vA <- value */ |
| KILL(workBits, decInsn.vA); |
| break; |
| |
| case OP_CONST_WIDE_16: |
| case OP_CONST_WIDE_32: |
| case OP_CONST_WIDE: |
| case OP_CONST_WIDE_HIGH16: |
| case OP_SGET_WIDE: |
| /* vA(wide) <- value */ |
| KILLW(workBits, decInsn.vA); |
| break; |
| |
| case OP_MOVE: |
| case OP_MOVE_FROM16: |
| case OP_MOVE_16: |
| case OP_MOVE_OBJECT: |
| case OP_MOVE_OBJECT_FROM16: |
| case OP_MOVE_OBJECT_16: |
| case OP_INSTANCE_OF: |
| case OP_ARRAY_LENGTH: |
| case OP_NEW_ARRAY: |
| case OP_IGET: |
| case OP_IGET_BOOLEAN: |
| case OP_IGET_BYTE: |
| case OP_IGET_CHAR: |
| case OP_IGET_SHORT: |
| case OP_IGET_OBJECT: |
| case OP_NEG_INT: |
| case OP_NOT_INT: |
| case OP_NEG_FLOAT: |
| case OP_INT_TO_FLOAT: |
| case OP_FLOAT_TO_INT: |
| case OP_INT_TO_BYTE: |
| case OP_INT_TO_CHAR: |
| case OP_INT_TO_SHORT: |
| case OP_ADD_INT_LIT16: |
| case OP_RSUB_INT: |
| case OP_MUL_INT_LIT16: |
| case OP_DIV_INT_LIT16: |
| case OP_REM_INT_LIT16: |
| case OP_AND_INT_LIT16: |
| case OP_OR_INT_LIT16: |
| case OP_XOR_INT_LIT16: |
| case OP_ADD_INT_LIT8: |
| case OP_RSUB_INT_LIT8: |
| case OP_MUL_INT_LIT8: |
| case OP_DIV_INT_LIT8: |
| case OP_REM_INT_LIT8: |
| case OP_SHL_INT_LIT8: |
| case OP_SHR_INT_LIT8: |
| case OP_USHR_INT_LIT8: |
| case OP_AND_INT_LIT8: |
| case OP_OR_INT_LIT8: |
| case OP_XOR_INT_LIT8: |
| /* vA <- vB */ |
| KILL(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| break; |
| |
| case OP_IGET_WIDE: |
| case OP_INT_TO_LONG: |
| case OP_INT_TO_DOUBLE: |
| case OP_FLOAT_TO_LONG: |
| case OP_FLOAT_TO_DOUBLE: |
| /* vA(wide) <- vB */ |
| KILLW(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| break; |
| |
| case OP_LONG_TO_INT: |
| case OP_LONG_TO_FLOAT: |
| case OP_DOUBLE_TO_INT: |
| case OP_DOUBLE_TO_FLOAT: |
| /* vA <- vB(wide) */ |
| KILL(workBits, decInsn.vA); |
| GENW(workBits, decInsn.vB); |
| break; |
| |
| case OP_MOVE_WIDE: |
| case OP_MOVE_WIDE_FROM16: |
| case OP_MOVE_WIDE_16: |
| case OP_NEG_LONG: |
| case OP_NOT_LONG: |
| case OP_NEG_DOUBLE: |
| case OP_LONG_TO_DOUBLE: |
| case OP_DOUBLE_TO_LONG: |
| /* vA(wide) <- vB(wide) */ |
| KILLW(workBits, decInsn.vA); |
| GENW(workBits, decInsn.vB); |
| break; |
| |
| case OP_CMPL_FLOAT: |
| case OP_CMPG_FLOAT: |
| case OP_AGET: |
| case OP_AGET_BOOLEAN: |
| case OP_AGET_BYTE: |
| case OP_AGET_CHAR: |
| case OP_AGET_SHORT: |
| case OP_AGET_OBJECT: |
| case OP_ADD_INT: |
| case OP_SUB_INT: |
| case OP_MUL_INT: |
| case OP_REM_INT: |
| case OP_DIV_INT: |
| case OP_AND_INT: |
| case OP_OR_INT: |
| case OP_XOR_INT: |
| case OP_SHL_INT: |
| case OP_SHR_INT: |
| case OP_USHR_INT: |
| case OP_ADD_FLOAT: |
| case OP_SUB_FLOAT: |
| case OP_MUL_FLOAT: |
| case OP_DIV_FLOAT: |
| case OP_REM_FLOAT: |
| /* vA <- vB, vC */ |
| KILL(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| GEN(workBits, decInsn.vC); |
| break; |
| |
| case OP_AGET_WIDE: |
| /* vA(wide) <- vB, vC */ |
| KILLW(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| GEN(workBits, decInsn.vC); |
| break; |
| |
| case OP_CMPL_DOUBLE: |
| case OP_CMPG_DOUBLE: |
| case OP_CMP_LONG: |
| /* vA <- vB(wide), vC(wide) */ |
| KILL(workBits, decInsn.vA); |
| GENW(workBits, decInsn.vB); |
| GENW(workBits, decInsn.vC); |
| break; |
| |
| case OP_SHL_LONG: |
| case OP_SHR_LONG: |
| case OP_USHR_LONG: |
| /* vA(wide) <- vB(wide), vC */ |
| KILLW(workBits, decInsn.vA); |
| GENW(workBits, decInsn.vB); |
| GEN(workBits, decInsn.vC); |
| break; |
| |
| case OP_ADD_LONG: |
| case OP_SUB_LONG: |
| case OP_MUL_LONG: |
| case OP_DIV_LONG: |
| case OP_REM_LONG: |
| case OP_AND_LONG: |
| case OP_OR_LONG: |
| case OP_XOR_LONG: |
| case OP_ADD_DOUBLE: |
| case OP_SUB_DOUBLE: |
| case OP_MUL_DOUBLE: |
| case OP_DIV_DOUBLE: |
| case OP_REM_DOUBLE: |
| /* vA(wide) <- vB(wide), vC(wide) */ |
| KILLW(workBits, decInsn.vA); |
| GENW(workBits, decInsn.vB); |
| GENW(workBits, decInsn.vC); |
| break; |
| |
| case OP_ADD_INT_2ADDR: |
| case OP_SUB_INT_2ADDR: |
| case OP_MUL_INT_2ADDR: |
| case OP_REM_INT_2ADDR: |
| case OP_SHL_INT_2ADDR: |
| case OP_SHR_INT_2ADDR: |
| case OP_USHR_INT_2ADDR: |
| case OP_AND_INT_2ADDR: |
| case OP_OR_INT_2ADDR: |
| case OP_XOR_INT_2ADDR: |
| case OP_DIV_INT_2ADDR: |
| /* vA <- vA, vB */ |
| /* KILL(workBits, decInsn.vA); */ |
| GEN(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| break; |
| |
| case OP_SHL_LONG_2ADDR: |
| case OP_SHR_LONG_2ADDR: |
| case OP_USHR_LONG_2ADDR: |
| /* vA(wide) <- vA(wide), vB */ |
| /* KILLW(workBits, decInsn.vA); */ |
| GENW(workBits, decInsn.vA); |
| GEN(workBits, decInsn.vB); |
| break; |
| |
| case OP_ADD_LONG_2ADDR: |
| case OP_SUB_LONG_2ADDR: |
| case OP_MUL_LONG_2ADDR: |
| case OP_DIV_LONG_2ADDR: |
| case OP_REM_LONG_2ADDR: |
| case OP_AND_LONG_2ADDR: |
| case OP_OR_LONG_2ADDR: |
| case OP_XOR_LONG_2ADDR: |
| case OP_ADD_FLOAT_2ADDR: |
| case OP_SUB_FLOAT_2ADDR: |
| case OP_MUL_FLOAT_2ADDR: |
| case OP_DIV_FLOAT_2ADDR: |
| case OP_REM_FLOAT_2ADDR: |
| case OP_ADD_DOUBLE_2ADDR: |
| case OP_SUB_DOUBLE_2ADDR: |
| case OP_MUL_DOUBLE_2ADDR: |
| case OP_DIV_DOUBLE_2ADDR: |
| case OP_REM_DOUBLE_2ADDR: |
| /* vA(wide) <- vA(wide), vB(wide) */ |
| /* KILLW(workBits, decInsn.vA); */ |
| GENW(workBits, decInsn.vA); |
| GENW(workBits, decInsn.vB); |
| break; |
| |
| /* we will only see this if liveness analysis is done after general vfy */ |
| case OP_THROW_VERIFICATION_ERROR: |
| /* no registers used */ |
| break; |
| |
| /* quickened instructions, not expected to appear */ |
| case OP_EXECUTE_INLINE: |
| case OP_EXECUTE_INLINE_RANGE: |
| case OP_IGET_QUICK: |
| case OP_IGET_WIDE_QUICK: |
| case OP_IGET_OBJECT_QUICK: |
| case OP_IPUT_QUICK: |
| case OP_IPUT_WIDE_QUICK: |
| case OP_IPUT_OBJECT_QUICK: |
| case OP_INVOKE_VIRTUAL_QUICK: |
| case OP_INVOKE_VIRTUAL_QUICK_RANGE: |
| case OP_INVOKE_SUPER_QUICK: |
| case OP_INVOKE_SUPER_QUICK_RANGE: |
| /* fall through to failure */ |
| |
| /* correctness fixes, not expected to appear */ |
| case OP_INVOKE_OBJECT_INIT_RANGE: |
| case OP_RETURN_VOID_BARRIER: |
| case OP_SPUT_VOLATILE: |
| case OP_SPUT_OBJECT_VOLATILE: |
| case OP_SPUT_WIDE_VOLATILE: |
| case OP_IPUT_VOLATILE: |
| case OP_IPUT_OBJECT_VOLATILE: |
| case OP_IPUT_WIDE_VOLATILE: |
| case OP_SGET_VOLATILE: |
| case OP_SGET_OBJECT_VOLATILE: |
| case OP_SGET_WIDE_VOLATILE: |
| case OP_IGET_VOLATILE: |
| case OP_IGET_OBJECT_VOLATILE: |
| case OP_IGET_WIDE_VOLATILE: |
| /* fall through to failure */ |
| |
| /* these should never appear during verification */ |
| case OP_UNUSED_3E: |
| case OP_UNUSED_3F: |
| case OP_UNUSED_40: |
| case OP_UNUSED_41: |
| case OP_UNUSED_42: |
| case OP_UNUSED_43: |
| case OP_UNUSED_73: |
| case OP_UNUSED_79: |
| case OP_UNUSED_7A: |
| case OP_BREAKPOINT: |
| case OP_UNUSED_FF: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * This is a dexDecodeDebugInfo callback, used by markDebugLocals(). |
| */ |
| static void markLocalsCb(void* ctxt, u2 reg, u4 startAddress, u4 endAddress, |
| const char* name, const char* descriptor, const char* signature) |
| { |
| VerifierData* vdata = (VerifierData*) ctxt; |
| bool verbose = dvmWantVerboseVerification(vdata->method); |
| |
| if (verbose) { |
| ALOGI("%04x-%04x %2d (%s %s)", |
| startAddress, endAddress, reg, name, descriptor); |
| } |
| |
| bool wide = (descriptor[0] == 'D' || descriptor[0] == 'J'); |
| assert(reg <= vdata->insnRegCount + (wide ? 1 : 0)); |
| |
| /* |
| * Set the bit in all GC point instructions in the range |
| * [startAddress, endAddress). |
| */ |
| unsigned int idx; |
| for (idx = startAddress; idx < endAddress; idx++) { |
| BitVector* liveRegs = vdata->registerLines[idx].liveRegs; |
| if (liveRegs != NULL) { |
| if (wide) { |
| GENW(liveRegs, reg); |
| } else { |
| GEN(liveRegs, reg); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Mark all debugger-visible locals as live. |
| * |
| * The "locals" table describes the positions of the various locals in the |
| * stack frame based on the current execution address. If the debugger |
| * wants to display one, it issues a request by "slot number". We need |
| * to ensure that references in stack slots that might be queried by the |
| * debugger aren't GCed. |
| * |
| * (If the GC had some way to mark the slot as invalid we wouldn't have |
| * to do this. We could also have the debugger interface check the |
| * register map and simply refuse to return a "dead" value, but that's |
| * potentially confusing since the referred-to object might actually be |
| * alive, and being able to see it without having to hunt around for a |
| * "live" stack frame is useful.) |
| */ |
| static bool markDebugLocals(VerifierData* vdata) |
| { |
| const Method* meth = vdata->method; |
| |
| dexDecodeDebugInfo(meth->clazz->pDvmDex->pDexFile, dvmGetMethodCode(meth), |
| meth->clazz->descriptor, meth->prototype.protoIdx, meth->accessFlags, |
| NULL, markLocalsCb, vdata); |
| |
| return true; |
| } |
| |
| |
| /* |
| * Dump the liveness bits to the log. |
| * |
| * "curIdx" is for display only. |
| */ |
| static void dumpLiveState(const VerifierData* vdata, u4 curIdx, |
| const BitVector* workBits) |
| { |
| u4 insnRegCount = vdata->insnRegCount; |
| size_t regCharSize = insnRegCount + (insnRegCount-1)/4 + 2 +1; |
| char regChars[regCharSize +1]; |
| unsigned int idx; |
| |
| memset(regChars, ' ', regCharSize); |
| regChars[0] = '['; |
| if (insnRegCount == 0) |
| regChars[1] = ']'; |
| else |
| regChars[1 + (insnRegCount-1) + (insnRegCount-1)/4 +1] = ']'; |
| regChars[regCharSize] = '\0'; |
| |
| for (idx = 0; idx < insnRegCount; idx++) { |
| char ch = dvmIsBitSet(workBits, idx) ? '+' : '-'; |
| regChars[1 + idx + (idx/4)] = ch; |
| } |
| |
| ALOGI("0x%04x %s", curIdx, regChars); |
| } |