| // 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.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.naming.NamingLens; |
| 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"); |
| factory.sort(NamingLens.getIdentityLens()); |
| 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"); |
| factory.sort(NamingLens.getIdentityLens()); |
| 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); |
| DexEncodedMethod method = getMethod( |
| inspector, |
| "android.databinding.DataBinderMapperImpl", |
| "android.databinding.ViewDataBinding", |
| "getDataBinder", |
| ImmutableList.of("android.databinding.DataBindingComponent", "android.view.View", "int")); |
| Instruction[] instructions = method.getCode().asDexCode().instructions; |
| assertEquals(0, countJumboStrings(instructions)); |
| assertEquals(1, countSimpleNops(instructions)); |
| |
| DexItemFactory factory = inspector.getFactory(); |
| DexString string = factory.createString("view must have a tag"); |
| factory.sort(NamingLens.getIdentityLens()); |
| 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, null, ParameterAnnotationsList.empty(), code); |
| return new JumboStringRewriter(method, string, factory).rewrite(); |
| } |
| } |