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.mutators;
18
19import dexfuzz.Log;
20import dexfuzz.MutationStats;
21import dexfuzz.program.MInsn;
22import dexfuzz.program.MTryBlock;
23import dexfuzz.program.MutatableCode;
24import dexfuzz.program.Mutation;
25
26import java.util.List;
27import java.util.Random;
28
29public class TryBlockShifter extends CodeMutator {
30  /**
31   * Every CodeMutator has an AssociatedMutation, representing the
32   * mutation that this CodeMutator can perform, to allow separate
33   * generateMutation() and applyMutation() phases, allowing serialization.
34   */
35  public static class AssociatedMutation extends Mutation {
36    public int tryIdx;
37    public boolean shiftingTryBlock; // false => shifting handler
38    public boolean shiftingStart; // false => shifting end (try block only)
39    public boolean shiftingHandlerCatchall;
40    public int shiftingHandlerIdx;
41    public int newShiftedInsnIdx;
42
43    @Override
44    public String getString() {
45      String result = String.format("%d %s %s %s %d %d",
46          tryIdx,
47          (shiftingTryBlock ? "T" : "F"),
48          (shiftingStart ? "T" : "F"),
49          (shiftingHandlerCatchall ? "T" : "F"),
50          shiftingHandlerIdx,
51          newShiftedInsnIdx
52          );
53      return result;
54    }
55
56    @Override
57    public void parseString(String[] elements) {
58      tryIdx = Integer.parseInt(elements[2]);
59      shiftingTryBlock = elements[3].equals("T");
60      shiftingStart = elements[4].equals("T");
61      shiftingHandlerCatchall = elements[5].equals("T");
62      shiftingHandlerIdx = Integer.parseInt(elements[6]);
63      newShiftedInsnIdx = Integer.parseInt(elements[7]);
64    }
65  }
66
67  // The following two methods are here for the benefit of MutationSerializer,
68  // so it can create a CodeMutator and get the correct associated Mutation, as it
69  // reads in mutations from a dump of mutations.
70  @Override
71  public Mutation getNewMutation() {
72    return new AssociatedMutation();
73  }
74
75  public TryBlockShifter() { }
76
77  public TryBlockShifter(Random rng, MutationStats stats, List<Mutation> mutations) {
78    super(rng, stats, mutations);
79    likelihood = 40;
80  }
81
82  @Override
83  protected boolean canMutate(MutatableCode mutatableCode) {
84    if (mutatableCode.triesSize > 0) {
85      return true;
86    }
87
88    Log.debug("Method contains no tries.");
89    return false;
90  }
91
92  @Override
93  protected Mutation generateMutation(MutatableCode mutatableCode) {
94    // Pick a random try.
95    int tryIdx = rng.nextInt(mutatableCode.triesSize);
96    MTryBlock tryBlock = mutatableCode.mutatableTries.get(tryIdx);
97
98    boolean shiftingTryBlock = rng.nextBoolean();
99    boolean shiftingStart = false;
100    boolean shiftingHandlerCatchall = false;
101    int shiftingHandlerIdx = -1;
102    if (shiftingTryBlock) {
103      // We're shifting the boundaries of the try block
104      // determine if we shift the start or the end.
105      shiftingStart = rng.nextBoolean();
106    } else {
107      // We're shifting the start of a handler of the try block.
108      if (tryBlock.handlers.isEmpty()) {
109        // No handlers, so we MUST mutate the catchall
110        shiftingHandlerCatchall = true;
111      } else if (tryBlock.catchAllHandler != null) {
112        // There is a catchall handler, so potentially mutate it.
113        shiftingHandlerCatchall = rng.nextBoolean();
114      }
115      // If we're not going to shift the catchall handler, then
116      // pick an explicit handler to shift.
117      if (!shiftingHandlerCatchall) {
118        shiftingHandlerIdx = rng.nextInt(tryBlock.handlers.size());
119      }
120    }
121
122    // Get the original instruction wherever we're shifting.
123    MInsn oldInsn = null;
124    if (shiftingTryBlock && shiftingStart) {
125      oldInsn = tryBlock.startInsn;
126    } else if (shiftingTryBlock && !(shiftingStart)) {
127      oldInsn = tryBlock.endInsn;
128    } else if (!(shiftingTryBlock) && shiftingHandlerCatchall) {
129      oldInsn = tryBlock.catchAllHandler;
130    } else if (!(shiftingTryBlock) && !(shiftingHandlerCatchall)
131        && (shiftingHandlerIdx != -1)) {
132      oldInsn = tryBlock.handlers.get(shiftingHandlerIdx);
133    } else {
134      Log.errorAndQuit("Faulty logic in TryBlockShifter!");
135    }
136
137    // Find the index of this instruction.
138    int oldInsnIdx = mutatableCode.getInstructionIndex(oldInsn);
139
140    int newInsnIdx = oldInsnIdx;
141
142    int delta = 0;
143
144    // Keep searching for a new index.
145    while (newInsnIdx == oldInsnIdx) {
146      // Vary by +/- 2 instructions.
147      delta = 0;
148      while (delta == 0) {
149        delta = (rng.nextInt(5) - 2);
150      }
151
152      newInsnIdx = oldInsnIdx + delta;
153
154      // Check the new index is legal.
155      if (newInsnIdx < 0) {
156        newInsnIdx = 0;
157      } else if (newInsnIdx >= mutatableCode.getInstructionCount()) {
158        newInsnIdx = mutatableCode.getInstructionCount() - 1;
159      }
160    }
161
162    AssociatedMutation mutation = new AssociatedMutation();
163    mutation.setup(this.getClass(), mutatableCode);
164    mutation.tryIdx = tryIdx;
165    mutation.shiftingTryBlock = shiftingTryBlock;
166    mutation.shiftingStart = shiftingStart;
167    mutation.shiftingHandlerCatchall = shiftingHandlerCatchall;
168    mutation.shiftingHandlerIdx = shiftingHandlerIdx;
169    mutation.newShiftedInsnIdx = newInsnIdx;
170    return mutation;
171  }
172
173  @Override
174  protected void applyMutation(Mutation uncastMutation) {
175    // Cast the Mutation to our AssociatedMutation, so we can access its fields.
176    AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
177    MutatableCode mutatableCode = mutation.mutatableCode;
178
179    MTryBlock tryBlock = mutatableCode.mutatableTries.get(mutation.tryIdx);
180
181    MInsn newInsn =
182        mutatableCode.getInstructionAt(mutation.newShiftedInsnIdx);
183
184    // Find the right mutatable instruction in try block, and point it at the new instruction.
185    if (mutation.shiftingTryBlock && mutation.shiftingStart) {
186      tryBlock.startInsn = newInsn;
187      Log.info("Shifted the start of try block #" + mutation.tryIdx
188          + " to be at " + newInsn);
189    } else if (mutation.shiftingTryBlock && !(mutation.shiftingStart)) {
190      tryBlock.endInsn = newInsn;
191      Log.info("Shifted the end of try block #" + mutation.tryIdx
192          + " to be at " + newInsn);
193    } else if (!(mutation.shiftingTryBlock) && mutation.shiftingHandlerCatchall) {
194      tryBlock.catchAllHandler = newInsn;
195      Log.info("Shifted the catch all handler of try block #" + mutation.tryIdx
196          + " to be at " + newInsn);
197    } else if (!(mutation.shiftingTryBlock) && !(mutation.shiftingHandlerCatchall)
198        && (mutation.shiftingHandlerIdx != -1)) {
199      tryBlock.handlers.set(mutation.shiftingHandlerIdx, newInsn);
200      Log.info("Shifted handler #" + mutation.shiftingHandlerIdx
201          + " of try block #" + mutation.tryIdx + " to be at " + newInsn);
202    } else {
203      Log.errorAndQuit("faulty logic in TryBlockShifter");
204    }
205
206    stats.incrementStat("Shifted boundary in a try block");
207  }
208}
209