/* * 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; } }