| /* |
| * Copyright 2013, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.jf.dexlib2.analysis; |
| |
| import com.google.common.base.Function; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import org.jf.dexlib2.AccessFlags; |
| import org.jf.dexlib2.Opcode; |
| import org.jf.dexlib2.base.reference.BaseMethodReference; |
| import org.jf.dexlib2.iface.*; |
| import org.jf.dexlib2.iface.instruction.*; |
| import org.jf.dexlib2.iface.instruction.formats.*; |
| import org.jf.dexlib2.iface.reference.FieldReference; |
| import org.jf.dexlib2.iface.reference.MethodReference; |
| import org.jf.dexlib2.iface.reference.Reference; |
| import org.jf.dexlib2.iface.reference.TypeReference; |
| import org.jf.dexlib2.immutable.instruction.*; |
| import org.jf.dexlib2.immutable.reference.ImmutableFieldReference; |
| import org.jf.dexlib2.immutable.reference.ImmutableMethodReference; |
| import org.jf.dexlib2.util.MethodUtil; |
| import org.jf.dexlib2.util.ReferenceUtil; |
| import org.jf.dexlib2.util.TypeUtils; |
| import org.jf.dexlib2.writer.util.TryListBuilder; |
| import org.jf.util.BitSetUtils; |
| import org.jf.util.ExceptionWithContext; |
| import org.jf.util.SparseArray; |
| |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| import java.util.BitSet; |
| import java.util.List; |
| |
| /** |
| * The MethodAnalyzer performs several functions. It "analyzes" the instructions and infers the register types |
| * for each register, it can deodex odexed instructions, and it can verify the bytecode. The analysis and verification |
| * are done in two separate passes, because the analysis has to process instructions multiple times in some cases, and |
| * there's no need to perform the verification multiple times, so we wait until the method is fully analyzed and then |
| * verify it. |
| * |
| * Before calling the analyze() method, you must have initialized the ClassPath by calling |
| * ClassPath.InitializeClassPath |
| */ |
| public class MethodAnalyzer { |
| @Nonnull private final Method method; |
| @Nonnull private final MethodImplementation methodImpl; |
| |
| private final boolean normalizeVirtualMethods; |
| |
| private final int paramRegisterCount; |
| |
| @Nonnull private final ClassPath classPath; |
| @Nullable private final InlineMethodResolver inlineResolver; |
| |
| // This contains all the AnalyzedInstruction instances, keyed by the code unit address of the instruction |
| @Nonnull private final SparseArray<AnalyzedInstruction> analyzedInstructions = |
| new SparseArray<AnalyzedInstruction>(0); |
| |
| // Which instructions have been analyzed, keyed by instruction index |
| @Nonnull private final BitSet analyzedState; |
| |
| @Nullable private AnalysisException analysisException = null; |
| |
| // This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the |
| // register types for this instruction to the parameter types, in order to have them propagate to all of its |
| // successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first |
| // instruction, etc. |
| private final AnalyzedInstruction startOfMethod; |
| |
| public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method, |
| @Nullable InlineMethodResolver inlineResolver, boolean normalizeVirtualMethods) { |
| this.classPath = classPath; |
| this.inlineResolver = inlineResolver; |
| this.normalizeVirtualMethods = normalizeVirtualMethods; |
| |
| this.method = method; |
| |
| MethodImplementation methodImpl = method.getImplementation(); |
| if (methodImpl == null) { |
| throw new IllegalArgumentException("The method has no implementation"); |
| } |
| |
| this.methodImpl = methodImpl; |
| |
| // Override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't |
| // have to handle the case this special case of instruction being null, in the main class |
| startOfMethod = new AnalyzedInstruction(this, new ImmutableInstruction10x(Opcode.NOP), -1, methodImpl.getRegisterCount()) { |
| @Override protected boolean addPredecessor(AnalyzedInstruction predecessor) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override @Nonnull |
| public RegisterType getPredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| |
| buildInstructionList(); |
| |
| analyzedState = new BitSet(analyzedInstructions.size()); |
| paramRegisterCount = MethodUtil.getParameterRegisterCount(method); |
| analyze(); |
| } |
| |
| @Nonnull |
| public ClassPath getClassPath() { |
| return classPath; |
| } |
| |
| private void analyze() { |
| Method method = this.method; |
| MethodImplementation methodImpl = this.methodImpl; |
| |
| int totalRegisters = methodImpl.getRegisterCount(); |
| int parameterRegisters = paramRegisterCount; |
| |
| int nonParameterRegisters = totalRegisters - parameterRegisters; |
| |
| //if this isn't a static method, determine which register is the "this" register and set the type to the |
| //current class |
| if (!MethodUtil.isStatic(method)) { |
| int thisRegister = totalRegisters - parameterRegisters; |
| |
| //if this is a constructor, then set the "this" register to an uninitialized reference of the current class |
| if (MethodUtil.isConstructor(method)) { |
| setPostRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, |
| RegisterType.getRegisterType(RegisterType.UNINIT_THIS, |
| classPath.getClass(method.getDefiningClass()))); |
| } else { |
| setPostRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, |
| RegisterType.getRegisterType(RegisterType.REFERENCE, |
| classPath.getClass(method.getDefiningClass()))); |
| } |
| |
| propagateParameterTypes(totalRegisters-parameterRegisters+1); |
| } else { |
| propagateParameterTypes(totalRegisters-parameterRegisters); |
| } |
| |
| RegisterType uninit = RegisterType.getRegisterType(RegisterType.UNINIT, null); |
| for (int i=0; i<nonParameterRegisters; i++) { |
| setPostRegisterTypeAndPropagateChanges(startOfMethod, i, uninit); |
| } |
| |
| BitSet instructionsToAnalyze = new BitSet(analyzedInstructions.size()); |
| |
| //make sure all of the "first instructions" are marked for processing |
| for (AnalyzedInstruction successor: startOfMethod.successors) { |
| instructionsToAnalyze.set(successor.instructionIndex); |
| } |
| |
| BitSet undeodexedInstructions = new BitSet(analyzedInstructions.size()); |
| |
| do { |
| boolean didSomething = false; |
| |
| while (!instructionsToAnalyze.isEmpty()) { |
| for(int i=instructionsToAnalyze.nextSetBit(0); i>=0; i=instructionsToAnalyze.nextSetBit(i+1)) { |
| instructionsToAnalyze.clear(i); |
| if (analyzedState.get(i)) { |
| continue; |
| } |
| AnalyzedInstruction instructionToAnalyze = analyzedInstructions.valueAt(i); |
| try { |
| if (instructionToAnalyze.originalInstruction.getOpcode().odexOnly()) { |
| //if we had deodexed an odex instruction in a previous pass, we might have more specific |
| //register information now, so let's restore the original odexed instruction and |
| //re-deodex it |
| instructionToAnalyze.restoreOdexedInstruction(); |
| } |
| |
| if (!analyzeInstruction(instructionToAnalyze)) { |
| undeodexedInstructions.set(i); |
| continue; |
| } else { |
| didSomething = true; |
| undeodexedInstructions.clear(i); |
| } |
| } catch (AnalysisException ex) { |
| this.analysisException = ex; |
| int codeAddress = getInstructionAddress(instructionToAnalyze); |
| ex.codeAddress = codeAddress; |
| ex.addContext(String.format("opcode: %s", instructionToAnalyze.instruction.getOpcode().name)); |
| ex.addContext(String.format("code address: %d", codeAddress)); |
| ex.addContext(String.format("method: %s", ReferenceUtil.getReferenceString(method))); |
| break; |
| } |
| |
| analyzedState.set(instructionToAnalyze.getInstructionIndex()); |
| |
| for (AnalyzedInstruction successor: instructionToAnalyze.successors) { |
| instructionsToAnalyze.set(successor.getInstructionIndex()); |
| } |
| } |
| if (analysisException != null) { |
| break; |
| } |
| } |
| |
| if (!didSomething) { |
| break; |
| } |
| |
| if (!undeodexedInstructions.isEmpty()) { |
| for (int i=undeodexedInstructions.nextSetBit(0); i>=0; i=undeodexedInstructions.nextSetBit(i+1)) { |
| instructionsToAnalyze.set(i); |
| } |
| } |
| } while (true); |
| |
| //Now, go through and fix up any unresolvable odex instructions. These are usually odex instructions |
| //that operate on a null register, and thus always throw an NPE. They can also be any sort of odex instruction |
| //that occurs after an unresolvable odex instruction. We deodex if possible, or replace with an |
| //UnresolvableOdexInstruction |
| for (int i=0; i< analyzedInstructions.size(); i++) { |
| AnalyzedInstruction analyzedInstruction = analyzedInstructions.valueAt(i); |
| |
| Instruction instruction = analyzedInstruction.getInstruction(); |
| |
| if (instruction.getOpcode().odexOnly()) { |
| int objectRegisterNumber; |
| switch (instruction.getOpcode().format) { |
| case Format10x: |
| analyzeOdexReturnVoid(analyzedInstruction, false); |
| continue; |
| case Format21c: |
| case Format22c: |
| analyzePutGetVolatile(analyzedInstruction, false); |
| continue; |
| case Format35c: |
| analyzeInvokeDirectEmpty(analyzedInstruction, false); |
| continue; |
| case Format3rc: |
| analyzeInvokeObjectInitRange(analyzedInstruction, false); |
| continue; |
| case Format22cs: |
| objectRegisterNumber = ((Instruction22cs)instruction).getRegisterB(); |
| break; |
| case Format35mi: |
| case Format35ms: |
| objectRegisterNumber = ((FiveRegisterInstruction)instruction).getRegisterC(); |
| break; |
| case Format3rmi: |
| case Format3rms: |
| objectRegisterNumber = ((RegisterRangeInstruction)instruction).getStartRegister(); |
| break; |
| default: |
| continue; |
| } |
| |
| analyzedInstruction.setDeodexedInstruction( |
| new UnresolvedOdexInstruction(instruction, objectRegisterNumber)); |
| } |
| } |
| } |
| |
| private void propagateParameterTypes(int parameterStartRegister) { |
| int i=0; |
| for (MethodParameter parameter: method.getParameters()) { |
| if (TypeUtils.isWideType(parameter)) { |
| setPostRegisterTypeAndPropagateChanges(startOfMethod, parameterStartRegister + i++, |
| RegisterType.getWideRegisterType(parameter, true)); |
| setPostRegisterTypeAndPropagateChanges(startOfMethod, parameterStartRegister + i++, |
| RegisterType.getWideRegisterType(parameter, false)); |
| } else { |
| setPostRegisterTypeAndPropagateChanges(startOfMethod, parameterStartRegister + i++, |
| RegisterType.getRegisterType(classPath, parameter)); |
| } |
| } |
| } |
| |
| public List<AnalyzedInstruction> getAnalyzedInstructions() { |
| return analyzedInstructions.getValues(); |
| } |
| |
| public List<Instruction> getInstructions() { |
| return Lists.transform(analyzedInstructions.getValues(), new Function<AnalyzedInstruction, Instruction>() { |
| @Nullable @Override public Instruction apply(@Nullable AnalyzedInstruction input) { |
| if (input == null) { |
| return null; |
| } |
| return input.instruction; |
| } |
| }); |
| } |
| |
| @Nullable |
| public AnalysisException getAnalysisException() { |
| return analysisException; |
| } |
| |
| public int getParamRegisterCount() { |
| return paramRegisterCount; |
| } |
| |
| public int getInstructionAddress(@Nonnull AnalyzedInstruction instruction) { |
| return analyzedInstructions.keyAt(instruction.instructionIndex); |
| } |
| |
| private void setDestinationRegisterTypeAndPropagateChanges(@Nonnull AnalyzedInstruction analyzedInstruction, |
| @Nonnull RegisterType registerType) { |
| setPostRegisterTypeAndPropagateChanges(analyzedInstruction, analyzedInstruction.getDestinationRegister(), |
| registerType); |
| } |
| |
| private void propagateChanges(@Nonnull BitSet changedInstructions, int registerNumber, boolean override) { |
| //Using a for loop inside the while loop optimizes for the common case of the successors of an instruction |
| //occurring after the instruction. Any successors that occur prior to the instruction will be picked up on |
| //the next iteration of the while loop. |
| //This could also be done recursively, but in large methods it would likely cause very deep recursion. |
| while (!changedInstructions.isEmpty()) { |
| for (int instructionIndex=changedInstructions.nextSetBit(0); |
| instructionIndex>=0; |
| instructionIndex=changedInstructions.nextSetBit(instructionIndex+1)) { |
| |
| changedInstructions.clear(instructionIndex); |
| |
| propagateRegisterToSuccessors(analyzedInstructions.valueAt(instructionIndex), registerNumber, |
| changedInstructions, override); |
| } |
| } |
| } |
| |
| private void overridePredecessorRegisterTypeAndPropagateChanges( |
| @Nonnull AnalyzedInstruction analyzedInstruction, @Nonnull AnalyzedInstruction predecessor, |
| int registerNumber, @Nonnull RegisterType registerType) { |
| BitSet changedInstructions = new BitSet(analyzedInstructions.size()); |
| |
| if (!analyzedInstruction.overridePredecessorRegisterType( |
| predecessor, registerNumber, registerType, analyzedState)) { |
| return; |
| } |
| changedInstructions.set(analyzedInstruction.instructionIndex); |
| |
| propagateChanges(changedInstructions, registerNumber, true); |
| |
| if (registerType.category == RegisterType.LONG_LO) { |
| checkWidePair(registerNumber, analyzedInstruction); |
| overridePredecessorRegisterTypeAndPropagateChanges(analyzedInstruction, predecessor, registerNumber + 1, |
| RegisterType.LONG_HI_TYPE); |
| } else if (registerType.category == RegisterType.DOUBLE_LO) { |
| checkWidePair(registerNumber, analyzedInstruction); |
| overridePredecessorRegisterTypeAndPropagateChanges(analyzedInstruction, predecessor, registerNumber + 1, |
| RegisterType.DOUBLE_HI_TYPE); |
| } |
| } |
| |
| private void setPostRegisterTypeAndPropagateChanges(@Nonnull AnalyzedInstruction analyzedInstruction, |
| int registerNumber, @Nonnull RegisterType registerType) { |
| |
| BitSet changedInstructions = new BitSet(analyzedInstructions.size()); |
| |
| if (!analyzedInstruction.setPostRegisterType(registerNumber, registerType)) { |
| return; |
| } |
| |
| propagateRegisterToSuccessors(analyzedInstruction, registerNumber, changedInstructions, false); |
| |
| propagateChanges(changedInstructions, registerNumber, false); |
| |
| if (registerType.category == RegisterType.LONG_LO) { |
| checkWidePair(registerNumber, analyzedInstruction); |
| setPostRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, RegisterType.LONG_HI_TYPE); |
| } else if (registerType.category == RegisterType.DOUBLE_LO) { |
| checkWidePair(registerNumber, analyzedInstruction); |
| setPostRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, RegisterType.DOUBLE_HI_TYPE); |
| } |
| } |
| |
| private void propagateRegisterToSuccessors(@Nonnull AnalyzedInstruction instruction, int registerNumber, |
| @Nonnull BitSet changedInstructions, boolean override) { |
| RegisterType postRegisterType = instruction.getPostInstructionRegisterType(registerNumber); |
| for (AnalyzedInstruction successor: instruction.successors) { |
| if (successor.mergeRegister(registerNumber, postRegisterType, analyzedState, override)) { |
| changedInstructions.set(successor.instructionIndex); |
| } |
| } |
| } |
| |
| private void buildInstructionList() { |
| int registerCount = methodImpl.getRegisterCount(); |
| |
| ImmutableList<Instruction> instructions = ImmutableList.copyOf(methodImpl.getInstructions()); |
| |
| analyzedInstructions.ensureCapacity(instructions.size()); |
| |
| //first, create all the instructions and populate the instructionAddresses array |
| int currentCodeAddress = 0; |
| for (int i=0; i<instructions.size(); i++) { |
| Instruction instruction = instructions.get(i); |
| analyzedInstructions.append(currentCodeAddress, |
| new AnalyzedInstruction(this, instruction, i, registerCount)); |
| assert analyzedInstructions.indexOfKey(currentCodeAddress) == i; |
| currentCodeAddress += instruction.getCodeUnits(); |
| } |
| |
| //next, populate the exceptionHandlers array. The array item for each instruction that can throw an exception |
| //and is covered by a try block should be set to a list of the first instructions of each exception handler |
| //for the try block covering the instruction |
| List<? extends TryBlock<? extends ExceptionHandler>> tries = methodImpl.getTryBlocks(); |
| tries = TryListBuilder.massageTryBlocks(tries); |
| int triesIndex = 0; |
| TryBlock currentTry = null; |
| AnalyzedInstruction[] currentExceptionHandlers = null; |
| AnalyzedInstruction[][] exceptionHandlers = new AnalyzedInstruction[instructions.size()][]; |
| |
| if (tries != null) { |
| for (int i=0; i< analyzedInstructions.size(); i++) { |
| AnalyzedInstruction instruction = analyzedInstructions.valueAt(i); |
| Opcode instructionOpcode = instruction.instruction.getOpcode(); |
| currentCodeAddress = getInstructionAddress(instruction); |
| |
| //check if we have gone past the end of the current try |
| if (currentTry != null) { |
| if (currentTry.getStartCodeAddress() + currentTry.getCodeUnitCount() <= currentCodeAddress) { |
| currentTry = null; |
| triesIndex++; |
| } |
| } |
| |
| //check if the next try is applicable yet |
| if (currentTry == null && triesIndex < tries.size()) { |
| TryBlock<? extends ExceptionHandler> tryBlock = tries.get(triesIndex); |
| if (tryBlock.getStartCodeAddress() <= currentCodeAddress) { |
| assert(tryBlock.getStartCodeAddress() + tryBlock.getCodeUnitCount() > currentCodeAddress); |
| |
| currentTry = tryBlock; |
| |
| currentExceptionHandlers = buildExceptionHandlerArray(tryBlock); |
| } |
| } |
| |
| //if we're inside a try block, and the instruction can throw an exception, then add the exception handlers |
| //for the current instruction |
| if (currentTry != null && instructionOpcode.canThrow()) { |
| exceptionHandlers[i] = currentExceptionHandlers; |
| } |
| } |
| } |
| |
| //finally, populate the successors and predecessors for each instruction. We start at the fake "StartOfMethod" |
| //instruction and follow the execution path. Any unreachable code won't have any predecessors or successors, |
| //and no reachable code will have an unreachable predessor or successor |
| assert analyzedInstructions.size() > 0; |
| BitSet instructionsToProcess = new BitSet(instructions.size()); |
| |
| addPredecessorSuccessor(startOfMethod, analyzedInstructions.valueAt(0), exceptionHandlers, instructionsToProcess); |
| while (!instructionsToProcess.isEmpty()) { |
| int currentInstructionIndex = instructionsToProcess.nextSetBit(0); |
| instructionsToProcess.clear(currentInstructionIndex); |
| |
| AnalyzedInstruction instruction = analyzedInstructions.valueAt(currentInstructionIndex); |
| Opcode instructionOpcode = instruction.instruction.getOpcode(); |
| int instructionCodeAddress = getInstructionAddress(instruction); |
| |
| if (instruction.instruction.getOpcode().canContinue()) { |
| if (currentInstructionIndex == analyzedInstructions.size() - 1) { |
| throw new AnalysisException("Execution can continue past the last instruction"); |
| } |
| |
| AnalyzedInstruction nextInstruction = analyzedInstructions.valueAt(currentInstructionIndex+1); |
| addPredecessorSuccessor(instruction, nextInstruction, exceptionHandlers, instructionsToProcess); |
| } |
| |
| if (instruction.instruction instanceof OffsetInstruction) { |
| OffsetInstruction offsetInstruction = (OffsetInstruction)instruction.instruction; |
| |
| if (instructionOpcode == Opcode.PACKED_SWITCH || instructionOpcode == Opcode.SPARSE_SWITCH) { |
| AnalyzedInstruction analyzedSwitchPayload = analyzedInstructions.get( |
| instructionCodeAddress + offsetInstruction.getCodeOffset()); |
| if (analyzedSwitchPayload == null) { |
| throw new AnalysisException("Invalid switch payload offset"); |
| } |
| SwitchPayload switchPayload = (SwitchPayload)analyzedSwitchPayload.instruction; |
| |
| for (SwitchElement switchElement: switchPayload.getSwitchElements()) { |
| AnalyzedInstruction targetInstruction = analyzedInstructions.get(instructionCodeAddress + |
| switchElement.getOffset()); |
| if (targetInstruction == null) { |
| throw new AnalysisException("Invalid switch target offset"); |
| } |
| |
| addPredecessorSuccessor(instruction, targetInstruction, exceptionHandlers, |
| instructionsToProcess); |
| } |
| } else if (instructionOpcode != Opcode.FILL_ARRAY_DATA) { |
| int targetAddressOffset = offsetInstruction.getCodeOffset(); |
| AnalyzedInstruction targetInstruction = analyzedInstructions.get(instructionCodeAddress + |
| targetAddressOffset); |
| addPredecessorSuccessor(instruction, targetInstruction, exceptionHandlers, instructionsToProcess); |
| } |
| } |
| } |
| } |
| |
| private void addPredecessorSuccessor(@Nonnull AnalyzedInstruction predecessor, |
| @Nonnull AnalyzedInstruction successor, |
| @Nonnull AnalyzedInstruction[][] exceptionHandlers, |
| @Nonnull BitSet instructionsToProcess) { |
| addPredecessorSuccessor(predecessor, successor, exceptionHandlers, instructionsToProcess, false); |
| } |
| |
| private void addPredecessorSuccessor(@Nonnull AnalyzedInstruction predecessor, |
| @Nonnull AnalyzedInstruction successor, |
| @Nonnull AnalyzedInstruction[][] exceptionHandlers, |
| @Nonnull BitSet instructionsToProcess, boolean allowMoveException) { |
| |
| if (!allowMoveException && successor.instruction.getOpcode() == Opcode.MOVE_EXCEPTION) { |
| throw new AnalysisException("Execution can pass from the " + predecessor.instruction.getOpcode().name + |
| " instruction at code address 0x" + Integer.toHexString(getInstructionAddress(predecessor)) + |
| " to the move-exception instruction at address 0x" + |
| Integer.toHexString(getInstructionAddress(successor))); |
| } |
| |
| if (!successor.addPredecessor(predecessor)) { |
| return; |
| } |
| |
| predecessor.addSuccessor(successor); |
| instructionsToProcess.set(successor.getInstructionIndex()); |
| |
| |
| //if the successor can throw an instruction, then we need to add the exception handlers as additional |
| //successors to the predecessor (and then apply this same logic recursively if needed) |
| //Technically, we should handle the monitor-exit instruction as a special case. The exception is actually |
| //thrown *after* the instruction executes, instead of "before" the instruction executes, lke for any other |
| //instruction. But since it doesn't modify any registers, we can treat it like any other instruction. |
| AnalyzedInstruction[] exceptionHandlersForSuccessor = exceptionHandlers[successor.instructionIndex]; |
| if (exceptionHandlersForSuccessor != null) { |
| //the item for this instruction in exceptionHandlersForSuccessor should only be set if this instruction |
| //can throw an exception |
| assert successor.instruction.getOpcode().canThrow(); |
| |
| for (AnalyzedInstruction exceptionHandler: exceptionHandlersForSuccessor) { |
| addPredecessorSuccessor(predecessor, exceptionHandler, exceptionHandlers, instructionsToProcess, true); |
| } |
| } |
| } |
| |
| @Nonnull |
| private AnalyzedInstruction[] buildExceptionHandlerArray(@Nonnull TryBlock<? extends ExceptionHandler> tryBlock) { |
| List<? extends ExceptionHandler> exceptionHandlers = tryBlock.getExceptionHandlers(); |
| |
| AnalyzedInstruction[] handlerInstructions = new AnalyzedInstruction[exceptionHandlers.size()]; |
| for (int i=0; i<exceptionHandlers.size(); i++) { |
| handlerInstructions[i] = analyzedInstructions.get(exceptionHandlers.get(i).getHandlerCodeAddress()); |
| } |
| |
| return handlerInstructions; |
| } |
| |
| /** |
| * @return false if analyzedInstruction is an odex instruction that couldn't be deodexed, due to its |
| * object register being null |
| */ |
| private boolean analyzeInstruction(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| Instruction instruction = analyzedInstruction.instruction; |
| |
| switch (instruction.getOpcode()) { |
| case NOP: |
| return true; |
| case MOVE: |
| case MOVE_FROM16: |
| case MOVE_16: |
| case MOVE_WIDE: |
| case MOVE_WIDE_FROM16: |
| case MOVE_WIDE_16: |
| case MOVE_OBJECT: |
| case MOVE_OBJECT_FROM16: |
| case MOVE_OBJECT_16: |
| analyzeMove(analyzedInstruction); |
| return true; |
| case MOVE_RESULT: |
| case MOVE_RESULT_WIDE: |
| case MOVE_RESULT_OBJECT: |
| analyzeMoveResult(analyzedInstruction); |
| return true; |
| case MOVE_EXCEPTION: |
| analyzeMoveException(analyzedInstruction); |
| return true; |
| case RETURN_VOID: |
| case RETURN: |
| case RETURN_WIDE: |
| case RETURN_OBJECT: |
| return true; |
| case RETURN_VOID_BARRIER: |
| case RETURN_VOID_NO_BARRIER: |
| analyzeOdexReturnVoid(analyzedInstruction); |
| return true; |
| case CONST_4: |
| case CONST_16: |
| case CONST: |
| case CONST_HIGH16: |
| analyzeConst(analyzedInstruction); |
| return true; |
| case CONST_WIDE_16: |
| case CONST_WIDE_32: |
| case CONST_WIDE: |
| case CONST_WIDE_HIGH16: |
| analyzeWideConst(analyzedInstruction); |
| return true; |
| case CONST_STRING: |
| case CONST_STRING_JUMBO: |
| analyzeConstString(analyzedInstruction); |
| return true; |
| case CONST_CLASS: |
| analyzeConstClass(analyzedInstruction); |
| return true; |
| case MONITOR_ENTER: |
| case MONITOR_EXIT: |
| return true; |
| case CHECK_CAST: |
| analyzeCheckCast(analyzedInstruction); |
| return true; |
| case INSTANCE_OF: |
| analyzeInstanceOf(analyzedInstruction); |
| return true; |
| case ARRAY_LENGTH: |
| analyzeArrayLength(analyzedInstruction); |
| return true; |
| case NEW_INSTANCE: |
| analyzeNewInstance(analyzedInstruction); |
| return true; |
| case NEW_ARRAY: |
| analyzeNewArray(analyzedInstruction); |
| return true; |
| case FILLED_NEW_ARRAY: |
| case FILLED_NEW_ARRAY_RANGE: |
| return true; |
| case FILL_ARRAY_DATA: |
| return true; |
| case THROW: |
| case GOTO: |
| case GOTO_16: |
| case GOTO_32: |
| return true; |
| case PACKED_SWITCH: |
| case SPARSE_SWITCH: |
| return true; |
| case CMPL_FLOAT: |
| case CMPG_FLOAT: |
| case CMPL_DOUBLE: |
| case CMPG_DOUBLE: |
| case CMP_LONG: |
| analyzeFloatWideCmp(analyzedInstruction); |
| return true; |
| case IF_EQ: |
| case IF_NE: |
| case IF_LT: |
| case IF_GE: |
| case IF_GT: |
| case IF_LE: |
| case IF_LTZ: |
| case IF_GEZ: |
| case IF_GTZ: |
| case IF_LEZ: |
| return true; |
| case IF_EQZ: |
| case IF_NEZ: |
| analyzeIfEqzNez(analyzedInstruction); |
| return true; |
| case AGET: |
| analyze32BitPrimitiveAget(analyzedInstruction, RegisterType.INTEGER_TYPE); |
| return true; |
| case AGET_BOOLEAN: |
| analyze32BitPrimitiveAget(analyzedInstruction, RegisterType.BOOLEAN_TYPE); |
| return true; |
| case AGET_BYTE: |
| analyze32BitPrimitiveAget(analyzedInstruction, RegisterType.BYTE_TYPE); |
| return true; |
| case AGET_CHAR: |
| analyze32BitPrimitiveAget(analyzedInstruction, RegisterType.CHAR_TYPE); |
| return true; |
| case AGET_SHORT: |
| analyze32BitPrimitiveAget(analyzedInstruction, RegisterType.SHORT_TYPE); |
| return true; |
| case AGET_WIDE: |
| analyzeAgetWide(analyzedInstruction); |
| return true; |
| case AGET_OBJECT: |
| analyzeAgetObject(analyzedInstruction); |
| return true; |
| case APUT: |
| case APUT_BOOLEAN: |
| case APUT_BYTE: |
| case APUT_CHAR: |
| case APUT_SHORT: |
| case APUT_WIDE: |
| case APUT_OBJECT: |
| return true; |
| case IGET: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.INTEGER_TYPE); |
| return true; |
| case IGET_BOOLEAN: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.BOOLEAN_TYPE); |
| return true; |
| case IGET_BYTE: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.BYTE_TYPE); |
| return true; |
| case IGET_CHAR: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.CHAR_TYPE); |
| return true; |
| case IGET_SHORT: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.SHORT_TYPE); |
| return true; |
| case IGET_WIDE: |
| case IGET_OBJECT: |
| analyzeIgetSgetWideObject(analyzedInstruction); |
| return true; |
| case IPUT: |
| case IPUT_BOOLEAN: |
| case IPUT_BYTE: |
| case IPUT_CHAR: |
| case IPUT_SHORT: |
| case IPUT_WIDE: |
| case IPUT_OBJECT: |
| return true; |
| case SGET: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.INTEGER_TYPE); |
| return true; |
| case SGET_BOOLEAN: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.BOOLEAN_TYPE); |
| return true; |
| case SGET_BYTE: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.BYTE_TYPE); |
| return true; |
| case SGET_CHAR: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.CHAR_TYPE); |
| return true; |
| case SGET_SHORT: |
| analyze32BitPrimitiveIgetSget(analyzedInstruction, RegisterType.SHORT_TYPE); |
| return true; |
| case SGET_WIDE: |
| case SGET_OBJECT: |
| analyzeIgetSgetWideObject(analyzedInstruction); |
| return true; |
| case SPUT: |
| case SPUT_BOOLEAN: |
| case SPUT_BYTE: |
| case SPUT_CHAR: |
| case SPUT_SHORT: |
| case SPUT_WIDE: |
| case SPUT_OBJECT: |
| return true; |
| case INVOKE_VIRTUAL: |
| analyzeInvokeVirtual(analyzedInstruction, false); |
| return true; |
| case INVOKE_SUPER: |
| analyzeInvokeVirtual(analyzedInstruction, false); |
| return true; |
| case INVOKE_DIRECT: |
| analyzeInvokeDirect(analyzedInstruction); |
| return true; |
| case INVOKE_STATIC: |
| return true; |
| case INVOKE_INTERFACE: |
| // TODO: normalize interfaces |
| return true; |
| case INVOKE_VIRTUAL_RANGE: |
| analyzeInvokeVirtual(analyzedInstruction, true); |
| return true; |
| case INVOKE_SUPER_RANGE: |
| analyzeInvokeVirtual(analyzedInstruction, true); |
| return true; |
| case INVOKE_DIRECT_RANGE: |
| analyzeInvokeDirectRange(analyzedInstruction); |
| return true; |
| case INVOKE_STATIC_RANGE: |
| return true; |
| case INVOKE_INTERFACE_RANGE: |
| // TODO: normalize interfaces |
| return true; |
| case NEG_INT: |
| case NOT_INT: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE); |
| return true; |
| case NEG_LONG: |
| case NOT_LONG: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.LONG_LO_TYPE); |
| return true; |
| case NEG_FLOAT: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.FLOAT_TYPE); |
| return true; |
| case NEG_DOUBLE: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.DOUBLE_LO_TYPE); |
| return true; |
| case INT_TO_LONG: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.LONG_LO_TYPE); |
| return true; |
| case INT_TO_FLOAT: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.FLOAT_TYPE); |
| return true; |
| case INT_TO_DOUBLE: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.DOUBLE_LO_TYPE); |
| return true; |
| case LONG_TO_INT: |
| case DOUBLE_TO_INT: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE); |
| return true; |
| case LONG_TO_FLOAT: |
| case DOUBLE_TO_FLOAT: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.FLOAT_TYPE); |
| return true; |
| case LONG_TO_DOUBLE: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.DOUBLE_LO_TYPE); |
| return true; |
| case FLOAT_TO_INT: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE); |
| return true; |
| case FLOAT_TO_LONG: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.LONG_LO_TYPE); |
| return true; |
| case FLOAT_TO_DOUBLE: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.DOUBLE_LO_TYPE); |
| return true; |
| case DOUBLE_TO_LONG: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.LONG_LO_TYPE); |
| return true; |
| case INT_TO_BYTE: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.BYTE_TYPE); |
| return true; |
| case INT_TO_CHAR: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.CHAR_TYPE); |
| return true; |
| case INT_TO_SHORT: |
| analyzeUnaryOp(analyzedInstruction, RegisterType.SHORT_TYPE); |
| return true; |
| case ADD_INT: |
| case SUB_INT: |
| case MUL_INT: |
| case DIV_INT: |
| case REM_INT: |
| case SHL_INT: |
| case SHR_INT: |
| case USHR_INT: |
| analyzeBinaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE, false); |
| return true; |
| case AND_INT: |
| case OR_INT: |
| case XOR_INT: |
| analyzeBinaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE, true); |
| return true; |
| case ADD_LONG: |
| case SUB_LONG: |
| case MUL_LONG: |
| case DIV_LONG: |
| case REM_LONG: |
| case AND_LONG: |
| case OR_LONG: |
| case XOR_LONG: |
| case SHL_LONG: |
| case SHR_LONG: |
| case USHR_LONG: |
| analyzeBinaryOp(analyzedInstruction, RegisterType.LONG_LO_TYPE, false); |
| return true; |
| case ADD_FLOAT: |
| case SUB_FLOAT: |
| case MUL_FLOAT: |
| case DIV_FLOAT: |
| case REM_FLOAT: |
| analyzeBinaryOp(analyzedInstruction, RegisterType.FLOAT_TYPE, false); |
| return true; |
| case ADD_DOUBLE: |
| case SUB_DOUBLE: |
| case MUL_DOUBLE: |
| case DIV_DOUBLE: |
| case REM_DOUBLE: |
| analyzeBinaryOp(analyzedInstruction, RegisterType.DOUBLE_LO_TYPE, false); |
| return true; |
| case ADD_INT_2ADDR: |
| case SUB_INT_2ADDR: |
| case MUL_INT_2ADDR: |
| case DIV_INT_2ADDR: |
| case REM_INT_2ADDR: |
| case SHL_INT_2ADDR: |
| case SHR_INT_2ADDR: |
| case USHR_INT_2ADDR: |
| analyzeBinary2AddrOp(analyzedInstruction, RegisterType.INTEGER_TYPE, false); |
| return true; |
| case AND_INT_2ADDR: |
| case OR_INT_2ADDR: |
| case XOR_INT_2ADDR: |
| analyzeBinary2AddrOp(analyzedInstruction, RegisterType.INTEGER_TYPE, true); |
| return true; |
| case ADD_LONG_2ADDR: |
| case SUB_LONG_2ADDR: |
| case MUL_LONG_2ADDR: |
| case DIV_LONG_2ADDR: |
| case REM_LONG_2ADDR: |
| case AND_LONG_2ADDR: |
| case OR_LONG_2ADDR: |
| case XOR_LONG_2ADDR: |
| case SHL_LONG_2ADDR: |
| case SHR_LONG_2ADDR: |
| case USHR_LONG_2ADDR: |
| analyzeBinary2AddrOp(analyzedInstruction, RegisterType.LONG_LO_TYPE, false); |
| return true; |
| case ADD_FLOAT_2ADDR: |
| case SUB_FLOAT_2ADDR: |
| case MUL_FLOAT_2ADDR: |
| case DIV_FLOAT_2ADDR: |
| case REM_FLOAT_2ADDR: |
| analyzeBinary2AddrOp(analyzedInstruction, RegisterType.FLOAT_TYPE, false); |
| return true; |
| case ADD_DOUBLE_2ADDR: |
| case SUB_DOUBLE_2ADDR: |
| case MUL_DOUBLE_2ADDR: |
| case DIV_DOUBLE_2ADDR: |
| case REM_DOUBLE_2ADDR: |
| analyzeBinary2AddrOp(analyzedInstruction, RegisterType.DOUBLE_LO_TYPE, false); |
| return true; |
| case ADD_INT_LIT16: |
| case RSUB_INT: |
| case MUL_INT_LIT16: |
| case DIV_INT_LIT16: |
| case REM_INT_LIT16: |
| analyzeLiteralBinaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE, false); |
| return true; |
| case AND_INT_LIT16: |
| case OR_INT_LIT16: |
| case XOR_INT_LIT16: |
| analyzeLiteralBinaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE, true); |
| return true; |
| case ADD_INT_LIT8: |
| case RSUB_INT_LIT8: |
| case MUL_INT_LIT8: |
| case DIV_INT_LIT8: |
| case REM_INT_LIT8: |
| case SHL_INT_LIT8: |
| analyzeLiteralBinaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE, false); |
| return true; |
| case AND_INT_LIT8: |
| case OR_INT_LIT8: |
| case XOR_INT_LIT8: |
| analyzeLiteralBinaryOp(analyzedInstruction, RegisterType.INTEGER_TYPE, true); |
| return true; |
| case SHR_INT_LIT8: |
| analyzeLiteralBinaryOp(analyzedInstruction, getDestTypeForLiteralShiftRight(analyzedInstruction, true), |
| false); |
| return true; |
| case USHR_INT_LIT8: |
| analyzeLiteralBinaryOp(analyzedInstruction, getDestTypeForLiteralShiftRight(analyzedInstruction, false), |
| false); |
| return true; |
| |
| /*odexed instructions*/ |
| case IGET_VOLATILE: |
| case IPUT_VOLATILE: |
| case SGET_VOLATILE: |
| case SPUT_VOLATILE: |
| case IGET_OBJECT_VOLATILE: |
| case IGET_WIDE_VOLATILE: |
| case IPUT_WIDE_VOLATILE: |
| case SGET_WIDE_VOLATILE: |
| case SPUT_WIDE_VOLATILE: |
| analyzePutGetVolatile(analyzedInstruction); |
| return true; |
| case THROW_VERIFICATION_ERROR: |
| return true; |
| case EXECUTE_INLINE: |
| analyzeExecuteInline(analyzedInstruction); |
| return true; |
| case EXECUTE_INLINE_RANGE: |
| analyzeExecuteInlineRange(analyzedInstruction); |
| return true; |
| case INVOKE_DIRECT_EMPTY: |
| analyzeInvokeDirectEmpty(analyzedInstruction); |
| return true; |
| case INVOKE_OBJECT_INIT_RANGE: |
| analyzeInvokeObjectInitRange(analyzedInstruction); |
| return true; |
| case IGET_QUICK: |
| case IGET_WIDE_QUICK: |
| case IGET_OBJECT_QUICK: |
| case IPUT_QUICK: |
| case IPUT_WIDE_QUICK: |
| case IPUT_OBJECT_QUICK: |
| case IPUT_BOOLEAN_QUICK: |
| case IPUT_BYTE_QUICK: |
| case IPUT_CHAR_QUICK: |
| case IPUT_SHORT_QUICK: |
| case IGET_BOOLEAN_QUICK: |
| case IGET_BYTE_QUICK: |
| case IGET_CHAR_QUICK: |
| case IGET_SHORT_QUICK: |
| return analyzeIputIgetQuick(analyzedInstruction); |
| case INVOKE_VIRTUAL_QUICK: |
| return analyzeInvokeVirtualQuick(analyzedInstruction, false, false); |
| case INVOKE_SUPER_QUICK: |
| return analyzeInvokeVirtualQuick(analyzedInstruction, true, false); |
| case INVOKE_VIRTUAL_QUICK_RANGE: |
| return analyzeInvokeVirtualQuick(analyzedInstruction, false, true); |
| case INVOKE_SUPER_QUICK_RANGE: |
| return analyzeInvokeVirtualQuick(analyzedInstruction, true, true); |
| case IPUT_OBJECT_VOLATILE: |
| case SGET_OBJECT_VOLATILE: |
| case SPUT_OBJECT_VOLATILE: |
| analyzePutGetVolatile(analyzedInstruction); |
| return true; |
| default: |
| assert false; |
| return true; |
| } |
| } |
| |
| private static final BitSet Primitive32BitCategories = BitSetUtils.bitSetOfIndexes( |
| RegisterType.NULL, |
| RegisterType.ONE, |
| RegisterType.BOOLEAN, |
| RegisterType.BYTE, |
| RegisterType.POS_BYTE, |
| RegisterType.SHORT, |
| RegisterType.POS_SHORT, |
| RegisterType.CHAR, |
| RegisterType.INTEGER, |
| RegisterType.FLOAT); |
| |
| private static final BitSet WideLowCategories = BitSetUtils.bitSetOfIndexes( |
| RegisterType.LONG_LO, |
| RegisterType.DOUBLE_LO); |
| |
| private static final BitSet WideHighCategories = BitSetUtils.bitSetOfIndexes( |
| RegisterType.LONG_HI, |
| RegisterType.DOUBLE_HI); |
| |
| private static final BitSet ReferenceOrUninitCategories = BitSetUtils.bitSetOfIndexes( |
| RegisterType.NULL, |
| RegisterType.UNINIT_REF, |
| RegisterType.UNINIT_THIS, |
| RegisterType.REFERENCE); |
| |
| private static final BitSet BooleanCategories = BitSetUtils.bitSetOfIndexes( |
| RegisterType.NULL, |
| RegisterType.ONE, |
| RegisterType.BOOLEAN); |
| |
| private void analyzeMove(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; |
| |
| RegisterType sourceRegisterType = analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, sourceRegisterType); |
| } |
| |
| private void analyzeMoveResult(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| AnalyzedInstruction previousInstruction = null; |
| if (analyzedInstruction.instructionIndex > 0) { |
| previousInstruction = analyzedInstructions.valueAt(analyzedInstruction.instructionIndex-1); |
| } |
| if (previousInstruction == null || !previousInstruction.instruction.getOpcode().setsResult()) { |
| throw new AnalysisException(analyzedInstruction.instruction.getOpcode().name + " must occur after an " + |
| "invoke-*/fill-new-array instruction"); |
| } |
| |
| RegisterType resultRegisterType; |
| ReferenceInstruction invokeInstruction = (ReferenceInstruction)previousInstruction.instruction; |
| Reference reference = invokeInstruction.getReference(); |
| |
| if (reference instanceof MethodReference) { |
| resultRegisterType = RegisterType.getRegisterType(classPath, ((MethodReference)reference).getReturnType()); |
| } else { |
| resultRegisterType = RegisterType.getRegisterType(classPath, (TypeReference)reference); |
| } |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, resultRegisterType); |
| } |
| |
| private void analyzeMoveException(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| int instructionAddress = getInstructionAddress(analyzedInstruction); |
| |
| RegisterType exceptionType = RegisterType.UNKNOWN_TYPE; |
| |
| for (TryBlock<? extends ExceptionHandler> tryBlock: methodImpl.getTryBlocks()) { |
| for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) { |
| |
| if (handler.getHandlerCodeAddress() == instructionAddress) { |
| String type = handler.getExceptionType(); |
| if (type == null) { |
| exceptionType = RegisterType.getRegisterType(RegisterType.REFERENCE, |
| classPath.getClass("Ljava/lang/Throwable;")); |
| } else { |
| exceptionType = RegisterType.getRegisterType(RegisterType.REFERENCE, classPath.getClass(type)) |
| .merge(exceptionType); |
| } |
| } |
| } |
| } |
| |
| if (exceptionType.category == RegisterType.UNKNOWN) { |
| throw new AnalysisException("move-exception must be the first instruction in an exception handler block"); |
| } |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, exceptionType); |
| } |
| |
| private void analyzeOdexReturnVoid(AnalyzedInstruction analyzedInstruction) { |
| analyzeOdexReturnVoid(analyzedInstruction, true); |
| } |
| |
| private void analyzeOdexReturnVoid(@Nonnull AnalyzedInstruction analyzedInstruction, boolean analyzeResult) { |
| Instruction10x deodexedInstruction = new ImmutableInstruction10x(Opcode.RETURN_VOID); |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| |
| if (analyzeResult) { |
| analyzeInstruction(analyzedInstruction); |
| } |
| } |
| |
| private void analyzeConst(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| NarrowLiteralInstruction instruction = (NarrowLiteralInstruction)analyzedInstruction.instruction; |
| |
| //we assume that the literal value is a valid value for the given instruction type, because it's impossible |
| //to store an invalid literal with the instruction. so we don't need to check the type of the literal |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, |
| RegisterType.getRegisterTypeForLiteral(instruction.getNarrowLiteral())); |
| } |
| |
| private void analyzeWideConst(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.LONG_LO_TYPE); |
| } |
| |
| private void analyzeConstString(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| TypeProto stringClass = classPath.getClass("Ljava/lang/String;"); |
| RegisterType stringType = RegisterType.getRegisterType(RegisterType.REFERENCE, stringClass); |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, stringType); |
| } |
| |
| private void analyzeConstClass(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| TypeProto classClass = classPath.getClass("Ljava/lang/Class;"); |
| RegisterType classType = RegisterType.getRegisterType(RegisterType.REFERENCE, classClass); |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, classType); |
| } |
| |
| private void analyzeCheckCast(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| ReferenceInstruction instruction = (ReferenceInstruction)analyzedInstruction.instruction; |
| TypeReference reference = (TypeReference)instruction.getReference(); |
| RegisterType castRegisterType = RegisterType.getRegisterType(classPath, reference); |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, castRegisterType); |
| } |
| |
| private static boolean isNarrowingConversion(RegisterType originalType, RegisterType newType) { |
| if (originalType.type == null || newType.type == null) { |
| return false; |
| } |
| if (originalType.type.isInterface()) { |
| return newType.type.implementsInterface(originalType.type.getType()); |
| } else { |
| TypeProto commonSuperclass = newType.type.getCommonSuperclass(originalType.type); |
| return commonSuperclass.getType().equals(originalType.type.getType()); |
| } |
| } |
| |
| static boolean canNarrowAfterInstanceOf(AnalyzedInstruction analyzedInstanceOfInstruction, |
| AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) { |
| if (!classPath.isArt()) { |
| return false; |
| } |
| |
| Instruction ifInstruction = analyzedIfInstruction.instruction; |
| if (((Instruction21t)ifInstruction).getRegisterA() == analyzedInstanceOfInstruction.getDestinationRegister()) { |
| Reference reference = ((Instruction22c)analyzedInstanceOfInstruction.getInstruction()).getReference(); |
| RegisterType registerType = RegisterType.getRegisterType(classPath, (TypeReference)reference); |
| |
| try { |
| if (registerType.type != null && !registerType.type.isInterface()) { |
| int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction()) |
| .getRegisterB(); |
| |
| RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister); |
| |
| return isNarrowingConversion(originalType, registerType); |
| } |
| } catch (UnresolvedClassException ex) { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Art uses a peephole optimization for an if-eqz or if-nez that occur immediately after an instance-of. It will |
| * narrow the type if possible, and then NOP out any corresponding check-cast instruction later on |
| */ |
| private void analyzeIfEqzNez(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| if (classPath.isArt()) { |
| int instructionIndex = analyzedInstruction.getInstructionIndex(); |
| if (instructionIndex > 0) { |
| if (analyzedInstruction.getPredecessorCount() != 1) { |
| return; |
| } |
| AnalyzedInstruction prevAnalyzedInstruction = analyzedInstruction.getPredecessors().first(); |
| if (prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) { |
| Instruction22c instanceOfInstruction = (Instruction22c)prevAnalyzedInstruction.instruction; |
| if (canNarrowAfterInstanceOf(prevAnalyzedInstruction, analyzedInstruction, classPath)) { |
| List<Integer> narrowingRegisters = Lists.newArrayList(); |
| |
| RegisterType newType = RegisterType.getRegisterType(classPath, |
| (TypeReference)instanceOfInstruction.getReference()); |
| |
| if (instructionIndex > 1) { |
| // If we have something like: |
| // move-object/from16 v0, p1 |
| // instance-of v2, v0, Lblah; |
| // if-eqz v2, :target |
| // Then we need to narrow both v0 AND p1, but only if all predecessors of instance-of are a |
| // move-object for the same registers |
| |
| int additionalNarrowingRegister = -1; |
| for (AnalyzedInstruction prevPrevAnalyzedInstruction : prevAnalyzedInstruction.predecessors) { |
| Opcode opcode = prevPrevAnalyzedInstruction.instruction.getOpcode(); |
| if (opcode == Opcode.MOVE_OBJECT || opcode == Opcode.MOVE_OBJECT_16 || |
| opcode == Opcode.MOVE_OBJECT_FROM16) { |
| TwoRegisterInstruction moveInstruction = |
| ((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction); |
| RegisterType originalType = |
| prevPrevAnalyzedInstruction.getPostInstructionRegisterType( |
| moveInstruction.getRegisterB()); |
| if (moveInstruction.getRegisterA() != instanceOfInstruction.getRegisterB()) { |
| additionalNarrowingRegister = -1; |
| break; |
| } |
| if (originalType.type == null) { |
| additionalNarrowingRegister = -1; |
| break; |
| } |
| if (isNarrowingConversion(originalType, newType)) { |
| if (additionalNarrowingRegister != -1) { |
| if (additionalNarrowingRegister != moveInstruction.getRegisterB()) { |
| additionalNarrowingRegister = -1; |
| break; |
| } |
| } else { |
| additionalNarrowingRegister = moveInstruction.getRegisterB(); |
| } |
| } |
| } else { |
| additionalNarrowingRegister = -1; |
| break; |
| } |
| } |
| if (additionalNarrowingRegister != -1) { |
| narrowingRegisters.add(additionalNarrowingRegister); |
| } |
| } |
| |
| // Propagate the original type to the failing branch, and the new type to the successful branch |
| int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB(); |
| narrowingRegisters.add(narrowingRegister); |
| RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister); |
| |
| AnalyzedInstruction fallthroughInstruction = analyzedInstructions.valueAt( |
| analyzedInstruction.getInstructionIndex() + 1); |
| |
| int nextAddress = getInstructionAddress(analyzedInstruction) + |
| ((Instruction21t)analyzedInstruction.instruction).getCodeOffset(); |
| AnalyzedInstruction branchInstruction = analyzedInstructions.get(nextAddress); |
| |
| for (int register : narrowingRegisters) { |
| if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) { |
| overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction, |
| register, newType); |
| overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction, |
| register, originalType); |
| } else { |
| overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction, |
| register, originalType); |
| overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction, |
| register, newType); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void analyzeInstanceOf(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.BOOLEAN_TYPE); |
| } |
| |
| private void analyzeArrayLength(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.INTEGER_TYPE); |
| } |
| |
| private void analyzeNewInstance(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| ReferenceInstruction instruction = (ReferenceInstruction)analyzedInstruction.instruction; |
| |
| int register = ((OneRegisterInstruction)analyzedInstruction.instruction).getRegisterA(); |
| RegisterType destRegisterType = analyzedInstruction.getPostInstructionRegisterType(register); |
| if (destRegisterType.category != RegisterType.UNKNOWN) { |
| //the post-instruction destination register will only be set if we have already analyzed this instruction |
| //at least once. If this is the case, then the uninit reference has already been propagated to all |
| //successors and nothing else needs to be done. |
| assert destRegisterType.category == RegisterType.UNINIT_REF; |
| return; |
| } |
| |
| TypeReference typeReference = (TypeReference)instruction.getReference(); |
| |
| RegisterType classType = RegisterType.getRegisterType(classPath, typeReference); |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, |
| RegisterType.getRegisterType(RegisterType.UNINIT_REF, classType.type)); |
| } |
| |
| private void analyzeNewArray(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| ReferenceInstruction instruction = (ReferenceInstruction)analyzedInstruction.instruction; |
| |
| TypeReference type = (TypeReference)instruction.getReference(); |
| if (type.getType().charAt(0) != '[') { |
| throw new AnalysisException("new-array used with non-array type"); |
| } |
| |
| RegisterType arrayType = RegisterType.getRegisterType(classPath, type); |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, arrayType); |
| } |
| |
| private void analyzeFloatWideCmp(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.BYTE_TYPE); |
| } |
| |
| private void analyze32BitPrimitiveAget(@Nonnull AnalyzedInstruction analyzedInstruction, |
| @Nonnull RegisterType registerType) { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, registerType); |
| } |
| |
| private void analyzeAgetWide(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.instruction; |
| |
| RegisterType arrayRegisterType = analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); |
| if (arrayRegisterType.category != RegisterType.NULL) { |
| if (arrayRegisterType.category != RegisterType.REFERENCE || |
| !(arrayRegisterType.type instanceof ArrayProto)) { |
| throw new AnalysisException("aget-wide used with non-array register: %s", arrayRegisterType.toString()); |
| } |
| ArrayProto arrayProto = (ArrayProto)arrayRegisterType.type; |
| |
| if (arrayProto.dimensions != 1) { |
| throw new AnalysisException("aget-wide used with multi-dimensional array: %s", |
| arrayRegisterType.toString()); |
| } |
| |
| char arrayBaseType = arrayProto.getElementType().charAt(0); |
| if (arrayBaseType == 'J') { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.LONG_LO_TYPE); |
| } else if (arrayBaseType == 'D') { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.DOUBLE_LO_TYPE); |
| } else { |
| throw new AnalysisException("aget-wide used with narrow array: %s", arrayRegisterType); |
| } |
| } else { |
| // If the array register is null, we can assume that the destination register was meant to be a wide type. |
| // This is the same behavior as dalvik's verifier |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.LONG_LO_TYPE); |
| } |
| } |
| |
| private void analyzeAgetObject(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.instruction; |
| |
| RegisterType arrayRegisterType = analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); |
| if (arrayRegisterType.category != RegisterType.NULL) { |
| if (arrayRegisterType.category != RegisterType.REFERENCE || |
| !(arrayRegisterType.type instanceof ArrayProto)) { |
| throw new AnalysisException("aget-object used with non-array register: %s", |
| arrayRegisterType.toString()); |
| } |
| |
| ArrayProto arrayProto = (ArrayProto)arrayRegisterType.type; |
| |
| String elementType = arrayProto.getImmediateElementType(); |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, |
| RegisterType.getRegisterType(RegisterType.REFERENCE, classPath.getClass(elementType))); |
| } else { |
| // If the array register is null, we can assume that the destination register was meant to be a reference |
| // type, so we set the destination to NULL. This is the same behavior as dalvik's verifier |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, RegisterType.NULL_TYPE); |
| } |
| } |
| |
| private void analyze32BitPrimitiveIgetSget(@Nonnull AnalyzedInstruction analyzedInstruction, |
| @Nonnull RegisterType registerType) { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, registerType); |
| } |
| |
| private void analyzeIgetSgetWideObject(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| ReferenceInstruction referenceInstruction = (ReferenceInstruction)analyzedInstruction.instruction; |
| |
| FieldReference fieldReference = (FieldReference)referenceInstruction.getReference(); |
| |
| RegisterType fieldType = RegisterType.getRegisterType(classPath, fieldReference.getType()); |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, fieldType); |
| } |
| |
| private void analyzeInvokeDirect(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.instruction; |
| analyzeInvokeDirectCommon(analyzedInstruction, instruction.getRegisterC()); |
| } |
| |
| private void analyzeInvokeDirectRange(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.instruction; |
| analyzeInvokeDirectCommon(analyzedInstruction, instruction.getStartRegister()); |
| } |
| |
| private void analyzeInvokeDirectCommon(@Nonnull AnalyzedInstruction analyzedInstruction, int objectRegister) { |
| //the only time that an invoke instruction changes a register type is when using invoke-direct on a |
| //constructor (<init>) method, which changes the uninitialized reference (and any register that the same |
| //uninit reference has been copied to) to an initialized reference |
| |
| ReferenceInstruction instruction = (ReferenceInstruction)analyzedInstruction.instruction; |
| |
| MethodReference methodReference = (MethodReference)instruction.getReference(); |
| |
| if (!methodReference.getName().equals("<init>")) { |
| return; |
| } |
| |
| RegisterType objectRegisterType = analyzedInstruction.getPreInstructionRegisterType(objectRegister); |
| |
| if (objectRegisterType.category != RegisterType.UNINIT_REF && |
| objectRegisterType.category != RegisterType.UNINIT_THIS) { |
| return; |
| } |
| |
| setPostRegisterTypeAndPropagateChanges(analyzedInstruction, objectRegister, |
| RegisterType.getRegisterType(RegisterType.REFERENCE, objectRegisterType.type)); |
| |
| for (int i=0; i<analyzedInstruction.postRegisterMap.length; i++) { |
| RegisterType postInstructionRegisterType = analyzedInstruction.postRegisterMap[i]; |
| if (postInstructionRegisterType.category == RegisterType.UNKNOWN) { |
| RegisterType preInstructionRegisterType = |
| analyzedInstruction.getPreInstructionRegisterType(i); |
| |
| if (preInstructionRegisterType.category == RegisterType.UNINIT_REF || |
| preInstructionRegisterType.category == RegisterType.UNINIT_THIS) { |
| RegisterType registerType; |
| if (preInstructionRegisterType.equals(objectRegisterType)) { |
| registerType = analyzedInstruction.postRegisterMap[objectRegister]; |
| } else { |
| registerType = preInstructionRegisterType; |
| } |
| |
| setPostRegisterTypeAndPropagateChanges(analyzedInstruction, i, registerType); |
| } |
| } |
| } |
| } |
| |
| private void analyzeUnaryOp(@Nonnull AnalyzedInstruction analyzedInstruction, |
| @Nonnull RegisterType destRegisterType) { |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, destRegisterType); |
| } |
| |
| private void analyzeBinaryOp(@Nonnull AnalyzedInstruction analyzedInstruction, |
| @Nonnull RegisterType destRegisterType, boolean checkForBoolean) { |
| if (checkForBoolean) { |
| ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.instruction; |
| |
| RegisterType source1RegisterType = |
| analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); |
| RegisterType source2RegisterType = |
| analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterC()); |
| |
| if (BooleanCategories.get(source1RegisterType.category) && |
| BooleanCategories.get(source2RegisterType.category)) { |
| destRegisterType = RegisterType.BOOLEAN_TYPE; |
| } |
| } |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, destRegisterType); |
| } |
| |
| private void analyzeBinary2AddrOp(@Nonnull AnalyzedInstruction analyzedInstruction, |
| @Nonnull RegisterType destRegisterType, boolean checkForBoolean) { |
| if (checkForBoolean) { |
| TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; |
| |
| RegisterType source1RegisterType = |
| analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterA()); |
| RegisterType source2RegisterType = |
| analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); |
| |
| if (BooleanCategories.get(source1RegisterType.category) && |
| BooleanCategories.get(source2RegisterType.category)) { |
| destRegisterType = RegisterType.BOOLEAN_TYPE; |
| } |
| } |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, destRegisterType); |
| } |
| |
| private void analyzeLiteralBinaryOp(@Nonnull AnalyzedInstruction analyzedInstruction, |
| @Nonnull RegisterType destRegisterType, boolean checkForBoolean) { |
| if (checkForBoolean) { |
| TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; |
| |
| RegisterType sourceRegisterType = |
| analyzedInstruction.getPreInstructionRegisterType(instruction.getRegisterB()); |
| |
| if (BooleanCategories.get(sourceRegisterType.category)) { |
| int literal = ((NarrowLiteralInstruction)analyzedInstruction.instruction).getNarrowLiteral(); |
| if (literal == 0 || literal == 1) { |
| destRegisterType = RegisterType.BOOLEAN_TYPE; |
| } |
| } |
| } |
| |
| setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, destRegisterType); |
| } |
| |
| private RegisterType getDestTypeForLiteralShiftRight(@Nonnull AnalyzedInstruction analyzedInstruction, boolean signedShift) { |
| TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; |
| |
| RegisterType sourceRegisterType = getAndCheckSourceRegister(analyzedInstruction, instruction.getRegisterB(), |
| Primitive32BitCategories); |
| long literalShift = ((NarrowLiteralInstruction)analyzedInstruction.instruction).getNarrowLiteral(); |
| |
| if (literalShift == 0) { |
| return sourceRegisterType; |
| } |
| |
| RegisterType destRegisterType; |
| if (!signedShift) { |
| destRegisterType = RegisterType.INTEGER_TYPE; |
| } else { |
| destRegisterType = sourceRegisterType; |
| } |
| |
| literalShift = literalShift & 0x1f; |
| |
| switch (sourceRegisterType.category) { |
| case RegisterType.INTEGER: |
| case RegisterType.FLOAT: |
| if (!signedShift) { |
| if (literalShift > 24) { |
| return RegisterType.POS_BYTE_TYPE; |
| } |
| if (literalShift >= 16) { |
| return RegisterType.CHAR_TYPE; |
| } |
| } else { |
| if (literalShift >= 24) { |
| return RegisterType.BYTE_TYPE; |
| } |
| if (literalShift >= 16) { |
| return RegisterType.SHORT_TYPE; |
| } |
| } |
| break; |
| case RegisterType.SHORT: |
| if (signedShift && literalShift >= 8) { |
| return RegisterType.BYTE_TYPE; |
| } |
| break; |
| case RegisterType.POS_SHORT: |
| if (literalShift >= 8) { |
| return RegisterType.POS_BYTE_TYPE; |
| } |
| break; |
| case RegisterType.CHAR: |
| if (literalShift > 8) { |
| return RegisterType.POS_BYTE_TYPE; |
| } |
| break; |
| case RegisterType.BYTE: |
| break; |
| case RegisterType.POS_BYTE: |
| return RegisterType.POS_BYTE_TYPE; |
| case RegisterType.NULL: |
| case RegisterType.ONE: |
| case RegisterType.BOOLEAN: |
| return RegisterType.NULL_TYPE; |
| default: |
| assert false; |
| } |
| |
| return destRegisterType; |
| } |
| |
| |
| private void analyzeExecuteInline(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| if (inlineResolver == null) { |
| throw new AnalysisException("Cannot analyze an odexed instruction unless we are deodexing"); |
| } |
| |
| Instruction35mi instruction = (Instruction35mi)analyzedInstruction.instruction; |
| Method resolvedMethod = inlineResolver.resolveExecuteInline(analyzedInstruction); |
| |
| Opcode deodexedOpcode; |
| int acccessFlags = resolvedMethod.getAccessFlags(); |
| if (AccessFlags.STATIC.isSet(acccessFlags)) { |
| deodexedOpcode = Opcode.INVOKE_STATIC; |
| } else if (AccessFlags.PRIVATE.isSet(acccessFlags)) { |
| deodexedOpcode = Opcode.INVOKE_DIRECT; |
| } else { |
| deodexedOpcode = Opcode.INVOKE_VIRTUAL; |
| } |
| |
| Instruction35c deodexedInstruction = new ImmutableInstruction35c(deodexedOpcode, instruction.getRegisterCount(), |
| instruction.getRegisterC(), instruction.getRegisterD(), instruction.getRegisterE(), |
| instruction.getRegisterF(), instruction.getRegisterG(), resolvedMethod); |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| analyzeInstruction(analyzedInstruction); |
| } |
| |
| private void analyzeExecuteInlineRange(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| if (inlineResolver == null) { |
| throw new AnalysisException("Cannot analyze an odexed instruction unless we are deodexing"); |
| } |
| |
| Instruction3rmi instruction = (Instruction3rmi)analyzedInstruction.instruction; |
| Method resolvedMethod = inlineResolver.resolveExecuteInline(analyzedInstruction); |
| |
| Opcode deodexedOpcode; |
| int acccessFlags = resolvedMethod.getAccessFlags(); |
| if (AccessFlags.STATIC.isSet(acccessFlags)) { |
| deodexedOpcode = Opcode.INVOKE_STATIC_RANGE; |
| } else if (AccessFlags.PRIVATE.isSet(acccessFlags)) { |
| deodexedOpcode = Opcode.INVOKE_DIRECT_RANGE; |
| } else { |
| deodexedOpcode = Opcode.INVOKE_VIRTUAL_RANGE; |
| } |
| |
| Instruction3rc deodexedInstruction = new ImmutableInstruction3rc(deodexedOpcode, instruction.getStartRegister(), |
| instruction.getRegisterCount(), resolvedMethod); |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| analyzeInstruction(analyzedInstruction); |
| } |
| |
| private void analyzeInvokeDirectEmpty(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| analyzeInvokeDirectEmpty(analyzedInstruction, true); |
| } |
| |
| private void analyzeInvokeDirectEmpty(@Nonnull AnalyzedInstruction analyzedInstruction, boolean analyzeResult) { |
| Instruction35c instruction = (Instruction35c)analyzedInstruction.instruction; |
| |
| Instruction35c deodexedInstruction = new ImmutableInstruction35c(Opcode.INVOKE_DIRECT, |
| instruction.getRegisterCount(), instruction.getRegisterC(), instruction.getRegisterD(), |
| instruction.getRegisterE(), instruction.getRegisterF(), instruction.getRegisterG(), |
| instruction.getReference()); |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| |
| if (analyzeResult) { |
| analyzeInstruction(analyzedInstruction); |
| } |
| } |
| |
| private void analyzeInvokeObjectInitRange(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| analyzeInvokeObjectInitRange(analyzedInstruction, true); |
| } |
| |
| private void analyzeInvokeObjectInitRange(@Nonnull AnalyzedInstruction analyzedInstruction, boolean analyzeResult) { |
| Instruction3rc instruction = (Instruction3rc)analyzedInstruction.instruction; |
| |
| Instruction deodexedInstruction; |
| |
| int startRegister = instruction.getStartRegister(); |
| // hack: we should be using instruction.getRegisterCount, but some tweaked versions of dalvik appear |
| // to generate invoke-object-init/range instructions with an invalid register count. We know it should |
| // always be 1, so just use that. |
| int registerCount = 1; |
| if (startRegister < 16) { |
| deodexedInstruction = new ImmutableInstruction35c(Opcode.INVOKE_DIRECT, |
| registerCount, startRegister, 0, 0, 0, 0, instruction.getReference()); |
| } else { |
| deodexedInstruction = new ImmutableInstruction3rc(Opcode.INVOKE_DIRECT_RANGE, |
| startRegister, registerCount, instruction.getReference()); |
| } |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| |
| if (analyzeResult) { |
| analyzeInstruction(analyzedInstruction); |
| } |
| } |
| |
| private boolean analyzeIputIgetQuick(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| Instruction22cs instruction = (Instruction22cs)analyzedInstruction.instruction; |
| |
| int fieldOffset = instruction.getFieldOffset(); |
| RegisterType objectRegisterType = getAndCheckSourceRegister(analyzedInstruction, instruction.getRegisterB(), |
| ReferenceOrUninitCategories); |
| |
| if (objectRegisterType.category == RegisterType.NULL) { |
| return false; |
| } |
| |
| TypeProto objectRegisterTypeProto = objectRegisterType.type; |
| assert objectRegisterTypeProto != null; |
| |
| TypeProto classTypeProto = classPath.getClass(objectRegisterTypeProto.getType()); |
| FieldReference resolvedField = classTypeProto.getFieldByOffset(fieldOffset); |
| |
| if (resolvedField == null) { |
| throw new AnalysisException("Could not resolve the field in class %s at offset %d", |
| objectRegisterType.type.getType(), fieldOffset); |
| } |
| |
| ClassDef thisClass = classPath.getClassDef(method.getDefiningClass()); |
| |
| if (!TypeUtils.canAccessClass(thisClass.getType(), classPath.getClassDef(resolvedField.getDefiningClass()))) { |
| |
| // the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different |
| // than resolvedField.getDefiningClass()), and walk up the class hierarchy. |
| ClassDef fieldClass = classPath.getClassDef(objectRegisterTypeProto.getType()); |
| while (!TypeUtils.canAccessClass(thisClass.getType(), fieldClass)) { |
| String superclass = fieldClass.getSuperclass(); |
| if (superclass == null) { |
| throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s", |
| ReferenceUtil.getShortFieldDescriptor(resolvedField)); |
| } |
| |
| fieldClass = classPath.getClassDef(superclass); |
| } |
| |
| // fieldClass is now the first accessible class found. Now. we need to make sure that the field is |
| // actually valid for this class |
| FieldReference newResolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset); |
| if (newResolvedField == null) { |
| throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s", |
| ReferenceUtil.getShortFieldDescriptor(resolvedField)); |
| } |
| resolvedField = new ImmutableFieldReference(fieldClass.getType(), newResolvedField.getName(), |
| newResolvedField.getType()); |
| } |
| |
| String fieldType = resolvedField.getType(); |
| |
| Opcode opcode = classPath.getFieldInstructionMapper().getAndCheckDeodexedOpcode( |
| fieldType, instruction.getOpcode()); |
| |
| Instruction22c deodexedInstruction = new ImmutableInstruction22c(opcode, (byte)instruction.getRegisterA(), |
| (byte)instruction.getRegisterB(), resolvedField); |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| |
| analyzeInstruction(analyzedInstruction); |
| |
| return true; |
| } |
| |
| private boolean analyzeInvokeVirtual(@Nonnull AnalyzedInstruction analyzedInstruction, boolean isRange) { |
| MethodReference targetMethod; |
| |
| if (!normalizeVirtualMethods) { |
| return true; |
| } |
| |
| if (isRange) { |
| Instruction3rc instruction = (Instruction3rc)analyzedInstruction.instruction; |
| targetMethod = (MethodReference)instruction.getReference(); |
| } else { |
| Instruction35c instruction = (Instruction35c)analyzedInstruction.instruction; |
| targetMethod = (MethodReference)instruction.getReference(); |
| } |
| |
| MethodReference replacementMethod = normalizeMethodReference(targetMethod); |
| |
| if (replacementMethod == null || replacementMethod.equals(targetMethod)) { |
| return true; |
| } |
| |
| Instruction deodexedInstruction; |
| if (isRange) { |
| Instruction3rc instruction = (Instruction3rc)analyzedInstruction.instruction; |
| deodexedInstruction = new ImmutableInstruction3rc(instruction.getOpcode(), instruction.getStartRegister(), |
| instruction.getRegisterCount(), replacementMethod); |
| } else { |
| Instruction35c instruction = (Instruction35c)analyzedInstruction.instruction; |
| deodexedInstruction = new ImmutableInstruction35c(instruction.getOpcode(), instruction.getRegisterCount(), |
| instruction.getRegisterC(), instruction.getRegisterD(), instruction.getRegisterE(), |
| instruction.getRegisterF(), instruction.getRegisterG(), replacementMethod); |
| } |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| return true; |
| } |
| |
| private boolean analyzeInvokeVirtualQuick(@Nonnull AnalyzedInstruction analyzedInstruction, boolean isSuper, |
| boolean isRange) { |
| int methodIndex; |
| int objectRegister; |
| |
| if (isRange) { |
| Instruction3rms instruction = (Instruction3rms)analyzedInstruction.instruction; |
| methodIndex = instruction.getVtableIndex(); |
| objectRegister = instruction.getStartRegister(); |
| } else { |
| Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction; |
| methodIndex = instruction.getVtableIndex(); |
| objectRegister = instruction.getRegisterC(); |
| } |
| |
| RegisterType objectRegisterType = getAndCheckSourceRegister(analyzedInstruction, objectRegister, |
| ReferenceOrUninitCategories); |
| TypeProto objectRegisterTypeProto = objectRegisterType.type; |
| |
| if (objectRegisterType.category == RegisterType.NULL) { |
| return false; |
| } |
| |
| assert objectRegisterTypeProto != null; |
| |
| MethodReference resolvedMethod; |
| if (isSuper) { |
| // invoke-super is only used for the same class that we're currently in |
| TypeProto typeProto = classPath.getClass(method.getDefiningClass()); |
| TypeProto superType; |
| |
| String superclassType = typeProto.getSuperclass(); |
| if (superclassType != null) { |
| superType = classPath.getClass(superclassType); |
| } else { |
| // This is either java.lang.Object, or an UnknownClassProto |
| superType = typeProto; |
| } |
| |
| resolvedMethod = superType.getMethodByVtableIndex(methodIndex); |
| } else { |
| resolvedMethod = objectRegisterTypeProto.getMethodByVtableIndex(methodIndex); |
| } |
| |
| if (resolvedMethod == null) { |
| throw new AnalysisException("Could not resolve the method in class %s at index %d", |
| objectRegisterType.type.getType(), methodIndex); |
| } |
| |
| // no need to check class access for invoke-super. A class can obviously access its superclass. |
| ClassDef thisClass = classPath.getClassDef(method.getDefiningClass()); |
| |
| if (classPath.getClass(resolvedMethod.getDefiningClass()).isInterface()) { |
| resolvedMethod = new ReparentedMethodReference(resolvedMethod, objectRegisterTypeProto.getType()); |
| } else if (!isSuper && !TypeUtils.canAccessClass( |
| thisClass.getType(), classPath.getClassDef(resolvedMethod.getDefiningClass()))) { |
| |
| // the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different |
| // than resolvedMethod.getDefiningClass()), and walk up the class hierarchy. |
| ClassDef methodClass = classPath.getClassDef(objectRegisterTypeProto.getType()); |
| while (!TypeUtils.canAccessClass(thisClass.getType(), methodClass)) { |
| String superclass = methodClass.getSuperclass(); |
| if (superclass == null) { |
| throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s", |
| ReferenceUtil.getMethodDescriptor(resolvedMethod, true)); |
| } |
| |
| methodClass = classPath.getClassDef(superclass); |
| } |
| |
| // methodClass is now the first accessible class found. Now. we need to make sure that the method is |
| // actually valid for this class |
| MethodReference newResolvedMethod = |
| classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex); |
| if (newResolvedMethod == null) { |
| throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s", |
| ReferenceUtil.getMethodDescriptor(resolvedMethod, true)); |
| } |
| resolvedMethod = newResolvedMethod; |
| resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(), |
| resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType()); |
| |
| } |
| |
| if (normalizeVirtualMethods) { |
| MethodReference replacementMethod = normalizeMethodReference(resolvedMethod); |
| if (replacementMethod != null) { |
| resolvedMethod = replacementMethod; |
| } |
| } |
| |
| Instruction deodexedInstruction; |
| if (isRange) { |
| Instruction3rms instruction = (Instruction3rms)analyzedInstruction.instruction; |
| Opcode opcode; |
| if (isSuper) { |
| opcode = Opcode.INVOKE_SUPER_RANGE; |
| } else { |
| opcode = Opcode.INVOKE_VIRTUAL_RANGE; |
| } |
| |
| deodexedInstruction = new ImmutableInstruction3rc(opcode, instruction.getStartRegister(), |
| instruction.getRegisterCount(), resolvedMethod); |
| } else { |
| Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction; |
| Opcode opcode; |
| if (isSuper) { |
| opcode = Opcode.INVOKE_SUPER; |
| } else { |
| opcode = Opcode.INVOKE_VIRTUAL; |
| } |
| |
| deodexedInstruction = new ImmutableInstruction35c(opcode, instruction.getRegisterCount(), |
| instruction.getRegisterC(), instruction.getRegisterD(), instruction.getRegisterE(), |
| instruction.getRegisterF(), instruction.getRegisterG(), resolvedMethod); |
| } |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| analyzeInstruction(analyzedInstruction); |
| |
| return true; |
| } |
| |
| private boolean analyzePutGetVolatile(@Nonnull AnalyzedInstruction analyzedInstruction) { |
| return analyzePutGetVolatile(analyzedInstruction, true); |
| } |
| |
| private boolean analyzePutGetVolatile(@Nonnull AnalyzedInstruction analyzedInstruction, boolean analyzeResult) { |
| FieldReference field = (FieldReference)((ReferenceInstruction)analyzedInstruction.instruction).getReference(); |
| String fieldType = field.getType(); |
| |
| Opcode originalOpcode = analyzedInstruction.instruction.getOpcode(); |
| |
| Opcode opcode = classPath.getFieldInstructionMapper().getAndCheckDeodexedOpcode( |
| fieldType, originalOpcode); |
| |
| Instruction deodexedInstruction; |
| |
| if (originalOpcode.isStaticFieldAccessor()) { |
| OneRegisterInstruction instruction = (OneRegisterInstruction)analyzedInstruction.instruction; |
| deodexedInstruction = new ImmutableInstruction21c(opcode, instruction.getRegisterA(), field); |
| } else { |
| TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; |
| |
| deodexedInstruction = new ImmutableInstruction22c(opcode, instruction.getRegisterA(), |
| instruction.getRegisterB(), field); |
| } |
| |
| analyzedInstruction.setDeodexedInstruction(deodexedInstruction); |
| |
| if (analyzeResult) { |
| analyzeInstruction(analyzedInstruction); |
| } |
| return true; |
| } |
| |
| @Nonnull |
| private static RegisterType getAndCheckSourceRegister(@Nonnull AnalyzedInstruction analyzedInstruction, |
| int registerNumber, BitSet validCategories) { |
| assert registerNumber >= 0 && registerNumber < analyzedInstruction.postRegisterMap.length; |
| |
| RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(registerNumber); |
| |
| checkRegister(registerType, registerNumber, validCategories); |
| |
| if (validCategories == WideLowCategories) { |
| checkRegister(registerType, registerNumber, WideLowCategories); |
| checkWidePair(registerNumber, analyzedInstruction); |
| |
| RegisterType secondRegisterType = analyzedInstruction.getPreInstructionRegisterType(registerNumber + 1); |
| checkRegister(secondRegisterType, registerNumber+1, WideHighCategories); |
| } |
| |
| return registerType; |
| } |
| |
| private static void checkRegister(RegisterType registerType, int registerNumber, BitSet validCategories) { |
| if (!validCategories.get(registerType.category)) { |
| throw new AnalysisException(String.format("Invalid register type %s for register v%d.", |
| registerType.toString(), registerNumber)); |
| } |
| } |
| |
| private static void checkWidePair(int registerNumber, AnalyzedInstruction analyzedInstruction) { |
| if (registerNumber + 1 >= analyzedInstruction.postRegisterMap.length) { |
| throw new AnalysisException(String.format("v%d cannot be used as the first register in a wide register" + |
| "pair because it is the last register.", registerNumber)); |
| } |
| } |
| |
| @Nullable |
| private MethodReference normalizeMethodReference(@Nonnull MethodReference methodRef) { |
| TypeProto typeProto = classPath.getClass(methodRef.getDefiningClass()); |
| int methodIndex; |
| try { |
| methodIndex = typeProto.findMethodIndexInVtable(methodRef); |
| } catch (UnresolvedClassException ex) { |
| return null; |
| } |
| |
| if (methodIndex < 0) { |
| return null; |
| } |
| |
| ClassProto thisClass = (ClassProto)classPath.getClass(method.getDefiningClass()); |
| |
| Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex); |
| assert replacementMethod != null; |
| while (true) { |
| String superType = typeProto.getSuperclass(); |
| if (superType == null) { |
| break; |
| } |
| typeProto = classPath.getClass(superType); |
| Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex); |
| if (resolvedMethod == null) { |
| break; |
| } |
| |
| if (!resolvedMethod.equals(replacementMethod)) { |
| if (!AnalyzedMethodUtil.canAccess(thisClass, resolvedMethod, false, false, true)) { |
| continue; |
| } |
| |
| replacementMethod = resolvedMethod; |
| } |
| } |
| return replacementMethod; |
| } |
| |
| private static class ReparentedMethodReference extends BaseMethodReference { |
| private final MethodReference baseReference; |
| private final String definingClass; |
| |
| public ReparentedMethodReference(MethodReference baseReference, String definingClass) { |
| this.baseReference = baseReference; |
| this.definingClass = definingClass; |
| } |
| |
| @Override @Nonnull public String getName() { |
| return baseReference.getName(); |
| } |
| |
| @Override @Nonnull public List<? extends CharSequence> getParameterTypes() { |
| return baseReference.getParameterTypes(); |
| } |
| |
| @Override @Nonnull public String getReturnType() { |
| return baseReference.getReturnType(); |
| } |
| |
| @Nonnull @Override public String getDefiningClass() { |
| return definingClass; |
| } |
| } |
| } |