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.program;
18
19import dexfuzz.Log;
20import dexfuzz.rawdex.Instruction;
21import dexfuzz.rawdex.Opcode;
22
23import java.util.ArrayList;
24import java.util.Collections;
25import java.util.LinkedList;
26import java.util.List;
27
28/**
29 * A class that represents a CodeItem in a way that is more amenable to mutation.
30 */
31public class MutatableCode {
32  /**
33   * To ensure we update the correct CodeItem in the raw DEX file.
34   */
35  public int codeItemIdx;
36
37  /**
38   * This is an index into the Program's list of MutatableCodes.
39   */
40  public int mutatableCodeIdx;
41
42  /**
43   * Number of registers this code uses.
44   */
45  public short registersSize;
46
47  /**
48   * Number of ins this code has.
49   */
50  public short insSize;
51
52  /**
53   * Number of outs this code has.
54   */
55  public short outsSize;
56
57  /**
58   * Number of tries this code has.
59   */
60  public short triesSize;
61
62  /**
63   * CodeTranslator is responsible for creating this, and
64   * converting it back to a list of Instructions.
65   */
66  private List<MInsn> mutatableInsns;
67
68  /**
69   * CodeTranslator is responsible for creating this, and
70   * converting it back to the correct form for CodeItems.
71   */
72  public List<MTryBlock> mutatableTries;
73
74  /**
75   * The name of the method this code represents.
76   */
77  public String name;
78  public String shorty;
79  public boolean isStatic;
80
81  /**
82   * The Program that owns this MutatableCode.
83   * Currently used to get the size of constant pools for
84   * PoolIndexChanger/RandomInstructionGenerator
85   */
86  public Program program;
87
88  private short originalInVReg;
89  private short tempVRegsAllocated;
90  private short initialTempVReg;
91  private boolean vregsNeedCopying;
92  private int numMoveInsnsGenerated;
93
94  public MutatableCode(Program program) {
95    this.program = program;
96    this.mutatableInsns = new LinkedList<MInsn>();
97  }
98
99  /**
100   * Call this to update all instructions after the provided mInsn, to have their
101   * locations adjusted by the provided offset. It will also mark that they have been updated.
102   */
103  public void updateInstructionLocationsAfter(MInsn mInsn, int offset) {
104    boolean updating = false;
105    for (MInsn mInsnChecking : mutatableInsns) {
106      if (updating) {
107        mInsnChecking.locationUpdated = true;
108        mInsnChecking.location += offset;
109      } else {
110        if (mInsnChecking == mInsn) {
111          updating = true;
112        }
113      }
114
115    }
116  }
117
118  private void recalculateLocations() {
119    int loc = 0;
120    for (MInsn mInsn : mutatableInsns) {
121      mInsn.location = loc;
122      loc += mInsn.insn.getSize();
123    }
124  }
125
126  public List<MInsn> getInstructions() {
127    return Collections.unmodifiableList(mutatableInsns);
128  }
129
130  public int getInstructionCount() {
131    return mutatableInsns.size();
132  }
133
134  public int getInstructionIndex(MInsn mInsn) {
135    return mutatableInsns.indexOf(mInsn);
136  }
137
138  public MInsn getInstructionAt(int idx) {
139    return mutatableInsns.get(idx);
140  }
141
142  public void addInstructionToEnd(MInsn mInsn) {
143    mutatableInsns.add(mInsn);
144  }
145
146  public void insertInstructionAfter(MInsn toBeInserted, int insertionIdx) {
147    if ((insertionIdx + 1) < mutatableInsns.size()) {
148      insertInstructionAt(toBeInserted, insertionIdx + 1);
149    } else {
150      // Appending to end.
151      MInsn finalInsn = mutatableInsns.get(mutatableInsns.size() - 1);
152      toBeInserted.location = finalInsn.location + finalInsn.insn.getSize();
153      mutatableInsns.add(toBeInserted);
154    }
155  }
156
157  /**
158   * Has same semantics as List.add()
159   */
160  public void insertInstructionAt(MInsn toBeInserted, int insertionIdx) {
161    MInsn currentInsn = mutatableInsns.get(insertionIdx);
162    toBeInserted.location = currentInsn.location;
163    mutatableInsns.add(insertionIdx , toBeInserted);
164    updateInstructionLocationsAfter(toBeInserted, toBeInserted.insn.getSize());
165  }
166
167  /**
168   * Checks if any MTryBlock's instruction refs pointed at the 'before' MInsn,
169   * and points them to the 'after' MInsn, if so. 'twoWay' will check if 'after'
170   * was pointed to, and point refs to the 'before' insn.
171   * (one-way is used when deleting instructions,
172   * two-way is used when swapping instructions.)
173   */
174  private void updateTryBlocksWithReplacementInsn(MInsn before, MInsn after,
175      boolean twoWay) {
176    if (triesSize > 0) {
177      for (MTryBlock mTryBlock : mutatableTries) {
178        if (mTryBlock.startInsn == before) {
179          Log.debug("Try block's first instruction was updated");
180          mTryBlock.startInsn = after;
181        } else if (twoWay && mTryBlock.startInsn == after) {
182          Log.debug("Try block's first instruction was updated");
183          mTryBlock.startInsn = before;
184        }
185        if (mTryBlock.endInsn == before) {
186          Log.debug("Try block's last instruction was updated");
187          mTryBlock.endInsn = after;
188        } else if (twoWay && mTryBlock.endInsn == after) {
189          Log.debug("Try block's last instruction was updated");
190          mTryBlock.endInsn = before;
191        }
192        if (mTryBlock.catchAllHandler == before) {
193          Log.debug("Try block's catch-all instruction was updated");
194          mTryBlock.catchAllHandler = after;
195        } else if (twoWay && mTryBlock.catchAllHandler == after) {
196          Log.debug("Try block's catch-all instruction was updated");
197          mTryBlock.catchAllHandler = before;
198        }
199
200        List<Integer> matchesIndicesToChange = new ArrayList<Integer>();
201        List<Integer> replacementIndicesToChange = null;
202        if (twoWay) {
203          replacementIndicesToChange = new ArrayList<Integer>();
204        }
205
206        int idx = 0;
207        for (MInsn handler : mTryBlock.handlers) {
208          if (handler == before) {
209            matchesIndicesToChange.add(idx);
210            Log.debug("Try block's handler instruction was updated");
211          } else if (twoWay && handler == after) {
212            replacementIndicesToChange.add(idx);
213            Log.debug("Try block's handler instruction was updated");
214          }
215          idx++;
216        }
217
218        for (int idxToChange : matchesIndicesToChange) {
219          mTryBlock.handlers.set(idxToChange, after);
220        }
221
222        if (twoWay) {
223          for (int idxToChange : replacementIndicesToChange) {
224            mTryBlock.handlers.set(idxToChange, before);
225          }
226        }
227      }
228    }
229  }
230
231  /**
232   * The actual implementation of deleteInstruction called by
233   * the single-argument deleteInstructions.
234   */
235  private void deleteInstructionFull(MInsn toBeDeleted, int toBeDeletedIdx) {
236    // Make sure we update all locations afterwards first.
237    updateInstructionLocationsAfter(toBeDeleted, -(toBeDeleted.insn.getSize()));
238
239    // Remove it.
240    mutatableInsns.remove(toBeDeletedIdx);
241
242    // Update any branch instructions that branched to the instruction we just deleted!
243
244    // First, pick the replacement target.
245    int replacementTargetIdx = toBeDeletedIdx;
246    if (replacementTargetIdx == mutatableInsns.size()) {
247      replacementTargetIdx--;
248    }
249    MInsn replacementTarget = mutatableInsns.get(replacementTargetIdx);
250
251    for (MInsn mInsn : mutatableInsns) {
252      if (mInsn instanceof MBranchInsn) {
253        // Check if this branch insn points at the insn we just deleted.
254        MBranchInsn branchInsn = (MBranchInsn) mInsn;
255        MInsn target = branchInsn.target;
256        if (target == toBeDeleted) {
257          Log.debug(branchInsn + " was pointing at the deleted instruction, updated.");
258          branchInsn.target = replacementTarget;
259        }
260      } else if (mInsn instanceof MSwitchInsn) {
261        // Check if any of this switch insn's targets points at the insn we just deleted.
262        MSwitchInsn switchInsn = (MSwitchInsn) mInsn;
263        List<Integer> indicesToChange = new ArrayList<Integer>();
264        int idx = 0;
265        for (MInsn target : switchInsn.targets) {
266          if (target == toBeDeleted) {
267            indicesToChange.add(idx);
268            Log.debug(switchInsn + "[" + idx
269                + "] was pointing at the deleted instruction, updated.");
270          }
271          idx++;
272        }
273        for (int idxToChange : indicesToChange) {
274          switchInsn.targets.remove(idxToChange);
275          switchInsn.targets.add(idxToChange, replacementTarget);
276        }
277      }
278    }
279
280    // Now update the try blocks.
281    updateTryBlocksWithReplacementInsn(toBeDeleted, replacementTarget, false);
282  }
283
284  /**
285   * Delete the provided MInsn.
286   */
287  public void deleteInstruction(MInsn toBeDeleted) {
288    deleteInstructionFull(toBeDeleted, mutatableInsns.indexOf(toBeDeleted));
289  }
290
291  /**
292   * Delete the MInsn at the provided index.
293   */
294  public void deleteInstruction(int toBeDeletedIdx) {
295    deleteInstructionFull(mutatableInsns.get(toBeDeletedIdx), toBeDeletedIdx);
296  }
297
298  public void swapInstructionsByIndex(int aIdx, int bIdx) {
299    MInsn aInsn = mutatableInsns.get(aIdx);
300    MInsn bInsn = mutatableInsns.get(bIdx);
301
302    mutatableInsns.set(aIdx, bInsn);
303    mutatableInsns.set(bIdx, aInsn);
304
305    updateTryBlocksWithReplacementInsn(aInsn, bInsn, true);
306
307    recalculateLocations();
308  }
309
310  /**
311   * Some mutators may require the use of temporary registers. For instance,
312   * to easily add in printing of values without having to look for registers
313   * that aren't currently live.
314   * The idea is to allocate these registers at the top of the set of registers.
315   * Because this will then shift where the arguments to the method are, we then
316   * change the start of the method to copy the arguments to the method
317   * into the place where the rest of the method's code expects them to be.
318   * Call allocateTemporaryVRegs(n), then use getTemporaryVReg(n),
319   * and then make sure finishedUsingTemporaryVRegs() is called!
320   */
321  public void allocateTemporaryVRegs(int count) {
322    if (count > tempVRegsAllocated) {
323      if (tempVRegsAllocated == 0) {
324        Log.info("Allocating temporary vregs for method...");
325        initialTempVReg = registersSize;
326        originalInVReg = (short) (registersSize - insSize);
327      } else {
328        Log.info("Extending allocation of temporary vregs for method...");
329      }
330      registersSize = (short) (initialTempVReg + count);
331      if (outsSize < count) {
332        outsSize = (short) count;
333      }
334      vregsNeedCopying = true;
335      tempVRegsAllocated = (short) count;
336    }
337  }
338
339  public int getTemporaryVReg(int number) {
340    if (number >= tempVRegsAllocated) {
341      Log.errorAndQuit("Not allocated enough temporary vregs!");
342    }
343    return initialTempVReg + number;
344  }
345
346  public void finishedUsingTemporaryVRegs() {
347    if (tempVRegsAllocated > 0 && vregsNeedCopying) {
348      // Just delete all the move instructions and generate again, if we already have some.
349      while (numMoveInsnsGenerated > 0) {
350        deleteInstruction(0);
351        numMoveInsnsGenerated--;
352      }
353
354      Log.info("Moving 'in' vregs to correct locations after allocating temporary vregs");
355
356      int shortyIdx = 0;
357      if (isStatic) {
358        shortyIdx = 1;
359      }
360
361      int insertionCounter = 0;
362
363      // Insert copy insns that move all the in VRs down.
364      for (int i = 0; i < insSize; i++) {
365        MInsn moveInsn = new MInsn();
366        moveInsn.insn = new Instruction();
367        moveInsn.insn.vregA = originalInVReg + i;
368        moveInsn.insn.vregB = originalInVReg + i + tempVRegsAllocated;
369
370        char type = 'L';
371        if (shortyIdx > 0) {
372          type = shorty.charAt(shortyIdx);
373        }
374        shortyIdx++;
375
376        if (type == 'L') {
377          moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_OBJECT_16);
378        } else if (type == 'D' || type == 'J') {
379          moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_WIDE_16);
380          i++;
381        } else {
382          moveInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_16);
383        }
384
385        insertInstructionAt(moveInsn, insertionCounter);
386        insertionCounter++;
387        Log.info("Temp vregs creation, Added instruction " + moveInsn);
388        numMoveInsnsGenerated++;
389      }
390
391      vregsNeedCopying = false;
392    }
393  }
394
395  /**
396   * When we insert new Field/Type/MethodIds into the DEX file, this may shunt some Ids
397   * into a new position in the table. If this happens, every reference to the Ids must
398   * be updated! Because CodeItems have their Instructions wrapped into a graph of MInsns
399   * during mutation, they don't have a view of all their instructions during mutation,
400   * and so if they are asked to update their instructions' indices into the tables, they
401   * must call this method to get the actual list of instructions they currently own.
402   */
403  public List<Instruction> requestLatestInstructions() {
404    List<Instruction> latestInsns = new ArrayList<Instruction>();
405    for (MInsn mInsn : mutatableInsns) {
406      latestInsns.add(mInsn.insn);
407    }
408    return latestInsns;
409  }
410}
411