| // 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.dex.code.DexConst4; | 
 | import com.android.tools.r8.dex.code.DexConstString; | 
 | import com.android.tools.r8.dex.code.DexConstStringJumbo; | 
 | import com.android.tools.r8.dex.code.DexGoto32; | 
 | import com.android.tools.r8.dex.code.DexIfEq; | 
 | import com.android.tools.r8.dex.code.DexIfEqz; | 
 | import com.android.tools.r8.dex.code.DexIfNe; | 
 | import com.android.tools.r8.dex.code.DexIfNez; | 
 | import com.android.tools.r8.dex.code.DexInstruction; | 
 | import com.android.tools.r8.dex.code.DexReturnVoid; | 
 | import com.android.tools.r8.graph.DexCode; | 
 | import com.android.tools.r8.graph.DexCode.Try; | 
 | import com.android.tools.r8.graph.DexCode.TryHandler; | 
 | 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.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"); | 
 |     DexInstruction[] instructions = buildInstructions(string, false); | 
 |     DexCode code = jumboStringProcess(factory, string, instructions); | 
 |     DexInstruction[] rewrittenInstructions = code.instructions; | 
 |     assert rewrittenInstructions[1] instanceof DexIfEq; | 
 |     DexIfEq condition = (DexIfEq) rewrittenInstructions[1]; | 
 |     assert condition.getOffset() + condition.CCCC == rewrittenInstructions[3].getOffset(); | 
 |     assert rewrittenInstructions[2] instanceof DexGoto32; | 
 |     DexGoto32 jump = (DexGoto32) rewrittenInstructions[2]; | 
 |     DexInstruction 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"); | 
 |     DexInstruction[] instructions = buildInstructions(string, true); | 
 |     DexCode code = jumboStringProcess(factory, string, instructions); | 
 |     DexInstruction[] rewrittenInstructions = code.instructions; | 
 |     assert rewrittenInstructions[1] instanceof DexIfEqz; | 
 |     DexIfEqz condition = (DexIfEqz) rewrittenInstructions[1]; | 
 |     assert condition.getOffset() + condition.BBBB == rewrittenInstructions[3].getOffset(); | 
 |     assert rewrittenInstructions[2] instanceof DexGoto32; | 
 |     DexGoto32 jump = (DexGoto32) rewrittenInstructions[2]; | 
 |     DexInstruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1]; | 
 |     assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset(); | 
 |   } | 
 |  | 
 |   private DexInstruction[] buildInstructions(DexString string, boolean zeroCondition) { | 
 |     List<DexInstruction> instructions = new ArrayList<>(); | 
 |     int offset = 0; | 
 |     DexInstruction instr = new DexConst4(0, 0); | 
 |     instr.setOffset(offset); | 
 |     instructions.add(instr); | 
 |     offset += instr.getSize(); | 
 |     int lastInstructionOffset = 15000 * 2 + 2 + offset; | 
 |     if (zeroCondition) { | 
 |       instr = new DexIfNez(0, lastInstructionOffset - offset); | 
 |     } else { | 
 |       instr = new DexIfNe(0, 0, lastInstructionOffset - offset); | 
 |     } | 
 |     instr.setOffset(offset); | 
 |     instructions.add(instr); | 
 |     offset += instr.getSize(); | 
 |     for (int i = 0; i < 15000; i++) { | 
 |       instr = new DexConstString(0, string); | 
 |       instr.setOffset(offset); | 
 |       instructions.add(instr); | 
 |       offset += instr.getSize(); | 
 |     } | 
 |     instr = new DexReturnVoid(); | 
 |     instr.setOffset(offset); | 
 |     instructions.add(instr); | 
 |     assert instr.getOffset() == lastInstructionOffset; | 
 |     return instructions.toArray(DexInstruction.EMPTY_ARRAY); | 
 |   } | 
 |  | 
 |   private int countJumboStrings(DexInstruction[] instructions) { | 
 |     int count = 0; | 
 |     for (DexInstruction instruction : instructions) { | 
 |       count += instruction instanceof DexConstStringJumbo ? 1 : 0; | 
 |     } | 
 |     return count; | 
 |   } | 
 |  | 
 |   private int countSimpleNops(DexInstruction[] instructions) { | 
 |     int count = 0; | 
 |     for (DexInstruction 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")); | 
 |     DexInstruction[] 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); | 
 |     DexInstruction[] rewrittenInstructions = code.instructions; | 
 |     assertEquals(289, countJumboStrings(rewrittenInstructions)); | 
 |     assertEquals(0, countSimpleNops(rewrittenInstructions)); | 
 |   } | 
 |  | 
 |   private DexCode jumboStringProcess( | 
 |       DexItemFactory factory, DexString string, DexInstruction[] instructions) { | 
 |     DexCode code = new DexCode(1, 0, 0, instructions, new Try[0], new TryHandler[0], null); | 
 |     MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false); | 
 |     DexEncodedMethod method = | 
 |         DexEncodedMethod.builder() | 
 |             .setAccessFlags(flags) | 
 |             .setCode(code) | 
 |             .disableMethodNotNullCheck() | 
 |             .disableAndroidApiLevelCheck() | 
 |             .build(); | 
 |     return new JumboStringRewriter(method, string, factory).rewrite(); | 
 |   } | 
 | } |