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 
17 package dexfuzz.program.mutators;
18 
19 import dexfuzz.Log;
20 import dexfuzz.MutationStats;
21 import dexfuzz.program.MInsn;
22 import dexfuzz.program.MTryBlock;
23 import dexfuzz.program.MutatableCode;
24 import dexfuzz.program.Mutation;
25 
26 import java.util.List;
27 import java.util.Random;
28 
29 public 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
getString()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
parseString(String[] elements)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
getNewMutation()71   public Mutation getNewMutation() {
72     return new AssociatedMutation();
73   }
74 
TryBlockShifter()75   public TryBlockShifter() { }
76 
TryBlockShifter(Random rng, MutationStats stats, List<Mutation> mutations)77   public TryBlockShifter(Random rng, MutationStats stats, List<Mutation> mutations) {
78     super(rng, stats, mutations);
79     likelihood = 40;
80   }
81 
82   @Override
canMutate(MutatableCode mutatableCode)83   protected boolean canMutate(MutatableCode mutatableCode) {
84     if (mutatableCode.triesSize == 0) {
85       Log.debug("Method contains no tries.");
86       return false;
87     }
88     if (mutatableCode.getInstructionCount() <= 1) {
89       Log.debug("Not enough instructions to shift try block.");
90       return false;
91     }
92     return true;
93   }
94 
95   @Override
generateMutation(MutatableCode mutatableCode)96   protected Mutation generateMutation(MutatableCode mutatableCode) {
97     // Pick a random try.
98     int tryIdx = rng.nextInt(mutatableCode.triesSize);
99     MTryBlock tryBlock = mutatableCode.mutatableTries.get(tryIdx);
100 
101     boolean shiftingTryBlock = rng.nextBoolean();
102     boolean shiftingStart = false;
103     boolean shiftingHandlerCatchall = false;
104     int shiftingHandlerIdx = -1;
105     if (shiftingTryBlock) {
106       // We're shifting the boundaries of the try block
107       // determine if we shift the start or the end.
108       shiftingStart = rng.nextBoolean();
109     } else {
110       // We're shifting the start of a handler of the try block.
111       if (tryBlock.handlers.isEmpty()) {
112         // No handlers, so we MUST mutate the catchall
113         shiftingHandlerCatchall = true;
114       } else if (tryBlock.catchAllHandler != null) {
115         // There is a catchall handler, so potentially mutate it.
116         shiftingHandlerCatchall = rng.nextBoolean();
117       }
118       // If we're not going to shift the catchall handler, then
119       // pick an explicit handler to shift.
120       if (!shiftingHandlerCatchall) {
121         shiftingHandlerIdx = rng.nextInt(tryBlock.handlers.size());
122       }
123     }
124 
125     // Get the original instruction wherever we're shifting.
126     MInsn oldInsn = null;
127     if (shiftingTryBlock && shiftingStart) {
128       oldInsn = tryBlock.startInsn;
129     } else if (shiftingTryBlock && !(shiftingStart)) {
130       oldInsn = tryBlock.endInsn;
131     } else if (!(shiftingTryBlock) && shiftingHandlerCatchall) {
132       oldInsn = tryBlock.catchAllHandler;
133     } else if (!(shiftingTryBlock) && !(shiftingHandlerCatchall)
134         && (shiftingHandlerIdx != -1)) {
135       oldInsn = tryBlock.handlers.get(shiftingHandlerIdx);
136     } else {
137       Log.errorAndQuit("Faulty logic in TryBlockShifter!");
138     }
139 
140     // Find the index of this instruction.
141     int oldInsnIdx = mutatableCode.getInstructionIndex(oldInsn);
142 
143     int newInsnIdx = oldInsnIdx;
144 
145     int delta = 0;
146 
147     // Keep searching for a new index.
148     while (newInsnIdx == oldInsnIdx) {
149       // Vary by +/- 2 instructions.
150       delta = 0;
151       while (delta == 0) {
152         delta = (rng.nextInt(5) - 2);
153       }
154 
155       newInsnIdx = oldInsnIdx + delta;
156 
157       // Check the new index is legal.
158       if (newInsnIdx < 0) {
159         newInsnIdx = 0;
160       } else if (newInsnIdx >= mutatableCode.getInstructionCount()) {
161         newInsnIdx = mutatableCode.getInstructionCount() - 1;
162       }
163     }
164 
165     AssociatedMutation mutation = new AssociatedMutation();
166     mutation.setup(this.getClass(), mutatableCode);
167     mutation.tryIdx = tryIdx;
168     mutation.shiftingTryBlock = shiftingTryBlock;
169     mutation.shiftingStart = shiftingStart;
170     mutation.shiftingHandlerCatchall = shiftingHandlerCatchall;
171     mutation.shiftingHandlerIdx = shiftingHandlerIdx;
172     mutation.newShiftedInsnIdx = newInsnIdx;
173     return mutation;
174   }
175 
176   @Override
applyMutation(Mutation uncastMutation)177   protected void applyMutation(Mutation uncastMutation) {
178     // Cast the Mutation to our AssociatedMutation, so we can access its fields.
179     AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
180     MutatableCode mutatableCode = mutation.mutatableCode;
181 
182     MTryBlock tryBlock = mutatableCode.mutatableTries.get(mutation.tryIdx);
183 
184     MInsn newInsn =
185         mutatableCode.getInstructionAt(mutation.newShiftedInsnIdx);
186 
187     // Find the right mutatable instruction in try block, and point it at the new instruction.
188     if (mutation.shiftingTryBlock && mutation.shiftingStart) {
189       tryBlock.startInsn = newInsn;
190       Log.info("Shifted the start of try block #" + mutation.tryIdx
191           + " to be at " + newInsn);
192     } else if (mutation.shiftingTryBlock && !(mutation.shiftingStart)) {
193       tryBlock.endInsn = newInsn;
194       Log.info("Shifted the end of try block #" + mutation.tryIdx
195           + " to be at " + newInsn);
196     } else if (!(mutation.shiftingTryBlock) && mutation.shiftingHandlerCatchall) {
197       tryBlock.catchAllHandler = newInsn;
198       Log.info("Shifted the catch all handler of try block #" + mutation.tryIdx
199           + " to be at " + newInsn);
200     } else if (!(mutation.shiftingTryBlock) && !(mutation.shiftingHandlerCatchall)
201         && (mutation.shiftingHandlerIdx != -1)) {
202       tryBlock.handlers.set(mutation.shiftingHandlerIdx, newInsn);
203       Log.info("Shifted handler #" + mutation.shiftingHandlerIdx
204           + " of try block #" + mutation.tryIdx + " to be at " + newInsn);
205     } else {
206       Log.errorAndQuit("faulty logic in TryBlockShifter");
207     }
208 
209     stats.incrementStat("Shifted boundary in a try block");
210   }
211 }
212