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.MutatableCode;
23import dexfuzz.program.Mutation;
24import dexfuzz.rawdex.Instruction;
25import dexfuzz.rawdex.Opcode;
26
27import java.util.List;
28import java.util.Random;
29
30public class ValuePrinter extends CodeMutator {
31  /**
32   * Every CodeMutator has an AssociatedMutation, representing the
33   * mutation that this CodeMutator can perform, to allow separate
34   * generateMutation() and applyMutation() phases, allowing serialization.
35   */
36  public static class AssociatedMutation extends Mutation {
37    public int printedOutputIdx;
38
39    @Override
40    public String getString() {
41      return Integer.toString(printedOutputIdx);
42    }
43
44    @Override
45    public void parseString(String[] elements) {
46      printedOutputIdx = Integer.parseInt(elements[2]);
47    }
48  }
49
50  // The following two methods are here for the benefit of MutationSerializer,
51  // so it can create a CodeMutator and get the correct associated Mutation, as it
52  // reads in mutations from a dump of mutations.
53  @Override
54  public Mutation getNewMutation() {
55    return new AssociatedMutation();
56  }
57
58  public ValuePrinter() { }
59
60  public ValuePrinter(Random rng, MutationStats stats, List<Mutation> mutations) {
61    super(rng, stats, mutations);
62    likelihood = 40;
63  }
64
65  @Override
66  protected boolean canMutate(MutatableCode mutatableCode) {
67    for (MInsn mInsn : mutatableCode.getInstructions()) {
68      if (getInstructionOutputType(mInsn) != OutputType.UNKNOWN) {
69        return true;
70      }
71    }
72
73    Log.debug("No instructions with legible output in method, skipping.");
74    return false;
75  }
76
77  @Override
78  protected Mutation generateMutation(MutatableCode mutatableCode) {
79    // Find an instruction whose output we wish to print.
80    int printedOutputIdx = 0;
81    boolean foundInsn = false;
82
83    while (!foundInsn) {
84      printedOutputIdx = rng.nextInt(mutatableCode.getInstructionCount());
85      MInsn insnOutputToPrint =
86          mutatableCode.getInstructionAt(printedOutputIdx);
87      foundInsn = true;
88
89      // Don't want to insert instructions where there are raw instructions for now.
90      if (insnOutputToPrint.insn.justRaw) {
91        foundInsn = false;
92      }
93
94      if (getInstructionOutputType(insnOutputToPrint) == OutputType.UNKNOWN) {
95        foundInsn = false;
96      }
97    }
98
99    AssociatedMutation mutation = new AssociatedMutation();
100    mutation.setup(this.getClass(), mutatableCode);
101    mutation.printedOutputIdx = printedOutputIdx;
102    return mutation;
103  }
104
105  @Override
106  protected void applyMutation(Mutation uncastMutation) {
107    // Cast the Mutation to our AssociatedMutation, so we can access its fields.
108    AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
109    MutatableCode mutatableCode = mutation.mutatableCode;
110
111    MInsn insnOutputToPrint =
112        mutatableCode.getInstructionAt(mutation.printedOutputIdx);
113
114    int outFieldIdx = mutatableCode.program.getNewItemCreator().findOrCreateFieldId(
115        "Ljava/lang/System;",
116        "Ljava/io/PrintStream;",
117        "out");
118
119    OutputType outputType = getInstructionOutputType(insnOutputToPrint);
120
121    if (outputType == OutputType.UNKNOWN) {
122      Log.errorAndQuit("Requested to print output of an instruction, whose output"
123          + " type is unknown.");
124    }
125    int printMethodIdx = mutatableCode.program.getNewItemCreator().findOrCreateMethodId(
126        "Ljava/io/PrintStream;",
127        "print",
128        outputType.getSignatureForPrintln());
129
130    boolean isWide = false;
131    boolean isRef = false;
132    if (outputType == OutputType.LONG || outputType == OutputType.DOUBLE) {
133      isWide = true;
134    }
135    if (outputType == OutputType.STRING) {
136      isRef = true;
137    }
138
139    // If we're printing a wide value, we need to allocate 3 registers!
140    if (isWide) {
141      mutatableCode.allocateTemporaryVRegs(3);
142    } else {
143      mutatableCode.allocateTemporaryVRegs(2);
144    }
145
146    int streamRegister = mutatableCode.getTemporaryVReg(0);
147    int valueRegister = mutatableCode.getTemporaryVReg(1);
148
149    // Copy the value we want to print to the 2nd temporary register
150    // Then load the out stream
151    // Then call print(out stream, value)
152
153    MInsn valueCopyInsn = new MInsn();
154    valueCopyInsn.insn = new Instruction();
155    if (isRef) {
156      valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_OBJECT_16);
157    } else if (isWide) {
158      valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_WIDE_16);
159    } else {
160      valueCopyInsn.insn.info = Instruction.getOpcodeInfo(Opcode.MOVE_16);
161    }
162    valueCopyInsn.insn.vregB = insnOutputToPrint.insn.vregA;
163    valueCopyInsn.insn.vregA = valueRegister;
164
165    MInsn streamLoadInsn = new MInsn();
166    streamLoadInsn.insn = new Instruction();
167    streamLoadInsn.insn.info = Instruction.getOpcodeInfo(Opcode.SGET_OBJECT);
168    streamLoadInsn.insn.vregB = outFieldIdx;
169    streamLoadInsn.insn.vregA = streamRegister;
170
171    MInsn invokeInsn = new MInsn();
172    invokeInsn.insn = new Instruction();
173    invokeInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_VIRTUAL_RANGE);
174    if (isWide) {
175      invokeInsn.insn.vregA = 3;
176    } else {
177      invokeInsn.insn.vregA = 2;
178    }
179    invokeInsn.insn.vregB = printMethodIdx;
180    invokeInsn.insn.vregC = streamRegister;
181
182    Log.info(String.format("Printing output value of instruction %s", insnOutputToPrint));
183
184    stats.incrementStat("Printed output value");
185
186    mutatableCode.insertInstructionAfter(invokeInsn, mutation.printedOutputIdx);
187    mutatableCode.insertInstructionAfter(streamLoadInsn, mutation.printedOutputIdx);
188    mutatableCode.insertInstructionAfter(valueCopyInsn, mutation.printedOutputIdx);
189
190    mutatableCode.finishedUsingTemporaryVRegs();
191  }
192
193  private static enum OutputType {
194    STRING("(Ljava/lang/String;)V"),
195    BOOLEAN("(Z)V"),
196    BYTE("(B)V"),
197    CHAR("(C)V"),
198    SHORT("(S)V"),
199    INT("(I)V"),
200    LONG("(J)V"),
201    FLOAT("(F)V"),
202    DOUBLE("(D)V"),
203    UNKNOWN("UNKNOWN");
204
205    private String printingSignature;
206    private OutputType(String s) {
207      printingSignature = s;
208    }
209
210    public String getSignatureForPrintln() {
211      return printingSignature;
212    }
213  }
214
215  private OutputType getInstructionOutputType(MInsn mInsn) {
216    Opcode opcode = mInsn.insn.info.opcode;
217    if (opcode == Opcode.CONST_STRING || opcode == Opcode.CONST_STRING_JUMBO) {
218      return OutputType.STRING;
219    }
220    if (opcode == Opcode.IGET_BOOLEAN || opcode == Opcode.SGET_BOOLEAN) {
221      return OutputType.BOOLEAN;
222    }
223    if (opcode == Opcode.IGET_BYTE || opcode == Opcode.SGET_BYTE
224        || opcode == Opcode.INT_TO_BYTE) {
225      return OutputType.BYTE;
226    }
227    if (opcode == Opcode.IGET_CHAR || opcode == Opcode.SGET_CHAR
228        || opcode == Opcode.INT_TO_CHAR) {
229      return OutputType.CHAR;
230    }
231    if (opcode == Opcode.IGET_SHORT || opcode == Opcode.SGET_SHORT
232        || opcode == Opcode.INT_TO_SHORT) {
233      return OutputType.SHORT;
234    }
235    if (opcode == Opcode.NEG_INT || opcode == Opcode.NOT_INT
236        || opcode == Opcode.LONG_TO_INT || opcode == Opcode.FLOAT_TO_INT
237        || opcode == Opcode.DOUBLE_TO_INT
238        || Opcode.isBetween(opcode, Opcode.ADD_INT, Opcode.USHR_INT)
239        || Opcode.isBetween(opcode, Opcode.ADD_INT_2ADDR, Opcode.USHR_INT_2ADDR)
240        || Opcode.isBetween(opcode, Opcode.ADD_INT_LIT16, Opcode.USHR_INT_LIT8)) {
241      return OutputType.INT;
242    }
243    if (opcode == Opcode.NEG_LONG || opcode == Opcode.NOT_LONG
244        || opcode == Opcode.INT_TO_LONG || opcode == Opcode.FLOAT_TO_LONG
245        || opcode == Opcode.DOUBLE_TO_LONG
246        || Opcode.isBetween(opcode, Opcode.ADD_LONG, Opcode.USHR_LONG)
247        || Opcode.isBetween(opcode, Opcode.ADD_LONG_2ADDR, Opcode.USHR_LONG_2ADDR)) {
248      return OutputType.LONG;
249    }
250    if (opcode == Opcode.NEG_FLOAT
251        || opcode == Opcode.INT_TO_FLOAT || opcode == Opcode.LONG_TO_FLOAT
252        || opcode == Opcode.DOUBLE_TO_FLOAT
253        || Opcode.isBetween(opcode, Opcode.ADD_FLOAT, Opcode.REM_FLOAT)
254        || Opcode.isBetween(opcode, Opcode.ADD_FLOAT_2ADDR, Opcode.REM_FLOAT_2ADDR)) {
255      return OutputType.FLOAT;
256    }
257    if (opcode == Opcode.NEG_DOUBLE
258        || opcode == Opcode.INT_TO_DOUBLE || opcode == Opcode.LONG_TO_DOUBLE
259        || opcode == Opcode.FLOAT_TO_DOUBLE
260        || Opcode.isBetween(opcode, Opcode.ADD_DOUBLE, Opcode.REM_DOUBLE)
261        || Opcode.isBetween(opcode, Opcode.ADD_DOUBLE_2ADDR, Opcode.REM_DOUBLE_2ADDR)) {
262      return OutputType.DOUBLE;
263    }
264    return OutputType.UNKNOWN;
265  }
266}
267