1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package dexfuzz.rawdex;
18
19import dexfuzz.Log;
20import dexfuzz.program.MutatableCode;
21
22import java.io.IOException;
23import java.util.LinkedList;
24import java.util.List;
25
26public class CodeItem implements RawDexObject {
27  public short registersSize;
28  public short insSize;
29  public short outsSize;
30  public short triesSize;
31  public int debugInfoOff; // NB: this is a special case
32  public int insnsSize;
33  public List<Instruction> insns;
34  public TryItem[] tries;
35  public EncodedCatchHandlerList handlers;
36
37  private MutatableCode mutatableCode;
38
39  public static class MethodMetaInfo {
40    public String methodName;
41    public boolean isStatic;
42    public String shorty;
43  }
44
45  public MethodMetaInfo meta = new MethodMetaInfo();
46
47  @Override
48  public void read(DexRandomAccessFile file) throws IOException {
49    file.alignForwards(4);
50    file.getOffsetTracker().getNewOffsettable(file, this);
51    registersSize = file.readUShort();
52    insSize = file.readUShort();
53    outsSize = file.readUShort();
54    triesSize = file.readUShort();
55    debugInfoOff = file.readUInt();
56    insnsSize = file.readUInt();
57    populateInstructionList(file);
58    if (triesSize > 0) {
59      if ((insnsSize % 2) != 0) {
60        // Consume padding.
61        file.readUShort();
62      }
63      tries = new TryItem[triesSize];
64      for (int i = 0; i < triesSize; i++) {
65        (tries[i] = new TryItem()).read(file);
66      }
67      (handlers = new EncodedCatchHandlerList()).read(file);
68    }
69  }
70
71  private void populateInstructionList(DexRandomAccessFile file) throws IOException {
72    insns = new LinkedList<Instruction>();
73    long insnsOffset = file.getFilePointer();
74    if (insnsOffset != 0) {
75      long finger = insnsOffset;
76      long insnsEnd = insnsOffset + (2 * insnsSize);
77
78      while (finger < insnsEnd) {
79        file.seek(finger);
80        Instruction newInsn = new Instruction();
81        newInsn.read(file);
82        insns.add(newInsn);
83        finger += (2 * newInsn.getSize());
84      }
85
86      file.seek(finger);
87    }
88  }
89
90  @Override
91  public void write(DexRandomAccessFile file) throws IOException {
92    file.alignForwards(4);
93    file.getOffsetTracker().updatePositionOfNextOffsettable(file);
94    file.writeUShort(registersSize);
95    file.writeUShort(insSize);
96    file.writeUShort(outsSize);
97    file.writeUShort(triesSize);
98    // We do not support retaining debug info currently.
99    file.writeUInt(0 /*debug_info_off*/);
100    file.writeUInt(insnsSize);
101    for (Instruction insn : insns) {
102      insn.write(file);
103    }
104    if (triesSize > 0) {
105      if ((insnsSize % 2) != 0) {
106        // produce padding
107        file.writeUShort((short) 0);
108      }
109      for (TryItem tryItem : tries) {
110        tryItem.write(file);
111      }
112      handlers.write(file);
113    }
114  }
115
116  /**
117   * CodeTranslator should call this to notify a CodeItem about its
118   * mutatable code, so it can always get the "latest" view of its
119   * instructions.
120   */
121  public void registerMutatableCode(MutatableCode mutatableCode) {
122    this.mutatableCode = mutatableCode;
123  }
124
125  @Override
126  public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
127    if (kind == IndexUpdateKind.TYPE_ID && triesSize > 0) {
128      // EncodedCatchHandlerList (well, the EncodedTypeAddrPairs it owns)
129      // are only interested in TYPE_IDs.
130      handlers.incrementIndex(kind, insertedIdx);
131    }
132
133    if (kind == IndexUpdateKind.PROTO_ID) {
134      // The only kind we can't encounter in an instruction.
135      return;
136    }
137
138    List<Instruction> insnsToIncrement = insns;
139
140    // If we have an associated MutatableCode, then it may have created some new insns
141    // that we won't know about yet, during the mutation phase.
142    //
143    // Ask for the latest view of the insns.
144    if (mutatableCode != null) {
145      insnsToIncrement = mutatableCode.requestLatestInstructions();
146    }
147
148    for (Instruction insn : insnsToIncrement) {
149      Opcode opcode = insn.info.opcode;
150      switch (kind) {
151        case STRING_ID:
152          if (opcode == Opcode.CONST_STRING || opcode == Opcode.CONST_STRING_JUMBO) {
153            // STRING@BBBB
154            if (insn.vregB >= insertedIdx) {
155              insn.vregB++;
156            }
157          }
158          break;
159        case TYPE_ID:
160          if (opcode == Opcode.CONST_CLASS
161              || opcode == Opcode.CHECK_CAST
162              || opcode == Opcode.NEW_INSTANCE
163              || opcode == Opcode.FILLED_NEW_ARRAY
164              || opcode == Opcode.FILLED_NEW_ARRAY_RANGE) {
165            // TYPE@BBBB
166            if (insn.vregB >= insertedIdx) {
167              insn.vregB++;
168            }
169          } else if (opcode == Opcode.INSTANCE_OF || opcode == Opcode.NEW_ARRAY) {
170            // TYPE@CCCC
171            if (insn.vregC >= insertedIdx) {
172              insn.vregC++;
173            }
174          }
175          break;
176        case FIELD_ID:
177          if (Opcode.isBetween(opcode, Opcode.SGET, Opcode.SPUT_SHORT)) {
178            // FIELD@BBBB
179            if (insn.vregB >= insertedIdx) {
180              insn.vregB++;
181            }
182          } else if (Opcode.isBetween(opcode, Opcode.IGET, Opcode.IPUT_SHORT)) {
183            // FIELD@CCCC
184            if (insn.vregC >= insertedIdx) {
185              insn.vregC++;
186            }
187          }
188          break;
189        case METHOD_ID:
190          if (Opcode.isBetween(opcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE)
191              || Opcode.isBetween(opcode,
192                  Opcode.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_INTERFACE_RANGE)) {
193            // METHOD@BBBB
194            if (insn.vregB >= insertedIdx) {
195              insn.vregB++;
196            }
197          }
198          break;
199        default:
200          Log.errorAndQuit("Unexpected IndexUpdateKind requested "
201              + "in Instruction.incrementIndex()");
202      }
203    }
204  }
205}
206