blob: ecbdbb338e377dcb8d6c27f8fee88d5b5d6b023c [file] [log] [blame]
/*
* Copyright 2014, 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.smalidea.psi.impl;
import com.google.common.base.Preconditions;
import com.intellij.lang.ASTNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jf.dexlib2.Format;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.AnalyzedInstruction;
import org.jf.dexlib2.analysis.MethodAnalyzer;
import org.jf.smalidea.SmaliTokens;
import org.jf.smalidea.psi.SmaliCompositeElementFactory;
import org.jf.smalidea.psi.SmaliElementTypes;
import java.util.Arrays;
import java.util.List;
public class SmaliInstruction extends SmaliCompositeElement {
private static final int NO_OFFSET = -1;
@Nullable private Opcode opcode;
private int offset = NO_OFFSET;
public static final SmaliCompositeElementFactory FACTORY = new SmaliCompositeElementFactory() {
@Override public SmaliCompositeElement createElement() {
return new SmaliInstruction();
}
};
public SmaliInstruction() {
super(SmaliElementTypes.INSTRUCTION);
}
@NotNull public SmaliMethod getParentMethod() {
SmaliMethod smaliMethod = findAncestorByClass(SmaliMethod.class);
assert smaliMethod != null;
return smaliMethod;
}
@NotNull public Opcode getOpcode() {
if (opcode == null) {
ASTNode instructionNode = findChildByType(SmaliTokens.INSTRUCTION_TOKENS);
// this should be impossible, based on the parser definition
assert instructionNode != null;
// TODO: put a project level Opcodes instance with the appropriate api level somewhere
opcode = Opcodes.getDefault().getOpcodeByName(instructionNode.getText());
if (opcode == null) {
if (instructionNode.getText().equals(".packed-switch")) {
return Opcode.PACKED_SWITCH_PAYLOAD;
}
if (instructionNode.getText().equals(".sparse-switch")) {
return Opcode.SPARSE_SWITCH_PAYLOAD;
}
if (instructionNode.getText().equals(".array-data")) {
return Opcode.ARRAY_PAYLOAD;
}
assert false;
}
}
return opcode;
}
public int getOffset() {
// TODO: don't calculate this recursively. ugh!
if (offset == NO_OFFSET) {
SmaliInstruction previousInstruction = findPrevSiblingByClass(SmaliInstruction.class);
if (previousInstruction == null) {
offset = 0;
} else {
offset = previousInstruction.getOffset() + previousInstruction.getInstructionSize();
}
}
return offset;
}
public int getRegister(int registerIndex) {
Preconditions.checkArgument(registerIndex >= 0);
List<ASTNode> registers = findChildrenByType(SmaliElementTypes.REGISTER_REFERENCE);
if (registerIndex >= registers.size()) {
return -1;
}
SmaliRegisterReference registerReference = (SmaliRegisterReference)registers.get(registerIndex);
return registerReference.getRegisterNumber();
}
@Nullable
public SmaliLabelReference getTarget() {
return findChildByClass(SmaliLabelReference.class);
}
public int getRegisterCount() {
return findChildrenByType(SmaliElementTypes.REGISTER_REFERENCE).size();
}
@Nullable
public SmaliLiteral getLiteral() {
return findChildByClass(SmaliLiteral.class);
}
@Nullable
public SmaliTypeElement getTypeReference() {
return findChildByClass(SmaliTypeElement.class);
}
@Nullable
public SmaliFieldReference getFieldReference() {
return findChildByClass(SmaliFieldReference.class);
}
@Nullable
public SmaliMethodReference getMethodReference() {
return findChildByClass(SmaliMethodReference.class);
}
@Nullable
public SmaliLiteral getPackedSwitchStartKey() {
return findChildByClass(SmaliLiteral.class);
}
@NotNull
public List<SmaliPackedSwitchElement> getPackedSwitchElements() {
return Arrays.asList(findChildrenByClass(SmaliPackedSwitchElement.class));
}
@NotNull
public List<SmaliSparseSwitchElement> getSparseSwitchElements() {
return Arrays.asList(findChildrenByClass(SmaliSparseSwitchElement.class));
}
@Nullable
public SmaliLiteral getArrayDataWidth() {
return findChildByClass(SmaliLiteral.class);
}
@NotNull
public List<SmaliArrayDataElement> getArrayDataElements() {
return Arrays.asList(findChildrenByClass(SmaliArrayDataElement.class));
}
public int getInstructionSize() {
Opcode opcode = getOpcode();
if (!opcode.format.isPayloadFormat) {
return opcode.format.size;
} else if (opcode.format == Format.ArrayPayload) {
int elementWidth = (int)getArrayDataWidth().getIntegralValue();
int elementCount = getArrayDataElements().size();
return 8 + (elementWidth * elementCount + 1);
} else if (opcode.format == Format.PackedSwitchPayload) {
return 8 + getPackedSwitchElements().size() * 4;
} else if (opcode.format == Format.SparseSwitchPayload) {
return 2 + getSparseSwitchElements().size() * 4;
}
assert false;
throw new RuntimeException();
}
private AnalyzedInstruction analyzedInstruction = null;
@Nullable
private AnalyzedInstruction getAnalyzedInstructionFromMethod() {
SmaliMethod method = getParentMethod();
MethodAnalyzer analyzer = method.getMethodAnalyzer();
if (analyzer == null) {
return null;
}
int thisOffset = this.getOffset() / 2;
int codeOffset = 0;
for (AnalyzedInstruction instruction: analyzer.getAnalyzedInstructions()) {
if (codeOffset == thisOffset) {
return instruction;
}
assert codeOffset < thisOffset;
codeOffset += instruction.getOriginalInstruction().getCodeUnits();
}
assert false;
return null;
}
@Nullable
public AnalyzedInstruction getAnalyzedInstruction() {
if (analyzedInstruction == null) {
analyzedInstruction = getAnalyzedInstructionFromMethod();
}
return analyzedInstruction;
}
@Override public void clearCaches() {
super.clearCaches();
analyzedInstruction = null;
}
}