JumboStringConversionTest.java revision 3d721348c55b6b7b68d48dafb9829adb5f1829d2
1/*
2 * Copyright 2012, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.writer;
33
34import com.google.common.collect.Lists;
35import junit.framework.Assert;
36import org.jf.dexlib2.Opcode;
37import org.jf.dexlib2.iface.instruction.Instruction;
38import org.jf.dexlib2.iface.instruction.SwitchElement;
39import org.jf.dexlib2.iface.instruction.formats.*;
40import org.jf.dexlib2.immutable.ImmutableMethodImplementation;
41import org.jf.dexlib2.immutable.instruction.*;
42import org.jf.dexlib2.immutable.reference.ImmutableStringReference;
43import org.jf.dexlib2.writer.util.InstructionWriteUtil;
44import org.junit.Before;
45import org.junit.Test;
46
47import java.util.ArrayList;
48
49public class JumboStringConversionTest {
50    private static final int MIN_NUM_JUMBO_STRINGS = 256;
51
52    private MockStringPool mStringPool;
53    ArrayList<String> mJumboStrings;
54
55    @Before
56    public void setup() {
57        mStringPool = new MockStringPool();
58        StringBuilder stringBuilder = new StringBuilder("a");
59        mJumboStrings = Lists.newArrayList();
60        int index = 0;
61
62        // populate StringPool, make sure there are more than 64k+MIN_NUM_JUMBO_STRINGS strings
63        while (mJumboStrings.size()<MIN_NUM_JUMBO_STRINGS) {
64            for (int pos=stringBuilder.length()-1;pos>=0;pos--) {
65                for (char ch='a';ch<='z';ch++) {
66                    stringBuilder.setCharAt(pos, ch);
67                    mStringPool.intern(stringBuilder.toString(), index++);
68                    if (mStringPool.getNumItems()>0xFFFF) {
69                        mJumboStrings.add(stringBuilder.toString());
70                    }
71                }
72            }
73
74            stringBuilder.setLength(stringBuilder.length()+1);
75            for (int pos=0;pos<stringBuilder.length();pos++) {
76                stringBuilder.setCharAt(pos, 'a');
77            }
78        }
79    }
80
81    @Test
82    public void testInstruction21c() {
83        ArrayList<ImmutableInstruction> instructions = Lists.newArrayList();
84
85        ImmutableStringReference reference = new ImmutableStringReference(mJumboStrings.get(0));
86        ImmutableInstruction21c instruction = new ImmutableInstruction21c(Opcode.CONST_STRING, 0, reference);
87        instructions.add(instruction);
88
89        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
90        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
91
92        for (Instruction instr: writeUtil.getInstructions()) {
93            Assert.assertEquals("Jumbo string conversion was not performed!", instr.getOpcode(), Opcode.CONST_STRING_JUMBO);
94        }
95    }
96
97    private ArrayList<ImmutableInstruction> createSimpleInstructionList() {
98        ArrayList<ImmutableInstruction> instructions = Lists.newArrayList();
99
100        ImmutableStringReference reference = new ImmutableStringReference(mJumboStrings.get(0));
101        ImmutableInstruction21c stringInstr = new ImmutableInstruction21c(Opcode.CONST_STRING, 0, reference);
102        instructions.add(stringInstr);
103
104        reference = new ImmutableStringReference(mJumboStrings.get(1));
105        stringInstr = new ImmutableInstruction21c(Opcode.CONST_STRING, 0, reference);
106        instructions.add(stringInstr);
107
108        ImmutableInstruction10x nopInstr = new ImmutableInstruction10x(Opcode.NOP);
109        instructions.add(nopInstr);
110
111        ArrayList<SwitchElement> switchElements = Lists.newArrayList();
112        ImmutableSwitchElement switchElement = new ImmutableSwitchElement(0, 5);
113        switchElements.add(switchElement);
114
115        ImmutablePackedSwitchPayload packedSwitchInstr = new ImmutablePackedSwitchPayload(switchElements);
116        instructions.add(packedSwitchInstr);
117
118        ImmutableSparseSwitchPayload sparseSwitchPayload = new ImmutableSparseSwitchPayload(switchElements);
119        instructions.add(sparseSwitchPayload);
120
121        return instructions;
122    }
123
124    @Test
125    public void testInstruction10tSimple() {
126        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
127
128        ImmutableInstruction10t gotoInstr = new ImmutableInstruction10t(Opcode.GOTO, 3);
129        instructions.add(1, gotoInstr);
130
131        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
132        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
133
134        for (Instruction instr: writeUtil.getInstructions()) {
135            if (instr instanceof Instruction10t) {
136                Instruction10t instruction = (Instruction10t) instr;
137                Assert.assertEquals("goto (Format10t) target was not modified properly", instruction.getCodeOffset(), 4);
138                break;
139            }
140        }
141    }
142
143    @Test
144    public void testInstruction20tSimple() {
145        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
146
147        ImmutableInstruction20t gotoInstr = new ImmutableInstruction20t(Opcode.GOTO_16, 4);
148        instructions.add(1, gotoInstr);
149
150        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
151        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
152
153        for (Instruction instr: writeUtil.getInstructions()) {
154            if (instr instanceof Instruction20t) {
155                Instruction20t instruction = (Instruction20t) instr;
156                Assert.assertEquals("goto/16 (Format20t) target was not modified properly", instruction.getCodeOffset(), 5);
157                break;
158            }
159        }
160    }
161
162    @Test
163    public void testInstruction30t() {
164        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
165
166        ImmutableInstruction30t gotoInstr = new ImmutableInstruction30t(Opcode.GOTO_32, 5);
167        instructions.add(1, gotoInstr);
168
169        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
170        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
171
172        for (Instruction instr: writeUtil.getInstructions()) {
173            if (instr instanceof Instruction30t) {
174                Instruction30t instruction = (Instruction30t) instr;
175                Assert.assertEquals("goto/32 (Format30t) target was not modified properly", instruction.getCodeOffset(), 6);
176                break;
177            }
178        }
179    }
180
181    @Test
182    public void testInstruction21t() {
183        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
184
185        ImmutableInstruction21t branchInstr = new ImmutableInstruction21t(Opcode.IF_EQZ, 0, 4);
186        instructions.add(1, branchInstr);
187
188        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
189        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
190
191        for (Instruction instr: writeUtil.getInstructions()) {
192            if (instr instanceof Instruction21t) {
193                Instruction21t instruction = (Instruction21t) instr;
194                Assert.assertEquals("branch instruction (Format21t) target was not modified properly", instruction.getCodeOffset(), 5);
195                break;
196            }
197        }
198    }
199
200    @Test
201    public void testInstruction22t() {
202        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
203
204        ImmutableInstruction22t branchInstr = new ImmutableInstruction22t(Opcode.IF_EQ, 0, 1, 4);
205        instructions.add(1, branchInstr);
206
207        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
208        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
209
210        for (Instruction instr: writeUtil.getInstructions()) {
211            if (instr instanceof Instruction22t) {
212                Instruction22t instruction = (Instruction22t) instr;
213                Assert.assertEquals("branch instruction (Format22t) target was not modified properly", instruction.getCodeOffset(), 5);
214                break;
215            }
216        }
217    }
218
219    @Test
220    public void testInstruction31t() {
221        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
222
223        ImmutableInstruction31t branchInstr = new ImmutableInstruction31t(Opcode.PACKED_SWITCH, 0, 5);
224        instructions.add(1, branchInstr);
225
226        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
227        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
228
229        for (Instruction instr: writeUtil.getInstructions()) {
230            if (instr instanceof Instruction31t) {
231                Instruction31t instruction = (Instruction31t) instr;
232                Assert.assertEquals("branch instruction (Format31t) target was not modified properly", instruction.getCodeOffset(), 6);
233                break;
234            }
235        }
236    }
237
238    @Test
239    public void testPackedSwitchPayload() {
240        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
241
242        ImmutableInstruction31t branchInstr = new ImmutableInstruction31t(Opcode.PACKED_SWITCH, 0, 6);
243        instructions.add(1, branchInstr);
244
245        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
246        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
247
248        for (Instruction instr: writeUtil.getInstructions()) {
249            if (instr instanceof PackedSwitchPayload) {
250                PackedSwitchPayload instruction = (PackedSwitchPayload) instr;
251                for (SwitchElement switchElement: instruction.getSwitchElements()) {
252                    Assert.assertEquals("packed switch payload offset was not modified properly", switchElement.getOffset(), 6);
253                }
254                break;
255            }
256        }
257    }
258
259    @Test
260    public void testSparseSwitchPayload() {
261        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
262
263        ImmutableInstruction31t branchInstr = new ImmutableInstruction31t(Opcode.SPARSE_SWITCH, 0, 12);
264        instructions.add(1, branchInstr);
265
266        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
267        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
268
269        for (Instruction instr: writeUtil.getInstructions()) {
270            if (instr instanceof SparseSwitchPayload) {
271                SparseSwitchPayload instruction = (SparseSwitchPayload) instr;
272                for (SwitchElement switchElement: instruction.getSwitchElements()) {
273                    Assert.assertEquals("packed switch payload offset was not modified properly", switchElement.getOffset(), 6);
274                }
275                break;
276            }
277        }
278    }
279
280    @Test
281    public void testArrayPayloadAlignment() {
282        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
283
284        // add misaligned array payload
285        ImmutableInstruction10x nopInstr = new ImmutableInstruction10x(Opcode.NOP);
286        instructions.add(nopInstr);
287
288        ImmutableArrayPayload arrayPayload = new ImmutableArrayPayload(4, null);
289        instructions.add(arrayPayload);
290
291        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
292        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
293
294        int codeOffset = 0;
295        for (Instruction instr: writeUtil.getInstructions()) {
296            if (codeOffset == 21) {
297                Assert.assertEquals("array payload was not aligned properly", instr.getOpcode(), Opcode.NOP);
298            }
299            codeOffset += instr.getCodeUnits();
300        }
301    }
302
303    @Test
304    public void testPackedSwitchAlignment() {
305        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
306
307        // packed switch instruction is already misaligned
308
309        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
310        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
311
312        int codeOffset = 0;
313        for (Instruction instr: writeUtil.getInstructions()) {
314            if (codeOffset == 7) {
315                Assert.assertEquals("packed switch payload was not aligned properly", instr.getOpcode(), Opcode.NOP);
316            }
317            codeOffset += instr.getCodeUnits();
318        }
319    }
320
321    @Test
322    public void testSparseSwitchAlignment() {
323        ArrayList<ImmutableInstruction> instructions = createSimpleInstructionList();
324
325        // insert a nop to mis-align sparse switch payload
326        ImmutableInstruction10x nopInstr = new ImmutableInstruction10x(Opcode.NOP);
327        instructions.add(4, nopInstr);
328
329        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
330        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
331
332        int codeOffset = 0;
333        for (Instruction instr: writeUtil.getInstructions()) {
334            if (codeOffset == 15) {
335                Assert.assertEquals("packed switch payload was not aligned properly", instr.getOpcode(), Opcode.NOP);
336            }
337            codeOffset += instr.getCodeUnits();
338        }
339    }
340
341    @Test
342    public void testGotoToGoto16() {
343        ArrayList<ImmutableInstruction> instructions = Lists.newArrayList();
344
345        ImmutableInstruction10t gotoInstr = new ImmutableInstruction10t(Opcode.GOTO, 127);
346        instructions.add(gotoInstr);
347
348        ImmutableStringReference reference = new ImmutableStringReference(mJumboStrings.get(0));
349        ImmutableInstruction21c stringInstr = new ImmutableInstruction21c(Opcode.CONST_STRING, 0, reference);
350        instructions.add(stringInstr);
351
352        for (int i=0;i<127;i++) {
353            ImmutableInstruction10x nopInstr = new ImmutableInstruction10x(Opcode.NOP);
354            instructions.add(nopInstr);
355        }
356
357        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
358        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
359
360        Instruction instr = writeUtil.getInstructions().iterator().next();
361        Assert.assertEquals("goto was not converted to goto/16 properly", instr.getOpcode(), Opcode.GOTO_16);
362    }
363
364    @Test
365    public void testGoto16ToGoto32() {
366        ArrayList<ImmutableInstruction> instructions = Lists.newArrayList();
367
368        ImmutableInstruction20t gotoInstr = new ImmutableInstruction20t(Opcode.GOTO_16, Short.MAX_VALUE);
369        instructions.add(gotoInstr);
370
371        ImmutableStringReference reference = new ImmutableStringReference(mJumboStrings.get(0));
372        ImmutableInstruction21c stringInstr = new ImmutableInstruction21c(Opcode.CONST_STRING, 0, reference);
373        instructions.add(stringInstr);
374
375        for (int i=0;i<Short.MAX_VALUE;i++) {
376            ImmutableInstruction10x nopInstr = new ImmutableInstruction10x(Opcode.NOP);
377            instructions.add(nopInstr);
378        }
379
380        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
381        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
382
383        Instruction instr = writeUtil.getInstructions().iterator().next();
384        Assert.assertEquals("goto/16 was not converted to goto/32 properly", instr.getOpcode(), Opcode.GOTO_32);
385    }
386
387    @Test
388    public void testGotoIterative() {
389        ArrayList<ImmutableInstruction> instructions = Lists.newArrayList();
390
391        instructions.add(new ImmutableInstruction10t(Opcode.GOTO, 126));
392        instructions.add(new ImmutableInstruction10t(Opcode.GOTO, 127));
393        instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(0))));
394        for (int i=0;i<122;i++) {
395            instructions.add(new ImmutableInstruction10x(Opcode.NOP));
396        }
397        instructions.add(new ImmutableInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference(mJumboStrings.get(1))));
398        instructions.add(new ImmutableInstruction10x(Opcode.NOP));
399
400        // this misaligned array payload will cause nop insertion on the first pass and its removal on the second pass
401        instructions.add(new ImmutableInstruction10x(Opcode.NOP));
402        instructions.add(new ImmutableArrayPayload(4, null));
403
404        ImmutableMethodImplementation methodImplementation = new ImmutableMethodImplementation(1, instructions, null, null);
405        InstructionWriteUtil writeUtil = new InstructionWriteUtil(methodImplementation, mStringPool);
406
407        Instruction instr = writeUtil.getInstructions().iterator().next();
408        Assert.assertEquals("goto was not converted to goto/16 properly", instr.getOpcode(), Opcode.GOTO_16);
409
410        int codeOffset = 0;
411        for (Instruction instruction: writeUtil.getInstructions()) {
412            if (instruction instanceof ArrayPayload) {
413                Assert.assertEquals("packed switch payload was not aligned properly", codeOffset%2, 0);
414            }
415            codeOffset += instruction.getCodeUnits();
416        }
417    }
418}
419