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 NonsenseStringPrinter 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 insertionIdx;
38    public String nonsenseString;
39
40    @Override
41    public String getString() {
42      StringBuilder builder = new StringBuilder();
43      builder.append(insertionIdx).append(" ");
44      builder.append(nonsenseString);
45      return builder.toString();
46    }
47
48    @Override
49    public void parseString(String[] elements) {
50      insertionIdx = Integer.parseInt(elements[2]);
51      nonsenseString = elements[3];
52    }
53  }
54
55  // The following two methods are here for the benefit of MutationSerializer,
56  // so it can create a CodeMutator and get the correct associated Mutation, as it
57  // reads in mutations from a dump of mutations.
58  @Override
59  public Mutation getNewMutation() {
60    return new AssociatedMutation();
61  }
62
63  public NonsenseStringPrinter() { }
64
65  public NonsenseStringPrinter(Random rng, MutationStats stats, List<Mutation> mutations) {
66    super(rng, stats, mutations);
67    likelihood = 10;
68  }
69
70  @Override
71  protected Mutation generateMutation(MutatableCode mutatableCode) {
72    // Find the insertion point
73    int insertionIdx = 0;
74    boolean foundInsn = false;
75
76    while (!foundInsn) {
77      insertionIdx = rng.nextInt(mutatableCode.getInstructionCount());
78      MInsn insertionPoint =
79          mutatableCode.getInstructionAt(insertionIdx);
80      foundInsn = true;
81
82      // Don't want to insert instructions where there are raw instructions for now.
83      if (insertionPoint.insn.justRaw) {
84        foundInsn = false;
85      }
86    }
87
88    AssociatedMutation mutation = new AssociatedMutation();
89    mutation.setup(this.getClass(), mutatableCode);
90    mutation.insertionIdx = insertionIdx;
91    mutation.nonsenseString = getRandomString();
92    return mutation;
93  }
94
95  @Override
96  protected void applyMutation(Mutation uncastMutation) {
97    // Cast the Mutation to our AssociatedMutation, so we can access its fields.
98    AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
99    MutatableCode mutatableCode = mutation.mutatableCode;
100
101    int outFieldIdx = mutatableCode.program.getNewItemCreator().findOrCreateFieldId(
102        "Ljava/lang/System;",
103        "Ljava/io/PrintStream;",
104        "out");
105    int printMethodIdx = mutatableCode.program.getNewItemCreator().findOrCreateMethodId(
106        "Ljava/io/PrintStream;",
107        "print",
108        "(Ljava/lang/String;)V");
109    int nonsenseStringIdx = mutatableCode.program.getNewItemCreator().findOrCreateString(
110        mutation.nonsenseString);
111
112    MInsn insertionPoint = mutatableCode.getInstructionAt(mutation.insertionIdx);
113
114    mutatableCode.allocateTemporaryVRegs(2);
115
116    int streamRegister = mutatableCode.getTemporaryVReg(0);
117    int stringRegister = mutatableCode.getTemporaryVReg(1);
118
119    // Load into string and stream into the temporary registers.
120    // then call print(stream, string)
121    MInsn constStringInsn = new MInsn();
122    constStringInsn.insn = new Instruction();
123    constStringInsn.insn.info = Instruction.getOpcodeInfo(Opcode.CONST_STRING);
124    constStringInsn.insn.vregB = nonsenseStringIdx;
125    constStringInsn.insn.vregA = stringRegister;
126
127    MInsn streamLoadInsn = new MInsn();
128    streamLoadInsn.insn = new Instruction();
129    streamLoadInsn.insn.info = Instruction.getOpcodeInfo(Opcode.SGET_OBJECT);
130    streamLoadInsn.insn.vregB = outFieldIdx;
131    streamLoadInsn.insn.vregA = streamRegister;
132
133    MInsn invokeInsn = new MInsn();
134    invokeInsn.insn = new Instruction();
135    invokeInsn.insn.info = Instruction.getOpcodeInfo(Opcode.INVOKE_VIRTUAL_RANGE);
136    invokeInsn.insn.vregA = 2;
137    invokeInsn.insn.vregB = printMethodIdx;
138    invokeInsn.insn.vregC = streamRegister;
139
140    Log.info(String.format("Printing nonsense string '%s', inserting at %s",
141        mutation.nonsenseString, insertionPoint));
142
143    stats.incrementStat("Printed nonsense string");
144
145    mutatableCode.insertInstructionAt(invokeInsn, mutation.insertionIdx);
146    mutatableCode.insertInstructionAt(streamLoadInsn, mutation.insertionIdx);
147    mutatableCode.insertInstructionAt(constStringInsn, mutation.insertionIdx);
148
149    mutatableCode.finishedUsingTemporaryVRegs();
150  }
151
152  private String getRandomString() {
153    int size = rng.nextInt(10);
154    int start = (int) 'A';
155    int end = (int) 'Z';
156    StringBuilder builder = new StringBuilder();
157    for (int i = 0; i < size; i++) {
158      builder.append((char) (rng.nextInt((end + 1) - start) + start));
159    }
160    return builder.toString();
161  }
162}
163