blob: 1a9b9ad1461b3e08864e363474b2692837ac4d68 [file] [log] [blame]
/*
* 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.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.iface.instruction.*;
import org.jf.dexlib2.iface.instruction.formats.Instruction22c;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.iface.reference.Reference;
import org.jf.dexlib2.iface.reference.TypeReference;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
/**
* The MethodAnalyzer containing this instruction
*/
@Nonnull
protected final MethodAnalyzer methodAnalyzer;
/**
* The actual instruction
*/
@Nonnull
protected Instruction instruction;
/**
* The index of the instruction, where the first instruction in the method is at index 0, and so on
*/
protected final int instructionIndex;
/**
* Instructions that can pass on execution to this one during normal execution
*/
@Nonnull
protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>();
/**
* Instructions that can execution could pass on to next during normal execution
*/
@Nonnull
protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
/**
* This contains the register types *before* the instruction has executed
*/
@Nonnull
protected final RegisterType[] preRegisterMap;
/**
* This contains the register types *after* the instruction has executed
*/
@Nonnull
protected final RegisterType[] postRegisterMap;
/**
* This contains optional register type overrides for register types from predecessors
*/
@Nullable
protected Map<PredecessorOverrideKey, RegisterType> predecessorRegisterOverrides = null;
/**
* When deodexing, we might need to deodex this instruction multiple times, when we merge in new register
* information. When this happens, we need to restore the original (odexed) instruction, so we can deodex it again
*/
protected final Instruction originalInstruction;
public AnalyzedInstruction(@Nonnull MethodAnalyzer methodAnalyzer, @Nonnull Instruction instruction,
int instructionIndex, int registerCount) {
this.methodAnalyzer = methodAnalyzer;
this.instruction = instruction;
this.originalInstruction = instruction;
this.instructionIndex = instructionIndex;
this.postRegisterMap = new RegisterType[registerCount];
this.preRegisterMap = new RegisterType[registerCount];
RegisterType unknown = RegisterType.getRegisterType(RegisterType.UNKNOWN, null);
for (int i=0; i<registerCount; i++) {
preRegisterMap[i] = unknown;
postRegisterMap[i] = unknown;
}
}
public int getInstructionIndex() {
return instructionIndex;
}
public int getPredecessorCount() {
return predecessors.size();
}
public SortedSet<AnalyzedInstruction> getPredecessors() {
return Collections.unmodifiableSortedSet(predecessors);
}
public RegisterType getPredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber) {
if (predecessorRegisterOverrides != null) {
RegisterType override = predecessorRegisterOverrides.get(
new PredecessorOverrideKey(predecessor, registerNumber));
if (override != null) {
return override;
}
}
return predecessor.postRegisterMap[registerNumber];
}
protected boolean addPredecessor(AnalyzedInstruction predecessor) {
return predecessors.add(predecessor);
}
protected void addSuccessor(AnalyzedInstruction successor) {
successors.add(successor);
}
protected void setDeodexedInstruction(Instruction instruction) {
assert originalInstruction.getOpcode().odexOnly();
this.instruction = instruction;
}
protected void restoreOdexedInstruction() {
assert originalInstruction.getOpcode().odexOnly();
instruction = originalInstruction;
}
@Nonnull
public List<AnalyzedInstruction> getSuccessors() {
return Collections.unmodifiableList(successors);
}
@Nonnull
public Instruction getInstruction() {
return instruction;
}
@Nonnull
public Instruction getOriginalInstruction() {
return originalInstruction;
}
/**
* Is this instruction a "beginning instruction". A beginning instruction is defined to be an instruction
* that can be the first successfully executed instruction in the method. The first instruction is always a
* beginning instruction. If the first instruction can throw an exception, and is covered by a try block, then
* the first instruction of any exception handler for that try block is also a beginning instruction. And likewise,
* if any of those instructions can throw an exception and are covered by try blocks, the first instruction of the
* corresponding exception handler is a beginning instruction, etc.
*
* To determine this, we simply check if the first predecessor is the fake "StartOfMethod" instruction, which has
* an instruction index of -1.
* @return a boolean value indicating whether this instruction is a beginning instruction
*/
public boolean isBeginningInstruction() {
//if this instruction has no predecessors, it is either the fake "StartOfMethod" instruction or it is an
//unreachable instruction.
if (predecessors.size() == 0) {
return false;
}
return predecessors.first().instructionIndex == -1;
}
/*
* Merges the given register type into the specified pre-instruction register, and also sets the post-instruction
* register type accordingly if it isn't a destination register for this instruction
* @param registerNumber Which register to set
* @param registerType The register type
* @returns true If the post-instruction register type was changed. This might be false if either the specified
* register is a destination register for this instruction, or if the pre-instruction register type didn't change
* after merging in the given register type
*/
protected boolean mergeRegister(int registerNumber, RegisterType registerType, BitSet verifiedInstructions,
boolean override) {
assert registerNumber >= 0 && registerNumber < postRegisterMap.length;
assert registerType != null;
RegisterType oldRegisterType = preRegisterMap[registerNumber];
RegisterType mergedRegisterType;
if (override) {
mergedRegisterType = getMergedPreRegisterTypeFromPredecessors(registerNumber);
} else {
mergedRegisterType = oldRegisterType.merge(registerType);
}
if (mergedRegisterType.equals(oldRegisterType)) {
return false;
}
preRegisterMap[registerNumber] = mergedRegisterType;
verifiedInstructions.clear(instructionIndex);
if (!setsRegister(registerNumber)) {
postRegisterMap[registerNumber] = mergedRegisterType;
return true;
}
return false;
}
/**
* Iterates over the predecessors of this instruction, and merges all the post-instruction register types for the
* given register. Any dead, unreachable, or odexed predecessor is ignored. This takes into account any overridden
* predecessor register types
*
* @param registerNumber the register number
* @return The register type resulting from merging the post-instruction register types from all predecessors
*/
@Nonnull
protected RegisterType getMergedPreRegisterTypeFromPredecessors(int registerNumber) {
RegisterType mergedRegisterType = null;
for (AnalyzedInstruction predecessor: predecessors) {
RegisterType predecessorRegisterType = getPredecessorRegisterType(predecessor, registerNumber);
if (predecessorRegisterType != null) {
if (mergedRegisterType == null) {
mergedRegisterType = predecessorRegisterType;
} else {
mergedRegisterType = predecessorRegisterType.merge(mergedRegisterType);
}
}
}
if (mergedRegisterType == null) {
// This is a start-of-method or unreachable instruction.
throw new IllegalStateException();
}
return mergedRegisterType;
}
/**
* Sets the "post-instruction" register type as indicated.
* @param registerNumber Which register to set
* @param registerType The "post-instruction" register type
* @return true if the given register type is different than the existing post-instruction register type
*/
protected boolean setPostRegisterType(int registerNumber, RegisterType registerType) {
assert registerNumber >= 0 && registerNumber < postRegisterMap.length;
assert registerType != null;
RegisterType oldRegisterType = postRegisterMap[registerNumber];
if (oldRegisterType.equals(registerType)) {
return false;
}
postRegisterMap[registerNumber] = registerType;
return true;
}
/**
* Adds an override for a register type from a predecessor.
*
* This is used to set the register type for only one branch from a conditional jump.
*
* @param predecessor Which predecessor is being overridden
* @param registerNumber The register number of the register being overridden
* @param registerType The overridden register type
* @param verifiedInstructions A bit vector of instructions that have been verified
*
* @return true if the post-instruction register type for this instruction changed as a result of this override
*/
protected boolean overridePredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber,
@Nonnull RegisterType registerType, BitSet verifiedInstructions) {
if (predecessorRegisterOverrides == null) {
predecessorRegisterOverrides = Maps.newHashMap();
}
predecessorRegisterOverrides.put(new PredecessorOverrideKey(predecessor, registerNumber), registerType);
RegisterType mergedType = getMergedPreRegisterTypeFromPredecessors(registerNumber);
if (preRegisterMap[registerNumber].equals(mergedType)) {
return false;
}
preRegisterMap[registerNumber] = mergedType;
verifiedInstructions.clear(instructionIndex);
if (!setsRegister(registerNumber)) {
if (!postRegisterMap[registerNumber].equals(mergedType)) {
postRegisterMap[registerNumber] = mergedType;
return true;
}
}
return false;
}
public boolean isInvokeInit() {
if (!instruction.getOpcode().canInitializeReference()) {
return false;
}
ReferenceInstruction instruction = (ReferenceInstruction)this.instruction;
Reference reference = instruction.getReference();
if (reference instanceof MethodReference) {
return ((MethodReference)reference).getName().equals("<init>");
}
return false;
}
/**
* Determines if this instruction sets the given register, or alters its type
*
* @param registerNumber The register to check
* @return true if this instruction sets the given register or alters its type
*/
public boolean setsRegister(int registerNumber) {
// This method could be implemented by calling getSetRegisters and checking if registerNumber is in the result
// However, this is a frequently called method, and this is a more efficient implementation, because it doesn't
// allocate a new list, and it can potentially exit earlier
if (isInvokeInit()) {
// When constructing a new object, the register type will be an uninitialized reference after the
// new-instance instruction, but becomes an initialized reference once the <init> method is called. So even
// though invoke instructions don't normally change any registers, calling an <init> method will change the
// type of its object register. If the uninitialized reference has been copied to other registers, they will
// be initialized as well, so we need to check for that too
int destinationRegister;
if (instruction instanceof FiveRegisterInstruction) {
assert ((FiveRegisterInstruction)instruction).getRegisterCount() > 0;
destinationRegister = ((FiveRegisterInstruction)instruction).getRegisterC();
} else {
assert instruction instanceof RegisterRangeInstruction;
RegisterRangeInstruction rangeInstruction = (RegisterRangeInstruction)instruction;
assert rangeInstruction.getRegisterCount() > 0;
destinationRegister = rangeInstruction.getStartRegister();
}
RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(destinationRegister);
if (preInstructionDestRegisterType.category == RegisterType.UNKNOWN) {
// We never let an uninitialized reference propagate past an invoke-init if the object register type is
// unknown This is because the uninitialized reference may be an alias to the reference being
// initialized, but we can't know that until the object register's type is known
RegisterType preInstructionRegisterType = getPreInstructionRegisterType(registerNumber);
if (preInstructionRegisterType.category == RegisterType.UNINIT_REF ||
preInstructionRegisterType.category == RegisterType.UNINIT_THIS) {
return true;
}
}
if (preInstructionDestRegisterType.category != RegisterType.UNINIT_REF &&
preInstructionDestRegisterType.category != RegisterType.UNINIT_THIS) {
return false;
}
if (registerNumber == destinationRegister) {
return true;
}
//check if the uninit ref has been copied to another register
return preInstructionDestRegisterType.equals(getPreInstructionRegisterType(registerNumber));
}
// On art, the optimizer will often nop out a check-cast instruction after an instance-of instruction.
// Normally, check-cast is where the register type actually changes.
// In order to correctly handle this case, we have to propagate the narrowed register type to the appropriate
// branch of the following if-eqz/if-nez
if (instructionIndex > 0 &&
methodAnalyzer.getClassPath().isArt() &&
getPredecessorCount() == 1 &&
(instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ)) {
AnalyzedInstruction prevInstruction = predecessors.first();
if (prevInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
MethodAnalyzer.canPropagateTypeAfterInstanceOf(
prevInstruction, this, methodAnalyzer.getClassPath())) {
Instruction22c instanceOfInstruction = (Instruction22c)prevInstruction.instruction;
if (registerNumber == instanceOfInstruction.getRegisterB()) {
return true;
}
// Additionally, there may be a move instruction just before the instance-of, in order to put the value
// into a register that is addressable by the instance-of. In this case, we also need to propagate the
// new register type for the original register that the value was moved from.
// In some cases, the instance-of may have multiple predecessors. In this case, we should only do the
// propagation if all predecessors are move-object instructions for the same source register
// TODO: do we need to do some sort of additional check that these multiple move-object predecessors actually refer to the same value?
if (instructionIndex > 1) {
int originalSourceRegister = -1;
RegisterType newType = null;
for (AnalyzedInstruction prevPrevAnalyzedInstruction : prevInstruction.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()) {
originalSourceRegister = -1;
break;
}
if (originalType.type == null) {
originalSourceRegister = -1;
break;
}
if (newType == null) {
newType = RegisterType.getRegisterType(methodAnalyzer.getClassPath(),
(TypeReference)instanceOfInstruction.getReference());
}
if (MethodAnalyzer.isNotWideningConversion(originalType, newType)) {
if (originalSourceRegister != -1) {
if (originalSourceRegister != moveInstruction.getRegisterB()) {
originalSourceRegister = -1;
break;
}
} else {
originalSourceRegister = moveInstruction.getRegisterB();
}
}
} else {
originalSourceRegister = -1;
break;
}
}
if (originalSourceRegister != -1 && registerNumber == originalSourceRegister) {
return true;
}
}
}
}
if (!instruction.getOpcode().setsRegister()) {
return false;
}
int destinationRegister = getDestinationRegister();
if (registerNumber == destinationRegister) {
return true;
}
if (instruction.getOpcode().setsWideRegister() && registerNumber == (destinationRegister + 1)) {
return true;
}
return false;
}
public List<Integer> getSetRegisters() {
List<Integer> setRegisters = Lists.newArrayList();
if (instruction.getOpcode().setsRegister()) {
setRegisters.add(getDestinationRegister());
}
if (instruction.getOpcode().setsWideRegister()) {
setRegisters.add(getDestinationRegister() + 1);
}
if (isInvokeInit()) {
//When constructing a new object, the register type will be an uninitialized reference after the new-instance
//instruction, but becomes an initialized reference once the <init> method is called. So even though invoke
//instructions don't normally change any registers, calling an <init> method will change the type of its
//object register. If the uninitialized reference has been copied to other registers, they will be initialized
//as well, so we need to check for that too
int destinationRegister;
if (instruction instanceof FiveRegisterInstruction) {
destinationRegister = ((FiveRegisterInstruction)instruction).getRegisterC();
assert ((FiveRegisterInstruction)instruction).getRegisterCount() > 0;
} else {
assert instruction instanceof RegisterRangeInstruction;
RegisterRangeInstruction rangeInstruction = (RegisterRangeInstruction)instruction;
assert rangeInstruction.getRegisterCount() > 0;
destinationRegister = rangeInstruction.getStartRegister();
}
RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(destinationRegister);
if (preInstructionDestRegisterType.category == RegisterType.UNINIT_REF ||
preInstructionDestRegisterType.category == RegisterType.UNINIT_THIS) {
setRegisters.add(destinationRegister);
RegisterType objectRegisterType = preRegisterMap[destinationRegister];
for (int i = 0; i < preRegisterMap.length; i++) {
if (i == destinationRegister) {
continue;
}
RegisterType preInstructionRegisterType = preRegisterMap[i];
if (preInstructionRegisterType.equals(objectRegisterType)) {
setRegisters.add(i);
} else if (preInstructionRegisterType.category == RegisterType.UNINIT_REF ||
preInstructionRegisterType.category == RegisterType.UNINIT_THIS) {
RegisterType postInstructionRegisterType = postRegisterMap[i];
if (postInstructionRegisterType.category == RegisterType.UNKNOWN) {
setRegisters.add(i);
}
}
}
} else if (preInstructionDestRegisterType.category == RegisterType.UNKNOWN) {
// We never let an uninitialized reference propagate past an invoke-init if the object register type is
// unknown This is because the uninitialized reference may be an alias to the reference being
// initialized, but we can't know that until the object register's type is known
for (int i = 0; i < preRegisterMap.length; i++) {
RegisterType registerType = preRegisterMap[i];
if (registerType.category == RegisterType.UNINIT_REF ||
registerType.category == RegisterType.UNINIT_THIS) {
setRegisters.add(i);
}
}
}
}
// On art, the optimizer will often nop out a check-cast instruction after an instance-of instruction.
// Normally, check-cast is where the register type actually changes.
// In order to correctly handle this case, we have to propagate the narrowed register type to the appropriate
// branch of the following if-eqz/if-nez
if (instructionIndex > 0 &&
methodAnalyzer.getClassPath().isArt() &&
getPredecessorCount() == 1 &&
(instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ)) {
AnalyzedInstruction prevInstruction = predecessors.first();
if (prevInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
MethodAnalyzer.canPropagateTypeAfterInstanceOf(
prevInstruction, this, methodAnalyzer.getClassPath())) {
Instruction22c instanceOfInstruction = (Instruction22c)prevInstruction.instruction;
setRegisters.add(instanceOfInstruction.getRegisterB());
// Additionally, there may be a move instruction just before the instance-of, in order to put the value
// into a register that is addressable by the instance-of. In this case, we also need to propagate the
// new register type for the original register that the value was moved from.
// In some cases, the instance-of may have multiple predecessors. In this case, we should only do the
// propagation if all predecessors are move-object instructions for the same source register
// TODO: do we need to do some sort of additional check that these multiple move-object predecessors actually refer to the same value?
if (instructionIndex > 1) {
int originalSourceRegister = -1;
RegisterType newType = null;
for (AnalyzedInstruction prevPrevAnalyzedInstruction : prevInstruction.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()) {
originalSourceRegister = -1;
break;
}
if (originalType.type == null) {
originalSourceRegister = -1;
break;
}
if (newType == null) {
newType = RegisterType.getRegisterType(methodAnalyzer.getClassPath(),
(TypeReference)instanceOfInstruction.getReference());
}
if (MethodAnalyzer.isNotWideningConversion(originalType, newType)) {
if (originalSourceRegister != -1) {
if (originalSourceRegister != moveInstruction.getRegisterB()) {
originalSourceRegister = -1;
break;
}
} else {
originalSourceRegister = moveInstruction.getRegisterB();
}
}
} else {
originalSourceRegister = -1;
break;
}
}
if (originalSourceRegister != -1) {
setRegisters.add(originalSourceRegister);
}
}
}
}
return setRegisters;
}
public int getDestinationRegister() {
if (!this.instruction.getOpcode().setsRegister()) {
throw new ExceptionWithContext("Cannot call getDestinationRegister() for an instruction that doesn't " +
"store a value");
}
return ((OneRegisterInstruction)instruction).getRegisterA();
}
public int getRegisterCount() {
return postRegisterMap.length;
}
@Nonnull
public RegisterType getPostInstructionRegisterType(int registerNumber) {
return postRegisterMap[registerNumber];
}
@Nonnull
public RegisterType getPreInstructionRegisterType(int registerNumber) {
return preRegisterMap[registerNumber];
}
public int compareTo(@Nonnull AnalyzedInstruction analyzedInstruction) {
if (instructionIndex < analyzedInstruction.instructionIndex) {
return -1;
} else if (instructionIndex == analyzedInstruction.instructionIndex) {
return 0;
} else {
return 1;
}
}
private static class PredecessorOverrideKey {
public final AnalyzedInstruction analyzedInstruction;
public final int registerNumber;
public PredecessorOverrideKey(AnalyzedInstruction analyzedInstruction, int registerNumber) {
this.analyzedInstruction = analyzedInstruction;
this.registerNumber = registerNumber;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PredecessorOverrideKey that = (PredecessorOverrideKey)o;
return com.google.common.base.Objects.equal(registerNumber, that.registerNumber) &&
Objects.equal(analyzedInstruction, that.analyzedInstruction);
}
@Override public int hashCode() {
return Objects.hashCode(analyzedInstruction, registerNumber);
}
}
}