Merge "Fix splitting for exception overlapping in release mode."
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0914e7b..9f97f88 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -99,6 +99,7 @@
   private Code code;
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
+  private int classFileVersion = -1;
 
   public DexEncodedMethod(
       DexMethod method,
@@ -114,6 +115,17 @@
     assert code == null || !accessFlags.isAbstract();
   }
 
+  public DexEncodedMethod(
+      DexMethod method,
+      MethodAccessFlags flags,
+      DexAnnotationSet annotationSet,
+      ParameterAnnotationsList annotationsList,
+      Code code,
+      int classFileVersion) {
+    this(method, flags, annotationSet, annotationsList, code);
+    this.classFileVersion = classFileVersion;
+  }
+
   public boolean isProcessed() {
     return compilationState != CompilationState.NOT_PROCESSED;
   }
@@ -303,6 +315,21 @@
     return code.asDexCode().hasDebugPositions();
   }
 
+  public int getClassFileVersion() {
+    assert classFileVersion >= 0;
+    return classFileVersion;
+  }
+
+  public boolean hasClassFileVersion() {
+    return classFileVersion >= 0;
+  }
+
+  public void upgradeClassFileVersion(int version) {
+    assert version >= 0;
+    assert !hasClassFileVersion() || version >= getClassFileVersion();
+    classFileVersion = version;
+  }
+
   public String qualifiedName() {
     return method.qualifiedName();
   }
@@ -753,6 +780,16 @@
     return optimizationInfo;
   }
 
+  public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
+    // Record that the current method uses identifier name string if the inlinee did so.
+    if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
+      markUseIdentifierNameString();
+    }
+    if (inlinee.classFileVersion > classFileVersion) {
+      upgradeClassFileVersion(inlinee.getClassFileVersion());
+    }
+  }
+
   private static Builder builder(DexEncodedMethod from) {
     return new Builder(from);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index c1b843f..0a33000 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -356,9 +356,14 @@
   }
 
   public void setClassFileVersion(int classFileVersion) {
+    assert classFileVersion >= 0;
     this.classFileVersion = classFileVersion;
   }
 
+  public boolean hasClassFileVersion() {
+    return classFileVersion >= 0;
+  }
+
   public int getClassFileVersion() {
     assert classFileVersion != -1;
     return classFileVersion;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 02f0801..64ba508 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static org.objectweb.asm.ClassReader.SKIP_CODE;
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
 import static org.objectweb.asm.Opcodes.ASM6;
@@ -90,7 +91,7 @@
     ClassReader reader = new ClassReader(input);
     reader.accept(
         new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
-        SKIP_FRAMES);
+        SKIP_FRAMES | SKIP_CODE);
   }
 
   private static int cleanAccessFlags(int access) {
@@ -560,6 +561,11 @@
 
     @Override
     public void visitCode() {
+      throw new Unreachable("visitCode() should not be called when SKIP_CODE is set");
+    }
+
+    @Override
+    public void visitEnd() {
       if (!flags.isAbstract() && !flags.isNative() && parent.classKind == ClassKind.PROGRAM) {
         if (parent.application.options.enableCfFrontend) {
           code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
@@ -567,12 +573,6 @@
           code = new JarCode(method, parent.origin, parent.context, parent.application);
         }
       }
-    }
-
-    @Override
-    public void visitEnd() {
-      assert flags.isAbstract() || flags.isNative() || parent.classKind != ClassKind.PROGRAM
-          || code != null;
       ParameterAnnotationsList annotationsList;
       if (parameterAnnotationsLists == null) {
         annotationsList = ParameterAnnotationsList.empty();
@@ -595,8 +595,14 @@
             parameterFlags.toArray(new DexValue[parameterFlags.size()]),
             parent.application.getFactory()));
       }
-      DexEncodedMethod dexMethod = new DexEncodedMethod(method, flags,
-          createAnnotationSet(annotations), annotationsList, code);
+      DexEncodedMethod dexMethod =
+          new DexEncodedMethod(
+              method,
+              flags,
+              createAnnotationSet(annotations),
+              annotationsList,
+              code,
+              parent.version);
       if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
         parent.directMethods.add(dexMethod);
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index d4cb8a8..b75597d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -468,10 +468,7 @@
                   method.accessFlags.unsetBridge();
                 }
 
-                // Record that the current method uses identifier name string if the inlinee did so.
-                if (target.getOptimizationInfo().useIdentifierNameString()) {
-                  method.markUseIdentifierNameString();
-                }
+                method.copyMetadataFromInlinee(target);
               }
             }
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 2e5b703..6cb8f6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -64,6 +64,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import org.objectweb.asm.Opcodes;
 
 /**
  * Support class for implementing outlining (i.e. extracting common code patterns as methods).
@@ -872,6 +873,8 @@
               Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
       DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
       DexMethod method = outline.buildMethod(type, methodName);
+      List<DexEncodedMethod> sites = outlineSites.get(outline);
+      assert !sites.isEmpty();
       direct[count] =
           new DexEncodedMethod(
               method,
@@ -879,6 +882,9 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               new OutlineCode(outline));
+      if (options.isGeneratingClassFiles()) {
+        direct[count].upgradeClassFileVersion(sites.get(0).getClassFileVersion());
+      }
       generatedOutlines.put(outline, method);
       count++;
     }
@@ -908,10 +914,9 @@
             DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
             options.itemFactory.getSkipNameValidationForTesting());
     if (options.isGeneratingClassFiles()) {
-      // Don't set class file version below 50.0 (JDK release 1.6).
-      clazz.setClassFileVersion(Math.max(50, getClassFileVersion(outlines)));
+      // All program classes must have a class-file version. Use Java 6.
+      clazz.setClassFileVersion(Opcodes.V1_6);
     }
-
     return clazz;
   }
 
@@ -927,26 +932,6 @@
     return result;
   }
 
-  private int getClassFileVersion(List<Outline> outlines) {
-    assert options.isGeneratingClassFiles();
-    int classFileVersion = -1;
-    Set<DexType> seen = Sets.newIdentityHashSet();
-    for (Outline outline : outlines) {
-      List<DexEncodedMethod> methods = outlineSites.get(outline);
-      for (DexEncodedMethod method : methods) {
-        DexType holder = method.method.holder;
-        if (seen.add(holder)) {
-          DexProgramClass programClass = appInfo.definitionFor(holder).asProgramClass();
-          assert programClass != null : "Attempt to outline from library class";
-          assert programClass.originatesFromClassResource()
-              : "Attempt to outline from non-classfile input to classfile output";
-          classFileVersion = Math.max(classFileVersion, programClass.getClassFileVersion());
-        }
-      }
-    }
-    return classFileVersion;
-  }
-
   public void applyOutliningCandidate(IRCode code, DexEncodedMethod method) {
     assert !(method.getCode() instanceof OutlineCode);
     ListIterator<BasicBlock> blocksIterator = code.blocks.listIterator();
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 2970fb4..7a25e1a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -108,7 +108,7 @@
   private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
     ClassWriter writer = new ClassWriter(0);
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
-    int version = clazz.getClassFileVersion();
+    int version = getClassFileVersion(clazz);
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
     String name = namingLens.lookupInternalName(clazz.type);
@@ -161,6 +161,17 @@
         options.reporter, handler -> consumer.accept(result, desc, handler));
   }
 
+  private int getClassFileVersion(DexProgramClass clazz) {
+    int version = clazz.getClassFileVersion();
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      version = Math.max(version, method.getClassFileVersion());
+    }
+    for (DexEncodedMethod method : clazz.virtualMethods()) {
+      version = Math.max(version, method.getClassFileVersion());
+    }
+    return version;
+  }
+
   private DexValue getSystemAnnotationValue(DexAnnotationSet annotations, DexType type) {
     DexAnnotation annotation = annotations.getFirstMatching(type);
     if (annotation == null) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d718026..19fe18e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -460,10 +460,11 @@
         DexType baseType = type.toBaseType(appInfo.dexItemFactory);
         if (baseType.isClassType()) {
           DexClass baseClass = appInfo.definitionFor(baseType);
-          if (baseClass != null && !baseClass.isLibraryClass()) {
+          if (baseClass != null && baseClass.isProgramClass()
+              && baseClass.hasDefaultInitializer()) {
             markClassAsInstantiatedWithCompatRule(baseClass);
           } else {
-            // This handles reporting of missing classes.
+            // This also handles reporting of missing classes.
             markTypeAsLive(baseType);
           }
           return true;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
new file mode 100644
index 0000000..7fcb318
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -0,0 +1,156 @@
+// 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.examples;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.io.ByteStreams;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+public class InliningClassVersionTest extends TestBase {
+
+  private final int OLD_VERSION = Opcodes.V1_6;
+  private final String BASE_DESCRIPTOR = DescriptorUtils.javaTypeToDescriptor(Base.class.getName());
+
+  private static class Base {
+
+    public static void main(String[] args) {
+      System.out.println(Inlinee.foo());
+    }
+  }
+
+  private static class Inlinee {
+    public static String foo() {
+      return "Hello from Inlinee!";
+    }
+  }
+
+  private static class DowngradeVisitor extends ClassVisitor {
+
+    private final int version;
+
+    DowngradeVisitor(ClassVisitor cv, int version) {
+      super(Opcodes.ASM6, cv);
+      this.version = version;
+    }
+
+    @Override
+    public void visit(
+        int version,
+        int access,
+        String name,
+        String signature,
+        String superName,
+        String[] interfaces) {
+      assert version > this.version
+          : "Going from " + version + " to " + this.version + " is not a downgrade";
+      super.visit(this.version, access, name, signature, superName, interfaces);
+    }
+  }
+
+  private static byte[] downgradeClass(byte[] classBytes, int version) {
+    ClassWriter writer = new ClassWriter(0);
+    new ClassReader(classBytes).accept(new DowngradeVisitor(writer, version), 0);
+    return writer.toByteArray();
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path inputJar = writeInput();
+    assertEquals(OLD_VERSION, getBaseClassVersion(inputJar));
+    ProcessResult runInput = run(inputJar);
+    assertEquals(0, runInput.exitCode);
+    Path outputJar = runR8(inputJar);
+    ProcessResult runOutput = run(outputJar);
+    assertEquals(runInput.toString(), runOutput.toString());
+    assertNotEquals(
+        "Inliner did not upgrade classfile version", OLD_VERSION, getBaseClassVersion(outputJar));
+  }
+
+  private int getBaseClassVersion(Path jar) throws Exception {
+    return getClassVersion(jar, BASE_DESCRIPTOR);
+  }
+
+  private int getClassVersion(Path jar, String descriptor) throws Exception {
+
+    class ClassVersionReader extends ClassVisitor {
+      private int version = -1;
+
+      private ClassVersionReader() {
+        super(Opcodes.ASM6);
+      }
+
+      @Override
+      public void visit(
+          int version,
+          int access,
+          String name,
+          String signature,
+          String superName,
+          String[] interfaces) {
+        super.visit(version, access, name, signature, superName, interfaces);
+        assert version != -1;
+        this.version = version;
+      }
+    }
+
+    byte[] bytes =
+        ByteStreams.toByteArray(
+            new ArchiveClassFileProvider(jar).getProgramResource(descriptor).getByteStream());
+    ClassVersionReader reader = new ClassVersionReader();
+    new ClassReader(bytes).accept(reader, 0);
+    assert reader.version != -1;
+    return reader.version;
+  }
+
+  private Path writeInput() throws Exception {
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+    consumer.accept(
+        downgradeClass(ToolHelper.getClassAsBytes(Base.class), OLD_VERSION), BASE_DESCRIPTOR, null);
+    consumer.accept(
+        ToolHelper.getClassAsBytes(Inlinee.class),
+        DescriptorUtils.javaTypeToDescriptor(Inlinee.class.getName()),
+        null);
+    consumer.finished(null);
+    return inputJar;
+  }
+
+  private ProcessResult run(Path jar) throws Exception {
+    return ToolHelper.runJava(jar, Base.class.getName());
+  }
+
+  private Path runR8(Path inputJar) throws Exception {
+    List<String> keepRule =
+        Collections.singletonList(
+            "-keep class " + Base.class.getName() + " { public static void main(...); }");
+    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addProgramFiles(inputJar)
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addProguardConfiguration(keepRule, Origin.unknown())
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .build());
+    return outputJar;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/DexMoveInstructionsTest.java b/src/test/java/com/android/tools/r8/smali/DexMoveInstructionsTest.java
new file mode 100644
index 0000000..c92b73b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/DexMoveInstructionsTest.java
@@ -0,0 +1,95 @@
+// 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.smali;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class DexMoveInstructionsTest extends SmaliTestBase {
+
+  public static final String CLASS = "Test";
+
+  @Test
+  public void testValidObjectMoves() throws Throwable {
+    ProcessResult result = testMoves("ExpectedToPass", "Ljava/lang/String;", Arrays.asList(
+        "move-object",
+        "move-object/from16",
+        "move-object/16"));
+    assertEquals(result.toString(), 0, result.exitCode);
+  }
+
+  @Test
+  public void testInvalidObjectMoves() throws Throwable {
+    ProcessResult result = testMoves("ExpectedToFail", "Ljava/lang/String;", Arrays.asList(
+        "move",
+        "move/from16",
+        "move/16"));
+    assertEquals(result.toString(), 1, result.exitCode);
+    assertTrue("Did not find 'Verification error' in " + result.stderr,
+        result.stderr.contains("Verification error") || result.stderr.contains("VerifyError"));
+  }
+
+  @Test
+  public void testValidSingleMoves() throws Throwable {
+    ProcessResult result = testMoves("ExpectedToPass", "I", Arrays.asList(
+        "move",
+        "move/from16",
+        "move/16"));
+    assertEquals(result.toString(), 0, result.exitCode);
+  }
+
+  @Test
+  public void testInvalidSingleMoves() throws Throwable {
+    ProcessResult result = testMoves("ExpectedToFail", "I", Arrays.asList(
+        "move-object",
+        "move-object/from16",
+        "move-object/16"));
+    assertEquals(result.toString(), 1, result.exitCode);
+    assertTrue("Did not find 'Verification error' in " + result.stderr,
+        result.stderr.contains("Verification error") || result.stderr.contains("VerifyError"));
+  }
+
+  private ProcessResult testMoves(String clazz, String typeDesc, List<String> moveOps)
+      throws Throwable {
+    String typeName = DescriptorUtils.descriptorToJavaType(typeDesc);
+
+    SmaliBuilder builder = new SmaliBuilder(clazz);
+    int i = 0;
+    for (String moveOp : moveOps) {
+      builder.addStaticMethod(typeName, "test" + i++, Collections.singletonList(typeName),
+          1,
+          "    " + moveOp + " v0, p0",
+          typeDesc.startsWith("L") ? "return-object v0" : "    return v0"
+      );
+    }
+
+    List<String> main = new ArrayList<>();
+    main.add("  sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;");
+    main.add("  const v2, 0");
+    i = 0;
+    for (String moveOp : moveOps) {
+      main.add("  invoke-static { v2 }, L" + clazz + ";->test" + i++
+          + "(" + typeDesc + ")" + typeDesc);
+      if (typeDesc.startsWith("L")) {
+        main.add("  move-result-object v1");
+      } else {
+        main.add("  move-result v1");
+      }
+      main.add("  invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(" + typeDesc + ")V");
+    }
+    main.add("  return-void");
+    builder.addMainMethod(3, main.toArray(new String[0]));
+
+    return runOnArtRaw(builder.build(), clazz);
+  }
+}