1 /* 2 * Copyright 2014, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package org.jf.smalidea.psi.impl; 33 34 import com.google.common.base.Preconditions; 35 import com.intellij.lang.ASTNode; 36 import org.jetbrains.annotations.NotNull; 37 import org.jetbrains.annotations.Nullable; 38 import org.jf.dexlib2.Format; 39 import org.jf.dexlib2.Opcode; 40 import org.jf.dexlib2.Opcodes; 41 import org.jf.dexlib2.analysis.AnalyzedInstruction; 42 import org.jf.dexlib2.analysis.MethodAnalyzer; 43 import org.jf.smalidea.SmaliTokens; 44 import org.jf.smalidea.psi.SmaliCompositeElementFactory; 45 import org.jf.smalidea.psi.SmaliElementTypes; 46 47 import java.util.Arrays; 48 import java.util.List; 49 50 public class SmaliInstruction extends SmaliCompositeElement { 51 private static final int NO_OFFSET = -1; 52 53 @Nullable private Opcode opcode; 54 private int offset = NO_OFFSET; 55 56 public static final SmaliCompositeElementFactory FACTORY = new SmaliCompositeElementFactory() { 57 @Override public SmaliCompositeElement createElement() { 58 return new SmaliInstruction(); 59 } 60 }; 61 SmaliInstruction()62 public SmaliInstruction() { 63 super(SmaliElementTypes.INSTRUCTION); 64 } 65 getParentMethod()66 @NotNull public SmaliMethod getParentMethod() { 67 SmaliMethod smaliMethod = findAncestorByClass(SmaliMethod.class); 68 assert smaliMethod != null; 69 return smaliMethod; 70 } 71 getOpcode()72 @NotNull public Opcode getOpcode() { 73 if (opcode == null) { 74 ASTNode instructionNode = findChildByType(SmaliTokens.INSTRUCTION_TOKENS); 75 // this should be impossible, based on the parser definition 76 assert instructionNode != null; 77 78 // TODO: put a project level Opcodes instance with the appropriate api level somewhere 79 opcode = Opcodes.getDefault().getOpcodeByName(instructionNode.getText()); 80 if (opcode == null) { 81 if (instructionNode.getText().equals(".packed-switch")) { 82 return Opcode.PACKED_SWITCH_PAYLOAD; 83 } 84 if (instructionNode.getText().equals(".sparse-switch")) { 85 return Opcode.SPARSE_SWITCH_PAYLOAD; 86 } 87 if (instructionNode.getText().equals(".array-data")) { 88 return Opcode.ARRAY_PAYLOAD; 89 } 90 assert false; 91 } 92 } 93 return opcode; 94 } 95 getOffset()96 public int getOffset() { 97 // TODO: don't calculate this recursively. ugh! 98 if (offset == NO_OFFSET) { 99 SmaliInstruction previousInstruction = findPrevSiblingByClass(SmaliInstruction.class); 100 if (previousInstruction == null) { 101 offset = 0; 102 } else { 103 offset = previousInstruction.getOffset() + previousInstruction.getInstructionSize(); 104 } 105 } 106 return offset; 107 } 108 getRegister(int registerIndex)109 public int getRegister(int registerIndex) { 110 Preconditions.checkArgument(registerIndex >= 0); 111 112 List<ASTNode> registers = findChildrenByType(SmaliElementTypes.REGISTER_REFERENCE); 113 if (registerIndex >= registers.size()) { 114 return -1; 115 } 116 117 SmaliRegisterReference registerReference = (SmaliRegisterReference)registers.get(registerIndex); 118 return registerReference.getRegisterNumber(); 119 } 120 121 @Nullable getTarget()122 public SmaliLabelReference getTarget() { 123 return findChildByClass(SmaliLabelReference.class); 124 } 125 getRegisterCount()126 public int getRegisterCount() { 127 return findChildrenByType(SmaliElementTypes.REGISTER_REFERENCE).size(); 128 } 129 130 @Nullable getLiteral()131 public SmaliLiteral getLiteral() { 132 return findChildByClass(SmaliLiteral.class); 133 } 134 135 @Nullable getTypeReference()136 public SmaliTypeElement getTypeReference() { 137 return findChildByClass(SmaliTypeElement.class); 138 } 139 140 @Nullable getFieldReference()141 public SmaliFieldReference getFieldReference() { 142 return findChildByClass(SmaliFieldReference.class); 143 } 144 145 @Nullable getMethodReference()146 public SmaliMethodReference getMethodReference() { 147 return findChildByClass(SmaliMethodReference.class); 148 } 149 150 @Nullable getPackedSwitchStartKey()151 public SmaliLiteral getPackedSwitchStartKey() { 152 return findChildByClass(SmaliLiteral.class); 153 } 154 155 @NotNull getPackedSwitchElements()156 public List<SmaliPackedSwitchElement> getPackedSwitchElements() { 157 return Arrays.asList(findChildrenByClass(SmaliPackedSwitchElement.class)); 158 } 159 160 @NotNull getSparseSwitchElements()161 public List<SmaliSparseSwitchElement> getSparseSwitchElements() { 162 return Arrays.asList(findChildrenByClass(SmaliSparseSwitchElement.class)); 163 } 164 165 @Nullable getArrayDataWidth()166 public SmaliLiteral getArrayDataWidth() { 167 return findChildByClass(SmaliLiteral.class); 168 } 169 170 @NotNull getArrayDataElements()171 public List<SmaliArrayDataElement> getArrayDataElements() { 172 return Arrays.asList(findChildrenByClass(SmaliArrayDataElement.class)); 173 } 174 getInstructionSize()175 public int getInstructionSize() { 176 Opcode opcode = getOpcode(); 177 if (!opcode.format.isPayloadFormat) { 178 return opcode.format.size; 179 } else if (opcode.format == Format.ArrayPayload) { 180 int elementWidth = (int)getArrayDataWidth().getIntegralValue(); 181 int elementCount = getArrayDataElements().size(); 182 183 return 8 + (elementWidth * elementCount + 1); 184 } else if (opcode.format == Format.PackedSwitchPayload) { 185 return 8 + getPackedSwitchElements().size() * 4; 186 } else if (opcode.format == Format.SparseSwitchPayload) { 187 return 2 + getSparseSwitchElements().size() * 4; 188 } 189 assert false; 190 throw new RuntimeException(); 191 } 192 193 private AnalyzedInstruction analyzedInstruction = null; 194 195 @Nullable getAnalyzedInstructionFromMethod()196 private AnalyzedInstruction getAnalyzedInstructionFromMethod() { 197 SmaliMethod method = getParentMethod(); 198 199 MethodAnalyzer analyzer = method.getMethodAnalyzer(); 200 if (analyzer == null) { 201 return null; 202 } 203 204 int thisOffset = this.getOffset() / 2; 205 int codeOffset = 0; 206 207 for (AnalyzedInstruction instruction: analyzer.getAnalyzedInstructions()) { 208 if (codeOffset == thisOffset) { 209 return instruction; 210 } 211 assert codeOffset < thisOffset; 212 213 codeOffset += instruction.getOriginalInstruction().getCodeUnits(); 214 } 215 assert false; 216 return null; 217 } 218 219 @Nullable 220 public AnalyzedInstruction getAnalyzedInstruction() { 221 if (analyzedInstruction == null) { 222 analyzedInstruction = getAnalyzedInstructionFromMethod(); 223 } 224 return analyzedInstruction; 225 } 226 227 @Override public void clearCaches() { 228 super.clearCaches(); 229 analyzedInstruction = null; 230 } 231 } 232