Merge "Update dx to a version that support class file v52."
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 72402b3..5bea64e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -327,7 +327,8 @@
     targets.put(INITIAL_BLOCK_OFFSET, new BlockInfo());
 
     // Process reachable code paths starting from instruction 0.
-    processedInstructions = new boolean[source.instructionCount()];
+    int instCount = source.instructionCount();
+    processedInstructions = new boolean[instCount];
     traceBlocksWorklist.add(0);
     while (!traceBlocksWorklist.isEmpty()) {
       int startOfBlockOffset = traceBlocksWorklist.remove();
@@ -337,17 +338,17 @@
         continue;
       }
       // Process each instruction until the block is closed.
-      for (int index = startOfBlockIndex; index < source.instructionCount(); ++index) {
+      for (int index = startOfBlockIndex; index < instCount; ++index) {
         markIndexProcessed(index);
         int closedAt = source.traceInstruction(index, this);
         if (closedAt != -1) {
-          if (closedAt + 1 < source.instructionCount()) {
+          if (closedAt + 1 < instCount) {
             ensureBlockWithoutEnqueuing(source.instructionOffset(closedAt + 1));
           }
           break;
         }
         // If the next instruction starts a block, fall through to it.
-        if (index + 1 < source.instructionCount()) {
+        if (index + 1 < instCount) {
           int nextOffset = source.instructionOffset(index + 1);
           if (targets.get(nextOffset) != null) {
             ensureNormalSuccessorBlock(startOfBlockOffset, nextOffset);
@@ -491,7 +492,8 @@
         continue;
       }
       // Build IR for each dex instruction in the block.
-      for (int i = item.firstInstructionIndex; i < source.instructionCount(); ++i) {
+      int instCount = source.instructionCount();
+      for (int i = item.firstInstructionIndex; i < instCount; ++i) {
         if (currentBlock == null) {
           break;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 9d2cd1d..2298bc6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -424,11 +424,12 @@
     for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) {
       state.restoreState(item.instructionIndex);
       // Iterate each of the instructions in the block to compute the outgoing JarState.
-      for (int i = item.instructionIndex; i <= instructionCount(); ++i) {
+      int instCount = instructionCount();
+      for (int i = item.instructionIndex; i <= instCount; ++i) {
         // If we are at the end of the instruction stream or if we have reached the start
         // of a new block, propagate the state to all successors and add the ones
         // that changed to the worklist.
-        if (i == instructionCount() || (i != item.instructionIndex && CFG.containsKey(i))) {
+        if (i == instCount || (i != item.instructionIndex && CFG.containsKey(i))) {
           item.blockInfo.normalSuccessors.iterator().forEachRemaining(offset -> {
             if (state.recordStateForTarget(offset)) {
               if (offset >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 9b893d1..dc0cd0f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -225,7 +225,8 @@
   private final Map<Origin, List<InvalidParameterAnnotationInfo>> warningInvalidParameterAnnotations
       = new HashMap<>();
 
-  private final Map<Origin, List<DexEncodedMethod>> warningInvalidDebugInfo = new HashMap<>();
+  private final Map<Origin, List<Pair<DexEncodedMethod, String>>> warningInvalidDebugInfo
+      = new HashMap<>();
 
   // Don't read code from dex files. Used to extract non-code information from vdex files where
   // the code contains unsupported byte codes.
@@ -268,7 +269,8 @@
   public void warningInvalidDebugInfo(
       DexEncodedMethod method, Origin origin, InvalidDebugInfoException e) {
     synchronized (warningInvalidDebugInfo) {
-      warningInvalidDebugInfo.computeIfAbsent(origin, k -> new ArrayList<>()).add(method);
+      warningInvalidDebugInfo.computeIfAbsent(
+          origin, k -> new ArrayList<>()).add(new Pair<>(method, e.getMessage()));
     }
   }
 
@@ -299,7 +301,7 @@
     }
     if (warningInvalidDebugInfo.size() > 0) {
       int count = 0;
-      for (List<DexEncodedMethod> methods : warningInvalidDebugInfo.values()) {
+      for (List<Pair<DexEncodedMethod, String>> methods : warningInvalidDebugInfo.values()) {
         count += methods.size();
       }
       reporter.warning(
@@ -309,10 +311,11 @@
                   + (count == 1 ? " method." : " methods.")));
       for (Origin origin : new TreeSet<>(warningInvalidDebugInfo.keySet())) {
         StringBuilder builder = new StringBuilder("Methods with invalid locals information:");
-        for (DexEncodedMethod method : warningInvalidDebugInfo.get(origin)) {
-          builder.append("\n  ").append(method.toSourceString());
+        for (Pair<DexEncodedMethod, String> method : warningInvalidDebugInfo.get(origin)) {
+          builder.append("\n  ").append(method.getFirst().toSourceString());
+          builder.append("\n  ").append(method.getSecond());
         }
-        reporter.info(new StringDiagnostic(builder.toString(), origin));
+        reporter.warning(new StringDiagnostic(builder.toString(), origin));
       }
       printed = true;
       printOutdatedToolchain = true;
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
index 28fe5dc..79017bc 100644
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+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.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
@@ -13,7 +16,6 @@
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.stream.Collectors;
-import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -31,13 +33,15 @@
     List<Path> inputs =
         ImmutableList.of(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "arithmetic.jar"));
 
+    String keepMain = "-keep public class arithmetic.Arithmetic {\n"
+        + "  public static void main(java.lang.String[]);\n"
+        + "}";
+
     Path pgConf = temp.getRoot().toPath().resolve("pg.conf");
-    FileUtils.writeTextFile(
-        pgConf, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+    FileUtils.writeTextFile(pgConf, keepMain);
 
     Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
-    FileUtils.writeTextFile(
-        mainDexRules, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+    FileUtils.writeTextFile(mainDexRules, keepMain);
 
     Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
     FileUtils.writeTextFile(mainDexList, "arithmetic/Arithmetic.class");
@@ -68,8 +72,8 @@
 
     ProcessBuilder builder = new ProcessBuilder(command);
     ProcessResult result = ToolHelper.runProcess(builder);
-    Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
-    Assert.assertTrue(result.stdout, result.stdout.isEmpty());
-    Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+    assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+    assertTrue(result.stdout, result.stdout.isEmpty());
+    assertTrue(result.stderr, result.stderr.isEmpty());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 2a613b4..5d7bec0 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -377,7 +377,7 @@
     Path proguardPrintSeedsConfiguration = temp.newFile("printseeds.txt").toPath().toAbsolutePath();
     FileUtils.writeTextFile(proguardPrintSeedsConfiguration, ImmutableList.of("-printseeds"));
     ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
-    assertTrue(result.exitCode == 0);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
     assertTrue(result.stdout.contains("void main(java.lang.String[])"));
   }
 
@@ -386,7 +386,7 @@
     Path proguardPrintUsageConfiguration = temp.newFile("printusage.txt").toPath().toAbsolutePath();
     FileUtils.writeTextFile(proguardPrintUsageConfiguration, ImmutableList.of("-printusage"));
     ProcessResult result = runR8OnShaking1(proguardPrintUsageConfiguration);
-    assertTrue(result.exitCode == 0);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
     assertTrue(result.stdout.contains("shaking1.Unused"));
   }
 
@@ -397,7 +397,7 @@
     FileUtils.writeTextFile(
         proguardPrintSeedsConfiguration, ImmutableList.of("-printseeds", "-printusage"));
     ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
-    assertTrue(result.exitCode == 0);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
     assertTrue(result.stdout.contains("void main(java.lang.String[])"));
     assertTrue(result.stdout.contains("shaking1.Unused"));
   }
diff --git a/src/test/sampleApks/simple/AndroidManifest.xml b/src/test/sampleApks/simple/AndroidManifest.xml
new file mode 100644
index 0000000..f959dbd
--- /dev/null
+++ b/src/test/sampleApks/simple/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tools.r8.sample.simple"
+    android:versionCode="1"
+    android:versionName="0.1" >
+
+  <uses-sdk android:minSdkVersion="21" />
+
+  <application
+      android:icon="@drawable/icon"
+      android:label="@string/app_name" >
+    <activity
+        android:name=".R8Activity"
+        android:label="@string/app_name" >
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+  </application>
+</manifest>
diff --git a/src/test/sampleApks/simple/assets/README.txt b/src/test/sampleApks/simple/assets/README.txt
new file mode 100644
index 0000000..fdf73af
--- /dev/null
+++ b/src/test/sampleApks/simple/assets/README.txt
@@ -0,0 +1 @@
+Sample app from R8 project
diff --git a/src/test/sampleApks/simple/res/drawable-mdpi/icon.png b/src/test/sampleApks/simple/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..0799d58
--- /dev/null
+++ b/src/test/sampleApks/simple/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/src/test/sampleApks/simple/res/layout/main.xml b/src/test/sampleApks/simple/res/layout/main.xml
new file mode 100644
index 0000000..7859435
--- /dev/null
+++ b/src/test/sampleApks/simple/res/layout/main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="fill_parent"
+    android:layout_height="fill_parent" android:id="@+id/MainLayout"
+    android:background="@android:color/background_light">
+
+  <Button
+      android:layout_height="wrap_content"
+      android:layout_width="match_parent" android:id="@+id/PressButton"
+      android:layout_margin="2dip"
+      android:text="Do something"/>
+</LinearLayout>
diff --git a/src/test/sampleApks/simple/res/values/strings.xml b/src/test/sampleApks/simple/res/values/strings.xml
new file mode 100644
index 0000000..8bae26c
--- /dev/null
+++ b/src/test/sampleApks/simple/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<resources>
+  <string name="app_name">R8 simple app</string>
+</resources>
diff --git a/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
new file mode 100644
index 0000000..4a09fd4
--- /dev/null
+++ b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
@@ -0,0 +1,17 @@
+// 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.sample.simple;
+
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.tools.r8.sample.simple.R;
+
+public class R8Activity extends Activity {
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setTheme(android.R.style.Theme_Light);
+    setContentView(R.layout.main);
+  }
+}
diff --git a/tools/apk-masseur.py b/tools/apk-masseur.py
index 8e98e47..4f24f0f 100755
--- a/tools/apk-masseur.py
+++ b/tools/apk-masseur.py
@@ -3,6 +3,7 @@
 # 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.
 
+import apk_utils
 import glob
 import optparse
 import os
@@ -68,23 +69,8 @@
   return processed_apk
 
 def sign(unsigned_apk, keystore, temp):
-  print 'Signing (ignore the warnings)'
-  cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
-  utils.PrintCmd(cmd)
-  subprocess.call(cmd)
   signed_apk = os.path.join(temp, 'unaligned.apk')
-  cmd = [
-    'jarsigner',
-    '-sigalg', 'SHA1withRSA',
-    '-digestalg', 'SHA1',
-    '-keystore', keystore,
-    '-storepass', 'android',
-    '-signedjar', signed_apk,
-    unsigned_apk,
-    'androiddebugkey'
-  ]
-  utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  apk_utils.sign(unsigned_apk, signed_apk, keystore)
   return signed_apk
 
 def align(signed_apk, temp):
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
new file mode 100644
index 0000000..a4c0471
--- /dev/null
+++ b/tools/apk_utils.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# 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.
+
+import subprocess
+import utils
+
+def sign(unsigned_apk, signed_apk, keystore):
+  print 'Signing (ignore the warnings)'
+  cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
+  utils.PrintCmd(cmd)
+  subprocess.call(cmd)
+  cmd = [
+    'jarsigner',
+    '-sigalg', 'SHA1withRSA',
+    '-digestalg', 'SHA1',
+    '-keystore', keystore,
+    '-storepass', 'android',
+    '-signedjar', signed_apk,
+    unsigned_apk,
+    'androiddebugkey'
+  ]
+  utils.PrintCmd(cmd)
+  subprocess.check_call(cmd)
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
new file mode 100755
index 0000000..5d691d9
--- /dev/null
+++ b/tools/build_sample_apk.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+# 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.
+
+# Script for building sample apks using the sdk tools directly.
+
+import apk_utils
+import fnmatch
+import glob
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+import utils
+
+ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
+DEFAULT_AAPT = 'aapt' # Assume in path.
+DEFAULT_D8 = os.path.join(utils.REPO_ROOT, 'tools', 'd8.py')
+DEFAULT_JAVAC = 'javac'
+SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
+DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
+
+SAMPLE_APKS = [
+    'simple'
+]
+
+def parse_options():
+  result = optparse.OptionParser()
+  result.add_option('--aapt',
+                    help='aapt executable to use',
+                    default=DEFAULT_AAPT)
+  result.add_option('--api',
+                    help='Android api level',
+                    default=21,
+                    choices=[14, 15, 19, 21, 22, 23, 24, 25, 26])
+  result.add_option('--keystore',
+                    help='Keystore used for signing',
+                    default=DEFAULT_KEYSTORE)
+  result.add_option('--app',
+                    help='Which app to build',
+                    default='simple',
+                    choices=SAMPLE_APKS)
+  return result.parse_args()
+
+def run_aapt(aapt, args):
+  command = [aapt]
+  command.extend(args)
+  utils.PrintCmd(command)
+  subprocess.check_call(command)
+
+def get_build_dir(app):
+  return os.path.join(utils.BUILD, 'sampleApks', app)
+
+def get_gen_path(app):
+  gen_path = os.path.join(get_build_dir(app), 'gen')
+  utils.makedirs_if_needed(gen_path)
+  return gen_path
+
+def get_bin_path(app):
+  bin_path = os.path.join(get_build_dir(app), 'bin')
+  utils.makedirs_if_needed(bin_path)
+  return bin_path
+
+def get_android_jar(api):
+  return os.path.join(utils.REPO_ROOT, ANDROID_JAR.format(api=api))
+
+def get_sample_dir(app):
+  return os.path.join(utils.REPO_ROOT, 'src', 'test', 'sampleApks', app)
+
+def get_src_path(app):
+  return os.path.join(get_sample_dir(app), 'src')
+
+def run_aapt_pack(aapt, api, app):
+  with utils.ChangedWorkingDirectory(get_sample_dir(app)):
+    args = ['package',
+            '-v', '-f',
+            '-I', get_android_jar(api),
+            '-M', 'AndroidManifest.xml',
+            '-A', 'assets',
+            '-S', 'res',
+            '-m',
+            '-J', get_gen_path(app),
+            '-F', os.path.join(get_bin_path(app), 'resources.ap_')]
+    run_aapt(aapt, args)
+
+def compile_with_javac(api, app):
+  with utils.ChangedWorkingDirectory(get_sample_dir(app)):
+    files = glob.glob(SRC_LOCATION.format(app=app))
+    command = [DEFAULT_JAVAC,
+               '-classpath', get_android_jar(api),
+               '-sourcepath', '%s:%s' % (get_src_path(app), get_gen_path(app)),
+               '-d', get_bin_path(app)]
+    command.extend(files)
+    utils.PrintCmd(command)
+    subprocess.check_call(command)
+
+def dex(app, api):
+  files = []
+  for root, dirnames, filenames in os.walk(get_bin_path(app)):
+    for filename in fnmatch.filter(filenames, '*.class'):
+        files.append(os.path.join(root, filename))
+  command = [DEFAULT_D8,
+             '--output', get_bin_path(app),
+             '--classpath', get_android_jar(api),
+             '--min-api', str(api)]
+  command.extend(files)
+  utils.PrintCmd(command)
+  subprocess.check_call(command)
+
+def create_temp_apk(app):
+  temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
+  shutil.move(os.path.join(get_bin_path(app), 'resources.ap_'),
+              temp_apk_path)
+  return temp_apk_path
+
+def aapt_add_dex(aapt, app, temp_apk_path):
+  args = ['add',
+          '-k', temp_apk_path,
+          os.path.join(get_bin_path(app), 'classes.dex')]
+  run_aapt(aapt, args)
+
+def Main():
+  (options, args) = parse_options()
+  run_aapt_pack(options.aapt, options.api, options.app)
+  compile_with_javac(options.api, options.app)
+  dex(options.app, options.api)
+  temp_apk_path = create_temp_apk(options.app)
+  aapt_add_dex(options.aapt, options.app, temp_apk_path)
+  apk_path = os.path.join(get_bin_path(options.app), '%s.apk' % options.app)
+  apk_utils.sign(temp_apk_path, apk_path,  options.keystore)
+  print('Apk available at: %s' % apk_path)
+
+if __name__ == '__main__':
+  sys.exit(Main())