Home | History | Annotate | Download | only in impl
      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 
     62     public SmaliInstruction() {
     63         super(SmaliElementTypes.INSTRUCTION);
     64     }
     65 
     66     @NotNull public SmaliMethod getParentMethod() {
     67         SmaliMethod smaliMethod = findAncestorByClass(SmaliMethod.class);
     68         assert smaliMethod != null;
     69         return smaliMethod;
     70     }
     71 
     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 
     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 
    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
    122     public SmaliLabelReference getTarget() {
    123         return findChildByClass(SmaliLabelReference.class);
    124     }
    125 
    126     public int getRegisterCount() {
    127         return findChildrenByType(SmaliElementTypes.REGISTER_REFERENCE).size();
    128     }
    129 
    130     @Nullable
    131     public SmaliLiteral getLiteral() {
    132         return findChildByClass(SmaliLiteral.class);
    133     }
    134 
    135     @Nullable
    136     public SmaliTypeElement getTypeReference() {
    137         return findChildByClass(SmaliTypeElement.class);
    138     }
    139 
    140     @Nullable
    141     public SmaliFieldReference getFieldReference() {
    142         return findChildByClass(SmaliFieldReference.class);
    143     }
    144 
    145     @Nullable
    146     public SmaliMethodReference getMethodReference() {
    147         return findChildByClass(SmaliMethodReference.class);
    148     }
    149 
    150     @Nullable
    151     public SmaliLiteral getPackedSwitchStartKey() {
    152         return findChildByClass(SmaliLiteral.class);
    153     }
    154 
    155     @NotNull
    156     public List<SmaliPackedSwitchElement> getPackedSwitchElements() {
    157         return Arrays.asList(findChildrenByClass(SmaliPackedSwitchElement.class));
    158     }
    159 
    160     @NotNull
    161     public List<SmaliSparseSwitchElement> getSparseSwitchElements() {
    162         return Arrays.asList(findChildrenByClass(SmaliSparseSwitchElement.class));
    163     }
    164 
    165     @Nullable
    166     public SmaliLiteral getArrayDataWidth() {
    167         return findChildByClass(SmaliLiteral.class);
    168     }
    169 
    170     @NotNull
    171     public List<SmaliArrayDataElement> getArrayDataElements() {
    172         return Arrays.asList(findChildrenByClass(SmaliArrayDataElement.class));
    173     }
    174 
    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
    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