|  | // Copyright (c) 2016, 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.ir; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertSame; | 
|  | import static org.junit.Assert.assertTrue; | 
|  |  | 
|  | import com.android.tools.r8.graph.AppInfo; | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.DexApplication; | 
|  | import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; | 
|  | import com.android.tools.r8.ir.code.Add; | 
|  | import com.android.tools.r8.ir.code.BasicBlock; | 
|  | import com.android.tools.r8.ir.code.ConstNumber; | 
|  | import com.android.tools.r8.ir.code.IRCode; | 
|  | import com.android.tools.r8.ir.code.Instruction; | 
|  | import com.android.tools.r8.ir.code.InstructionListIterator; | 
|  | import com.android.tools.r8.ir.code.NumericType; | 
|  | import com.android.tools.r8.ir.code.Position; | 
|  | import com.android.tools.r8.ir.code.Value; | 
|  | import com.android.tools.r8.smali.SmaliBuilder; | 
|  | import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.codeinspector.MethodSubject; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import org.junit.Test; | 
|  |  | 
|  | public class SplitBlockTest extends IrInjectionTestBase { | 
|  |  | 
|  | private TestApplication codeWithoutCatchHandlers() { | 
|  | SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); | 
|  |  | 
|  | String returnType = "int"; | 
|  | List<String> parameters = ImmutableList.of("int", "int"); | 
|  | MethodSignature signature = builder.addStaticMethod( | 
|  | returnType, | 
|  | DEFAULT_METHOD_NAME, | 
|  | parameters, | 
|  | 0, | 
|  | "    add-int             p0, p0, p0", | 
|  | "    sub-int             p1, p1, p0", | 
|  | "    mul-int             p0, p0, p1", | 
|  | "    return              p0" | 
|  | ); | 
|  |  | 
|  | builder.addMainMethod( | 
|  | 3, | 
|  | "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", | 
|  | "    const/4             v1, 1", | 
|  | "    const/4             v2, 5", | 
|  | "    invoke-static       { v1, v2 }, LTest;->method(II)I", | 
|  | "    move-result         v1", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V", | 
|  | "    return-void" | 
|  | ); | 
|  |  | 
|  | InternalOptions options = new InternalOptions(); | 
|  | DexApplication application = buildApplication(builder, options); | 
|  | AppView<?> appView = AppView.createForD8(new AppInfo(application), options); | 
|  |  | 
|  | // Return the processed method for inspection. | 
|  | MethodSubject methodSubject = getMethodSubject(application, signature); | 
|  | return new TestApplication(appView, methodSubject); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void noCatchHandlers() throws Exception { | 
|  | final int initialBlockCount = 1; | 
|  | final int argumentInstructions = 2; | 
|  | final int firstBlockInstructions = 6; | 
|  | // Try split between all non-argument instructions in the first block. | 
|  | for (int i = argumentInstructions; i < firstBlockInstructions; i++) { | 
|  | TestApplication test = codeWithoutCatchHandlers(); | 
|  | IRCode code = test.code; | 
|  | assertEquals(initialBlockCount, code.blocks.size()); | 
|  |  | 
|  | BasicBlock block = code.entryBlock(); | 
|  | int instructionCount = block.getInstructions().size(); | 
|  | assertEquals(firstBlockInstructions, instructionCount); | 
|  |  | 
|  | assertEquals(argumentInstructions, test.countArgumentInstructions()); | 
|  | assertEquals(firstBlockInstructions, block.getInstructions().size()); | 
|  | assertTrue(!block.getInstructions().get(i).isArgument()); | 
|  |  | 
|  | InstructionListIterator iterator = test.listIteratorAt(block, i); | 
|  | BasicBlock newBlock = iterator.split(code); | 
|  | assertTrue(code.isConsistentSSA()); | 
|  |  | 
|  | assertEquals(initialBlockCount + 1, code.blocks.size()); | 
|  | assertEquals(i + 1, code.entryBlock().getInstructions().size()); | 
|  | assertEquals(instructionCount - i, code.blocks.get(1).getInstructions().size()); | 
|  | assertSame(newBlock, code.blocks.get(1)); | 
|  |  | 
|  | // Run code and check result (code in the test object is updated). | 
|  | String result = test.run(); | 
|  | assertEquals("6", result); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void noCatchHandlersSplitThree() throws Exception { | 
|  | final int initialBlockCount = 1; | 
|  | final int argumentInstructions = 2; | 
|  | final int firstBlockInstructions = 6; | 
|  | // Try split out all non-argument instructions in the first block. | 
|  | for (int i = argumentInstructions; i < firstBlockInstructions - 1; i++) { | 
|  | TestApplication test = codeWithoutCatchHandlers(); | 
|  | IRCode code = test.code; | 
|  | assertEquals(initialBlockCount, code.blocks.size()); | 
|  |  | 
|  | BasicBlock block = code.entryBlock(); | 
|  | int instructionCount = block.getInstructions().size(); | 
|  | assertEquals(firstBlockInstructions, instructionCount); | 
|  |  | 
|  | assertEquals(argumentInstructions, test.countArgumentInstructions()); | 
|  | assertEquals(firstBlockInstructions, block.getInstructions().size()); | 
|  | assertTrue(!block.getInstructions().get(i).isArgument()); | 
|  |  | 
|  | InstructionListIterator iterator = test.listIteratorAt(block, i); | 
|  | BasicBlock newBlock = iterator.split(code, 1); | 
|  | assertTrue(code.isConsistentSSA()); | 
|  |  | 
|  | assertEquals(initialBlockCount + 2, code.blocks.size()); | 
|  | assertEquals(i + 1, code.entryBlock().getInstructions().size()); | 
|  | assertEquals(2, code.blocks.get(1).getInstructions().size()); | 
|  | assertEquals(instructionCount - i - 1, code.blocks.get(2).getInstructions().size()); | 
|  | assertSame(newBlock, code.blocks.get(1)); | 
|  |  | 
|  | // Run code and check result (code in the test object is updated). | 
|  | String result = test.run(); | 
|  | assertEquals("6", result); | 
|  | } | 
|  | } | 
|  |  | 
|  | private TestApplication codeWithCatchHandlers(boolean shouldThrow, boolean twoGuards) { | 
|  | SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); | 
|  |  | 
|  | String secondGuard = twoGuards ? | 
|  | "    .catch Ljava/lang/Exception; {:try_start .. :try_end} :catch" : "    "; | 
|  |  | 
|  | String returnType = "int"; | 
|  | List<String> parameters = ImmutableList.of("int", "int"); | 
|  | MethodSignature signature = builder.addStaticMethod( | 
|  | returnType, | 
|  | DEFAULT_METHOD_NAME, | 
|  | parameters, | 
|  | 0, | 
|  | "    :try_start", | 
|  | "    add-int             p0, p0, p0", | 
|  | "    add-int             p1, p1, p1", | 
|  | "    div-int             p0, p0, p1", | 
|  | "    :try_end", | 
|  | "    .catch Ljava/lang/ArithmeticException; {:try_start .. :try_end} :catch", | 
|  | secondGuard, | 
|  | "    :return", | 
|  | "    return              p0", | 
|  | "    :catch", | 
|  | "    const/4             p0, -1", | 
|  | "    goto :return" | 
|  | ); | 
|  |  | 
|  | builder.addMainMethod( | 
|  | 3, | 
|  | "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", | 
|  | "    const/4             v1, 2", | 
|  | "    const/4             v2, " + (shouldThrow ? "0" : "1"), | 
|  | "    invoke-static       { v1, v2 }, LTest;->method(II)I", | 
|  | "    move-result         v1", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V", | 
|  | "    return-void" | 
|  | ); | 
|  |  | 
|  | InternalOptions options = new InternalOptions(); | 
|  | DexApplication application = buildApplication(builder, options); | 
|  | AppView<?> appView = AppView.createForD8(new AppInfo(application), options); | 
|  |  | 
|  | // Return the processed method for inspection. | 
|  | MethodSubject methodSubject = getMethodSubject(application, signature); | 
|  | return new TestApplication(appView, methodSubject); | 
|  | } | 
|  |  | 
|  | private void hasCatchandlerIfThrowing(BasicBlock block) { | 
|  | boolean throwing = false; | 
|  | for (Instruction instruction : block.getInstructions()) { | 
|  | throwing |= instruction.instructionTypeCanThrow(); | 
|  | } | 
|  | assertEquals(throwing, block.hasCatchHandlers()); | 
|  | } | 
|  |  | 
|  | private void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception { | 
|  | final int secondBlockInstructions = 4; | 
|  | final int initialBlockCount = twoGuards ? 7 : 6; | 
|  | // Try split between all instructions in second block. | 
|  | for (int i = 1; i < secondBlockInstructions; i++) { | 
|  | TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards); | 
|  | IRCode code = test.code; | 
|  | assertEquals(initialBlockCount, code.blocks.size()); | 
|  |  | 
|  | BasicBlock block = code.blocks.get(1); | 
|  | int instructionCount = block.getInstructions().size(); | 
|  | assertEquals(secondBlockInstructions, instructionCount); | 
|  |  | 
|  | InstructionListIterator iterator = test.listIteratorAt(block, i); | 
|  | BasicBlock newBlock = iterator.split(code); | 
|  | assertTrue(code.isConsistentSSA()); | 
|  |  | 
|  | assertEquals(initialBlockCount + 1, code.blocks.size()); | 
|  | assertEquals(i + 1, code.blocks.get(1).getInstructions().size()); | 
|  | assertEquals(instructionCount - i, newBlock.getInstructions().size()); | 
|  | assertSame(newBlock, code.blocks.get(2)); | 
|  |  | 
|  | code.blocks.forEach(this::hasCatchandlerIfThrowing); | 
|  |  | 
|  | // Run code and check result (code in the test object is updated). | 
|  | String result = test.run(); | 
|  | assertEquals(codeThrows ? "-1" : "2", result); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void catchHandlers() throws Exception { | 
|  | runCatchHandlerTest(false, false); | 
|  | runCatchHandlerTest(true, false); | 
|  | runCatchHandlerTest(false, true); | 
|  | runCatchHandlerTest(true, true); | 
|  | } | 
|  |  | 
|  | private void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards) | 
|  | throws Exception { | 
|  | final int secondBlockInstructions = 4; | 
|  | final int initialBlockCount = twoGuards ? 7 : 6; | 
|  | // Try split out all instructions in second block. | 
|  | for (int i = 1; i < secondBlockInstructions - 1; i++) { | 
|  | TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards); | 
|  | IRCode code = test.code; | 
|  | assertEquals(initialBlockCount, code.blocks.size()); | 
|  |  | 
|  | BasicBlock block = code.blocks.get(1); | 
|  | int instructionCount = block.getInstructions().size(); | 
|  | assertEquals(secondBlockInstructions, instructionCount); | 
|  |  | 
|  | InstructionListIterator iterator = test.listIteratorAt(block, i); | 
|  | BasicBlock newBlock = iterator.split(code, 1); | 
|  | assertTrue(code.isConsistentSSA()); | 
|  |  | 
|  | assertEquals(initialBlockCount + 2, code.blocks.size()); | 
|  | assertEquals(i + 1, code.blocks.get(1).getInstructions().size()); | 
|  | assertEquals(2, newBlock.getInstructions().size()); | 
|  | assertEquals(instructionCount - i - 1, code.blocks.get(3).getInstructions().size()); | 
|  | assertSame(newBlock, code.blocks.get(2)); | 
|  |  | 
|  | code.blocks.forEach(this::hasCatchandlerIfThrowing); | 
|  |  | 
|  | // Run code and check result (code in the test object is updated). | 
|  | String result = test.run(); | 
|  | assertEquals(codeThrows ? "-1" : "2", result); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void catchHandlersSplitThree() throws Exception { | 
|  | runCatchHandlerSplitThreeTest(false, false); | 
|  | runCatchHandlerSplitThreeTest(true, false); | 
|  | runCatchHandlerSplitThreeTest(false, true); | 
|  | runCatchHandlerSplitThreeTest(true, true); | 
|  | } | 
|  |  | 
|  | private TestApplication codeWithIf(boolean hitTrueBranch) { | 
|  | SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); | 
|  |  | 
|  | String returnType = "int"; | 
|  | List<String> parameters = ImmutableList.of("int", "int"); | 
|  | MethodSignature signature = builder.addStaticMethod( | 
|  | returnType, | 
|  | DEFAULT_METHOD_NAME, | 
|  | parameters, | 
|  | 0, | 
|  | "    if-eq               p0, p1, :eq", | 
|  | "    const/4             p0, 1", | 
|  | "    return              p0", | 
|  | "    :eq", | 
|  | "    const/4             p0, 0", | 
|  | "    return              p0" | 
|  | ); | 
|  |  | 
|  | builder.addMainMethod( | 
|  | 3, | 
|  | "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", | 
|  | "    const/4             v1, 2", | 
|  | "    const/4             v2, " + (hitTrueBranch ? "2" : "3"), | 
|  | "    invoke-static       { v1, v2 }, LTest;->method(II)I", | 
|  | "    move-result         v1", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V", | 
|  | "    return-void" | 
|  | ); | 
|  |  | 
|  | InternalOptions options = new InternalOptions(); | 
|  | DexApplication application = buildApplication(builder, options); | 
|  | AppView<?> appView = AppView.createForD8(new AppInfo(application), options); | 
|  |  | 
|  | // Return the processed method for inspection. | 
|  | MethodSubject methodSubject = getMethodSubject(application, signature); | 
|  | return new TestApplication(appView, methodSubject); | 
|  | } | 
|  |  | 
|  | private void runWithIfTest(boolean hitTrueBranch) throws Exception { | 
|  | final int initialBlockCount = 3; | 
|  | final int argumentInstructions = 2; | 
|  | final int firstBlockInstructions = 3; | 
|  | // Try split between all non-argument instructions in the first block. | 
|  | for (int i = argumentInstructions; i < firstBlockInstructions; i++) { | 
|  | TestApplication test = codeWithIf(hitTrueBranch); | 
|  | IRCode code = test.code; | 
|  | assertEquals(initialBlockCount, code.blocks.size()); | 
|  |  | 
|  | BasicBlock block = code.entryBlock(); | 
|  | int instructionCount = block.getInstructions().size(); | 
|  | assertEquals(firstBlockInstructions, instructionCount); | 
|  |  | 
|  | assertEquals(argumentInstructions, test.countArgumentInstructions()); | 
|  | assertEquals(firstBlockInstructions, block.getInstructions().size()); | 
|  | assertTrue(!block.getInstructions().get(i).isArgument()); | 
|  |  | 
|  | InstructionListIterator iterator = test.listIteratorAt(block, i); | 
|  | BasicBlock newBlock = iterator.split(code); | 
|  | assertTrue(code.isConsistentSSA()); | 
|  |  | 
|  | assertEquals(initialBlockCount + 1, code.blocks.size()); | 
|  | assertEquals(i + 1, code.entryBlock().getInstructions().size()); | 
|  | assertEquals(instructionCount - i, newBlock.getInstructions().size()); | 
|  | assertSame(newBlock, code.blocks.get(1)); | 
|  |  | 
|  | // Run code and check result (code in the test object is updated). | 
|  | String result = test.run(); | 
|  | assertEquals(hitTrueBranch ? "0" : "1", result); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void withIf() throws Exception { | 
|  | runWithIfTest(false); | 
|  | runWithIfTest(true); | 
|  | } | 
|  |  | 
|  | private void splitBeforeReturn(boolean hitTrueBranch) throws Exception { | 
|  | TestApplication test = codeWithIf(hitTrueBranch); | 
|  | IRCode code = test.code; | 
|  | // Locate the exit blocks and split before the return. | 
|  | List<BasicBlock> exitBlocks = new ArrayList<>(code.computeNormalExitBlocks()); | 
|  | for (BasicBlock originalReturnBlock : exitBlocks) { | 
|  | InstructionListIterator iterator = | 
|  | originalReturnBlock.listIterator(code, originalReturnBlock.getInstructions().size()); | 
|  | Instruction ret = iterator.previous(); | 
|  | assert ret.isReturn(); | 
|  | BasicBlock newReturnBlock = iterator.split(code); | 
|  | // Modify the code to make the inserted block add the constant 10 to the original return | 
|  | // value. | 
|  | Value newConstValue = | 
|  | new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null); | 
|  | Value newReturnValue = | 
|  | new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null); | 
|  | Value oldReturnValue = newReturnBlock.iterator().next().asReturn().returnValue(); | 
|  | newReturnBlock.iterator().next().asReturn().returnValue().replaceUsers(newReturnValue); | 
|  | Instruction constInstruction = new ConstNumber(newConstValue, 10); | 
|  | Instruction addInstruction = | 
|  | new Add(NumericType.INT, newReturnValue, oldReturnValue, newConstValue); | 
|  | iterator.previous(); | 
|  | iterator.add(constInstruction); | 
|  | iterator.add(addInstruction); | 
|  | addInstruction.setPosition(Position.none()); | 
|  | constInstruction.setPosition(Position.none()); | 
|  | } | 
|  | // Run code and check result (code in the test object is updated). | 
|  | String result = test.run(); | 
|  | assertEquals(hitTrueBranch ? "10" : "11", result); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void splitBeforeReturn() throws Exception { | 
|  | splitBeforeReturn(false); | 
|  | splitBeforeReturn(true); | 
|  | } | 
|  |  | 
|  | private TestApplication codeWithSwitch(boolean hitCase) { | 
|  | SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); | 
|  |  | 
|  | String returnType = "int"; | 
|  | List<String> parameters = ImmutableList.of("int"); | 
|  | MethodSignature signature = builder.addStaticMethod( | 
|  | returnType, | 
|  | DEFAULT_METHOD_NAME, | 
|  | parameters, | 
|  | 0, | 
|  | "    packed-switch       p0, :packed_switch_data", | 
|  | "    const/4             p0, 0x5", | 
|  | "    goto                :return", | 
|  | "    :case_0", | 
|  | "    const/4             p0, 0x2", | 
|  | "    goto                :return", | 
|  | "    :case_1", | 
|  | "    const/4             p0, 0x3", | 
|  | "    :return", | 
|  | "    return              p0", | 
|  | "    :packed_switch_data", | 
|  | "    .packed-switch 0x0", | 
|  | "      :case_0", | 
|  | "      :case_1", | 
|  | "    .end packed-switch" | 
|  | ); | 
|  |  | 
|  | builder.addMainMethod( | 
|  | 2, | 
|  | "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", | 
|  | "    const/4             v1, " + (hitCase ? "1" : "2"), | 
|  | "    invoke-static       { v1 }, LTest;->method(I)I", | 
|  | "    move-result         v1", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V", | 
|  | "    return-void" | 
|  | ); | 
|  |  | 
|  | InternalOptions options = new InternalOptions(); | 
|  | DexApplication application = buildApplication(builder, options); | 
|  | AppView<?> appView = AppView.createForD8(new AppInfo(application), options); | 
|  |  | 
|  | // Return the processed method for inspection. | 
|  | MethodSubject methodSubject = getMethodSubject(application, signature); | 
|  | return new TestApplication(appView, methodSubject); | 
|  | } | 
|  |  | 
|  | private void runWithSwitchTest(boolean hitCase) throws Exception { | 
|  | final int initialBlockCount = 5; | 
|  | final int argumentInstructions = 1; | 
|  | final int firstBlockInstructions = 2; | 
|  | // Try split between all non-argument instructions in the first block. | 
|  | for (int i = argumentInstructions; i < firstBlockInstructions; i++) { | 
|  | TestApplication test = codeWithSwitch(hitCase); | 
|  | IRCode code = test.code; | 
|  | assertEquals(initialBlockCount, code.blocks.size()); | 
|  |  | 
|  | BasicBlock block = code.entryBlock(); | 
|  | int instructionCount = block.getInstructions().size(); | 
|  | assertEquals(firstBlockInstructions, instructionCount); | 
|  |  | 
|  | assertEquals(argumentInstructions, test.countArgumentInstructions()); | 
|  | assertEquals(firstBlockInstructions, block.getInstructions().size()); | 
|  | assertTrue(!block.getInstructions().get(i).isArgument()); | 
|  |  | 
|  | InstructionListIterator iterator = test.listIteratorAt(block, i); | 
|  | BasicBlock newBlock = iterator.split(code); | 
|  | assertTrue(code.isConsistentSSA()); | 
|  |  | 
|  | assertEquals(initialBlockCount + 1, code.blocks.size()); | 
|  | assertEquals(i + 1, code.entryBlock().getInstructions().size()); | 
|  | assertEquals(instructionCount - i, newBlock.getInstructions().size()); | 
|  | assertSame(newBlock, code.blocks.get(1)); | 
|  |  | 
|  | // Run code and check result (code in the test object is updated). | 
|  | String result = test.run(); | 
|  | assertEquals(hitCase ? "3" : "5", result); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void withSwitch() throws Exception { | 
|  | runWithSwitchTest(false); | 
|  | runWithSwitchTest(true); | 
|  | } | 
|  | } |