| // 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.smali; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.DexIndexedConsumer; |
| import com.android.tools.r8.dex.ApplicationReader; |
| 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.graph.ProgramMethod; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Phi; |
| import com.android.tools.r8.ir.code.Return; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Timing; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import org.junit.Test; |
| |
| /** |
| * Regression test to ensure that we do not ignore the exceptional / on-throw value of a |
| * throwing instruction in the special case where the exceptional edge and the normal edge target |
| * the same block. |
| */ |
| public class CatchSuccessorFallthroughTest extends SmaliTestBase { |
| |
| @Test |
| public void catchSuccessorFallthroughTest() throws Exception { |
| |
| SmaliBuilder builder = new SmaliBuilder("Test"); |
| |
| builder.addStaticMethod("int", "maybeThrow", Arrays.asList("int"), 0, |
| " if-eqz v0, :throw", |
| " const v0, 42", |
| " return v0", |
| ":throw", |
| " div-int/2addr v0, v0", |
| " return v0"); |
| |
| MethodSignature methodSig = builder.addStaticMethod( |
| "int", "method", Collections.singletonList("int"), 0, |
| ":try_start", |
| " invoke-static {v0}, LTest;->maybeThrow(I)I", |
| " move-result v0", |
| ":try_end", |
| " .catch Ljava/lang/Throwable; {:try_start .. :try_end} :return", |
| ":return", |
| " return v0" |
| ); |
| |
| builder.addStaticMethod( |
| "void", "main", Arrays.asList("java.lang.String[]"), 2, |
| " sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;", |
| " const v0, 1", |
| " invoke-static {v0}, LTest;->method(I)I", |
| " move-result v0", |
| " invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V", |
| " const v0, 0", |
| " invoke-static {v0}, LTest;->method(I)I", |
| " move-result v0", |
| " invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V", |
| " return-void"); |
| |
| AndroidApp originalApplication = buildApplication(builder); |
| |
| InternalOptions options = new InternalOptions(); |
| options.programConsumer = DexIndexedConsumer.emptyConsumer(); |
| |
| DexApplication application = |
| new ApplicationReader(originalApplication, options, Timing.empty()).read(); |
| |
| ProgramMethod method = getProgramMethod(originalApplication, methodSig); |
| // Get the IR pre-optimization. |
| IRCode code = method.buildIR(AppView.createForD8(AppInfo.createInitialAppInfo(application))); |
| |
| // Find the exit block and assert that the value is a phi merging the exceptional edge |
| // with the normal edge. |
| boolean hasReturn = false; |
| for (BasicBlock block : code.blocks) { |
| if (block.exit() instanceof Return) { |
| // Find the return block. |
| // Check it has one phi with two operands / two predecessors. |
| Return ret = block.exit().asReturn(); |
| assertEquals(2, block.getPredecessors().size()); |
| assertEquals(1, block.getPhis().size()); |
| assertEquals(ret.returnValue(), block.getPhis().get(0)); |
| // Next we find and check that the phi values come from the expected predecessor. |
| boolean hasNormalPredecessor = false; |
| boolean hasExceptionalPredecessor = false; |
| Phi phi = block.getPhis().get(0); |
| for (Value operand : phi.getOperands()) { |
| BasicBlock defBlock = operand.definition.getBlock(); |
| if (defBlock.canThrow()) { |
| // Found the invoke instruction / block. |
| assertEquals(2, defBlock.getSuccessors().size()); |
| assertTrue( |
| defBlock.getInstructions().get(defBlock.getInstructions().size() - 2).isInvoke()); |
| for (BasicBlock returnPredecessor : block.getPredecessors()) { |
| if (defBlock.hasCatchSuccessor(returnPredecessor)) { |
| hasExceptionalPredecessor = true; |
| } else if (defBlock == returnPredecessor) { |
| // Normal flow goes to return. |
| hasNormalPredecessor = true; |
| } else if (defBlock.getSuccessors().contains(returnPredecessor)) { |
| // Normal flow goes to return after an edge split. |
| assertTrue(returnPredecessor.isTrivialGoto()); |
| hasNormalPredecessor = true; |
| } |
| } |
| } |
| } |
| assertTrue(hasNormalPredecessor); |
| assertTrue(hasExceptionalPredecessor); |
| hasReturn = true; |
| } |
| } |
| assertTrue(hasReturn); |
| } |
| } |