|  | // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  | package com.android.tools.r8.dex; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  |  | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.code.Const4; | 
|  | import com.android.tools.r8.code.ConstString; | 
|  | import com.android.tools.r8.code.ConstStringJumbo; | 
|  | import com.android.tools.r8.code.Goto32; | 
|  | import com.android.tools.r8.code.IfEq; | 
|  | import com.android.tools.r8.code.IfEqz; | 
|  | import com.android.tools.r8.code.IfNe; | 
|  | import com.android.tools.r8.code.IfNez; | 
|  | import com.android.tools.r8.code.Instruction; | 
|  | import com.android.tools.r8.code.ReturnVoid; | 
|  | import com.android.tools.r8.graph.DexAnnotationSet; | 
|  | import com.android.tools.r8.graph.DexCode; | 
|  | import com.android.tools.r8.graph.DexCode.Try; | 
|  | import com.android.tools.r8.graph.DexEncodedMethod; | 
|  | import com.android.tools.r8.graph.DexItemFactory; | 
|  | import com.android.tools.r8.graph.DexString; | 
|  | import com.android.tools.r8.graph.MethodAccessFlags; | 
|  | import com.android.tools.r8.graph.ParameterAnnotationsList; | 
|  | import com.android.tools.r8.graph.ProgramMethod; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.io.Files; | 
|  | import java.nio.file.Path; | 
|  | import java.nio.file.Paths; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import org.junit.Test; | 
|  |  | 
|  | public class JumboStringProcessing extends TestBase { | 
|  |  | 
|  | @Test | 
|  | public void branching() { | 
|  | DexItemFactory factory = new DexItemFactory(); | 
|  | DexString string = factory.createString("turn into jumbo"); | 
|  | Instruction[] instructions = buildInstructions(string, false); | 
|  | DexCode code = jumboStringProcess(factory, string, instructions); | 
|  | Instruction[] rewrittenInstructions = code.instructions; | 
|  | assert rewrittenInstructions[1] instanceof IfEq; | 
|  | IfEq condition = (IfEq) rewrittenInstructions[1]; | 
|  | assert condition.getOffset() + condition.CCCC == rewrittenInstructions[3].getOffset(); | 
|  | assert rewrittenInstructions[2] instanceof Goto32; | 
|  | Goto32 jump = (Goto32) rewrittenInstructions[2]; | 
|  | Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1]; | 
|  | assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void branching2() { | 
|  | DexItemFactory factory = new DexItemFactory(); | 
|  | DexString string = factory.createString("turn into jumbo"); | 
|  | Instruction[] instructions = buildInstructions(string, true); | 
|  | DexCode code = jumboStringProcess(factory, string, instructions); | 
|  | Instruction[] rewrittenInstructions = code.instructions; | 
|  | assert rewrittenInstructions[1] instanceof IfEqz; | 
|  | IfEqz condition = (IfEqz) rewrittenInstructions[1]; | 
|  | assert condition.getOffset() + condition.BBBB == rewrittenInstructions[3].getOffset(); | 
|  | assert rewrittenInstructions[2] instanceof Goto32; | 
|  | Goto32 jump = (Goto32) rewrittenInstructions[2]; | 
|  | Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1]; | 
|  | assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset(); | 
|  | } | 
|  |  | 
|  | private Instruction[] buildInstructions(DexString string, boolean zeroCondition) { | 
|  | List<Instruction> instructions = new ArrayList<>(); | 
|  | int offset = 0; | 
|  | Instruction instr = new Const4(0, 0); | 
|  | instr.setOffset(offset); | 
|  | instructions.add(instr); | 
|  | offset += instr.getSize(); | 
|  | int lastInstructionOffset = 15000 * 2 + 2 + offset; | 
|  | if (zeroCondition) { | 
|  | instr = new IfNez(0, lastInstructionOffset - offset); | 
|  | } else { | 
|  | instr = new IfNe(0, 0, lastInstructionOffset - offset); | 
|  | } | 
|  | instr.setOffset(offset); | 
|  | instructions.add(instr); | 
|  | offset += instr.getSize(); | 
|  | for (int i = 0; i < 15000; i++) { | 
|  | instr = new ConstString(0, string); | 
|  | instr.setOffset(offset); | 
|  | instructions.add(instr); | 
|  | offset += instr.getSize(); | 
|  | } | 
|  | instr = new ReturnVoid(); | 
|  | instr.setOffset(offset); | 
|  | instructions.add(instr); | 
|  | assert instr.getOffset() == lastInstructionOffset; | 
|  | return instructions.toArray(Instruction.EMPTY_ARRAY); | 
|  | } | 
|  |  | 
|  | private int countJumboStrings(Instruction[] instructions) { | 
|  | int count = 0; | 
|  | for (Instruction instruction : instructions) { | 
|  | count += instruction instanceof ConstStringJumbo ? 1 : 0; | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | private int countSimpleNops(Instruction[] instructions) { | 
|  | int count = 0; | 
|  | for (Instruction instruction : instructions) { | 
|  | count += instruction.isSimpleNop() ? 1 : 0; | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void regress78072750() throws Exception { | 
|  | // This dex file have the baksmali output from the failing class from b/78072750, with all | 
|  | // const-string/jumbo replaced with const-string. Also one of the nops before the first | 
|  | // payload has been removed to make it valid dex file (correct alignment of the payload | 
|  | // instruction). | 
|  | Path originalDexFile = | 
|  | Paths.get(ToolHelper.SMALI_BUILD_DIR, "regression/78072750/78072750.dex"); | 
|  | AndroidApp application = AndroidApp.builder() | 
|  | .addDexProgramData(Files.toByteArray(originalDexFile.toFile()), Origin.unknown()) | 
|  | .build(); | 
|  | CodeInspector inspector = new CodeInspector(application); | 
|  | ProgramMethod method = | 
|  | getMethod( | 
|  | inspector, | 
|  | "android.databinding.DataBinderMapperImpl", | 
|  | "android.databinding.ViewDataBinding", | 
|  | "getDataBinder", | 
|  | ImmutableList.of( | 
|  | "android.databinding.DataBindingComponent", "android.view.View", "int")); | 
|  | Instruction[] instructions = method.getDefinition().getCode().asDexCode().instructions; | 
|  | assertEquals(0, countJumboStrings(instructions)); | 
|  | assertEquals(1, countSimpleNops(instructions)); | 
|  |  | 
|  | DexItemFactory factory = inspector.getFactory(); | 
|  | DexString string = factory.createString("view must have a tag"); | 
|  | DexCode code = jumboStringProcess(factory, string, instructions); | 
|  | Instruction[] rewrittenInstructions = code.instructions; | 
|  | assertEquals(289, countJumboStrings(rewrittenInstructions)); | 
|  | assertEquals(0, countSimpleNops(rewrittenInstructions)); | 
|  | } | 
|  |  | 
|  | private DexCode jumboStringProcess( | 
|  | DexItemFactory factory, DexString string, Instruction[] instructions) { | 
|  | DexCode code = new DexCode( | 
|  | 1, | 
|  | 0, | 
|  | 0, | 
|  | instructions, | 
|  | new Try[0], | 
|  | null, | 
|  | null); | 
|  | MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false); | 
|  | DexEncodedMethod method = | 
|  | new DexEncodedMethod( | 
|  | null, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code); | 
|  | return new JumboStringRewriter(method, string, factory).rewrite(); | 
|  | } | 
|  | } |