Merge "CF backend: Omit writing code for native methods"
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index d709f49..ff37507 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -196,10 +196,11 @@
     labels = new HashMap<>(code.blocks.size());
     emittedLabels = new HashSet<>(code.blocks.size());
     instructions = new ArrayList<>();
-    Iterator<BasicBlock> blockIterator = code.listIterator();
+    ListIterator<BasicBlock> blockIterator = code.listIterator();
     BasicBlock block = blockIterator.next();
     CfLabel tryCatchStart = null;
     CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK;
+    BasicBlock pendingFrame = null;
     do {
       CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
       if (!tryCatchHandlers.equals(handlers)) {
@@ -229,10 +230,26 @@
         pendingLocalChanges = true;
       }
       buildCfInstructions(block, fallthrough, stack);
-      if (nextBlock != null && (!fallthrough || nextBlock.getPredecessors().size() > 1)) {
-        assert stack.isEmpty();
-        emitLabel(getLabel(nextBlock));
-        addFrame(nextBlock, Collections.emptyList());
+      if (nextBlock != null) {
+        if (!fallthrough || nextBlock.getPredecessors().size() > 1) {
+          assert stack.isEmpty();
+          pendingFrame = nextBlock;
+          emitLabel(getLabel(nextBlock));
+        }
+        if (pendingFrame != null) {
+          BasicBlock nextNextBlock = null;
+          if (blockIterator.hasNext()) {
+            nextNextBlock = blockIterator.next();
+            blockIterator.previous();
+          }
+          boolean advancesPC = hasMaterializingInstructions(nextBlock, nextNextBlock);
+          // If nextBlock has no materializing instructions, then nextNextBlock must be non-null
+          // (or we would fall off the edge of the method).
+          assert advancesPC || nextNextBlock != null;
+          if (advancesPC) {
+            addFrame(pendingFrame, Collections.emptyList());
+          }
+        }
       }
       block = nextBlock;
     } while (block != null);
@@ -251,6 +268,25 @@
         localVariablesTable);
   }
 
+  private static boolean isNopInstruction(Instruction instruction, BasicBlock nextBlock) {
+    // From DexBuilder
+    return instruction.isArgument()
+        || instruction.isDebugLocalsChange()
+        || (instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock);
+  }
+
+  private boolean hasMaterializingInstructions(BasicBlock block, BasicBlock nextBlock) {
+    if (block == null) {
+      return false;
+    }
+    for (Instruction instruction : block.getInstructions()) {
+      if (!isNopInstruction(instruction, nextBlock)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
     InstructionIterator it = block.iterator();
     while (it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index ca62583..b526973 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -352,21 +352,9 @@
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule());
       } else if (acceptString("if")) {
-        // TODO(b/73708139): add support -if <class_spec>
-        ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
-        parseClassSpec(keepRuleBuilder, true);
-        ProguardKeepRule ifRule = keepRuleBuilder.build();
-        assert ifRule.getClassType() == ProguardClassType.CLASS;
-
-        // Required a subsequent keep rule.
-        skipWhitespace();
-        if (acceptString("-keep")) {
-          ProguardKeepRule subsequentRule = parseKeepRule();
-          warnIgnoringOptions("if", optionStart);
-        } else {
-          throw reporter.fatalError(new StringDiagnostic(
-              "Option -if without a subsequent keep rule.", origin, getPosition(optionStart)));
-        }
+        configurationBuilder.addRule(parseIfRule(optionStart));
+        // TODO(b/73708139): remove warning once we add support -if <class_spec>
+        warnIgnoringOptions("if", optionStart);
       } else {
         String unknownOption = acceptString();
         reporter.error(new StringDiagnostic(
@@ -544,6 +532,22 @@
       return keepRuleBuilder.build();
     }
 
+    private ProguardIfRule parseIfRule(TextPosition optionStart)
+        throws ProguardRuleParserException {
+      ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder();
+      parseClassSpec(ifRuleBuilder, true);
+
+      // Required a subsequent keep rule.
+      skipWhitespace();
+      if (acceptString("-keep")) {
+        ProguardKeepRule subsequentRule = parseKeepRule();
+        ifRuleBuilder.setSubsequentRule(subsequentRule);
+        return ifRuleBuilder.build();
+      }
+      throw reporter.fatalError(new StringDiagnostic(
+          "Option -if without a subsequent keep rule.", origin, getPosition(optionStart)));
+    }
+
     private void parseClassSpec(
         ProguardConfigurationRule.Builder builder, boolean allowValueSpecification)
         throws ProguardRuleParserException {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
new file mode 100644
index 0000000..6facec1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2018, 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.shaking;
+
+import java.util.Set;
+
+public class ProguardIfRule extends ProguardConfigurationRule {
+
+  final ProguardKeepRule subsequentRule;
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    ProguardKeepRule subsequentRule = null;
+
+    private Builder() {
+    }
+
+    public void setSubsequentRule(ProguardKeepRule rule) {
+      subsequentRule = rule;
+    }
+
+    public ProguardIfRule build() {
+      assert subsequentRule != null : "Option -if without a subsequent rule.";
+      return new ProguardIfRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules, subsequentRule);
+    }
+  }
+
+  private ProguardIfRule(ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags, boolean classTypeNegated,
+      ProguardClassType classType, ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName, boolean inheritanceIsExtends,
+      Set<ProguardMemberRule> memberRules,
+      ProguardKeepRule subsequentRule) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    this.subsequentRule = subsequentRule;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "if";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index f504930..9c0dcdd 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -167,7 +167,10 @@
       }
     }
 
-    if (rule.getClassNames().matches(clazz.type)) {
+    if (rule instanceof ProguardIfRule) {
+      // TODO(b/73708139): add support -if <class_spec>
+      // Check if -if part matches or subsequent -keep part matches.
+    } else if (rule.getClassNames().matches(clazz.type)) {
       Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
       if (rule instanceof ProguardKeepRule) {
         switch (((ProguardKeepRule) rule).getType()) {
diff --git a/src/test/java/com/android/tools/r8/cf/NestedExceptionTest.java b/src/test/java/com/android/tools/r8/cf/NestedExceptionTest.java
new file mode 100644
index 0000000..f7823a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/NestedExceptionTest.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, 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.cf;
+
+public class NestedExceptionTest {
+
+  public static void main(String[] args) {
+    try {
+      new Integer(1);
+    } catch (Exception t) {
+      try {
+        new Integer(1);
+      } catch (Exception tInner) {
+        // This block must be empty
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
new file mode 100644
index 0000000..4c7effd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, 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.cf;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class NestedExceptionTestRunner {
+  static final Class CLASS = NestedExceptionTest.class;
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testNestedException() throws Exception {
+    AndroidAppConsumers a = new AndroidAppConsumers();
+    R8.run(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(a.wrapClassFileConsumer(null))
+            .build());
+    Path out = temp.newFolder().toPath();
+    a.build().writeToDirectory(out, OutputMode.ClassFile);
+    ToolHelper.runJava(out, CLASS.getCanonicalName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 00fa060..5f6a5cc 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1152,6 +1152,24 @@
     for (int i = 0; i < 3; i++) {
       assertTrue(handler.warnings.get(i).getDiagnosticMessage().contains("Ignoring option: -if"));
     }
+    ProguardConfiguration config = parser.getConfig();
+    // Three -if rules and one independent -keepnames
+    assertEquals(4, config.getRules().size());
+    long ifCount =
+        config.getRules().stream().filter(rule -> rule instanceof ProguardIfRule).count();
+    assertEquals(3, ifCount);
+    ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
+    assertEquals("**$$ModuleAdapter", if0.getClassNames().toString());
+    assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
+    assertEquals("A", if0.subsequentRule.getClassNames().toString());
+    ProguardIfRule if1 = (ProguardIfRule) config.getRules().get(1);
+    assertEquals("**$$InjectAdapter", if1.getClassNames().toString());
+    assertEquals(ProguardKeepRuleType.KEEP, if1.subsequentRule.getType());
+    assertEquals("B", if1.subsequentRule.getClassNames().toString());
+    ProguardIfRule if2 = (ProguardIfRule) config.getRules().get(2);
+    assertEquals("**$$StaticInjection", if2.getClassNames().toString());
+    assertEquals(ProguardKeepRuleType.KEEP, if2.subsequentRule.getType());
+    assertEquals("C", if2.subsequentRule.getClassNames().toString());
   }
 
   @Test