Add new const-method-handle opcode

Change-Id: I6d890810b5d76a1c6292f50717643a8391805d79
diff --git a/src/test/examplesAndroidP/invokecustom/InvokeCustom.java b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
new file mode 100644
index 0000000..282b5ae
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2017, 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 invokecustom;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+
+public class InvokeCustom {
+
+  private static String staticField1 = "StaticField1";
+
+  private static void targetMethodTest1() {
+    System.out.println("Hello World!");
+  }
+
+  private static void targetMethodTest2(MethodHandle mhInvokeStatic, MethodHandle mhGetStatic)
+      throws Throwable {
+    mhInvokeStatic.invokeExact();
+    System.out.println(mhGetStatic.invoke());
+  }
+
+  public static CallSite bsmLookupStatic(MethodHandles.Lookup caller, String name, MethodType type)
+      throws NoSuchMethodException, IllegalAccessException {
+    final MethodHandles.Lookup lookup = MethodHandles.lookup();
+    final MethodHandle targetMH = lookup.findStatic(lookup.lookupClass(), name, type);
+    return new ConstantCallSite(targetMH.asType(type));
+  }
+
+}
diff --git a/src/test/examplesAndroidP/invokecustom/TestGenerator.java b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
new file mode 100644
index 0000000..b9a5126
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2017, 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 invokecustom;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class TestGenerator {
+
+  private final Path classNamePath;
+
+  public static void main(String[] args) throws IOException {
+    assert args.length == 1;
+    TestGenerator testGenerator = new TestGenerator(Paths.get(args[0],
+        TestGenerator.class.getPackage().getName(), InvokeCustom.class.getSimpleName() + ".class"));
+    testGenerator.generateTests();
+  }
+
+  public TestGenerator(Path classNamePath) {
+    this.classNamePath = classNamePath;
+  }
+
+  private void generateTests() throws IOException {
+    ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+    cr.accept(
+        new ClassVisitor(Opcodes.ASM6, cw) {
+          @Override
+          public void visitEnd() {
+            generateMethodTest1(cw);
+            generateMethodMain(cw);
+            super.visitEnd();
+          }
+        }, 0);
+    new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
+  }
+
+  /* Generate main method that only call all test methods. */
+  private void generateMethodMain(ClassVisitor cv) {
+    MethodVisitor mv = cv.visitMethod(
+            Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+    mv.visitMethodInsn(
+        Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test1", "()V", false);
+    mv.visitInsn(Opcodes.RETURN);
+    mv.visitMaxs(-1, -1);
+  }
+
+  /**
+   *  Generate test with an invokedynamic, a static bootstrap method without extra args and
+   *  args to the target method.
+   */
+  private void generateMethodTest1(ClassVisitor cv) {
+    MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test1", "()V",
+        null, null);
+    MethodType mt = MethodType.methodType(
+            CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
+    Handle bootstrap = new Handle( Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
+        "bsmLookupStatic", mt.toMethodDescriptorString(), false);
+    mv.visitLdcInsn(new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
+        "targetMethodTest1", "()V", false));
+    mv.visitLdcInsn(new Handle(Opcodes.H_GETSTATIC, Type.getInternalName(InvokeCustom.class),
+        "staticField1", "Ljava/lang/String;", false));
+    mv.visitInvokeDynamicInsn("targetMethodTest2",
+        "(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V",
+        bootstrap);
+    mv.visitInsn(Opcodes.RETURN);
+    mv.visitMaxs(-1, -1);
+  }
+}
diff --git a/src/test/examplesAndroidP/invokecustom/keep-rules.txt b/src/test/examplesAndroidP/invokecustom/keep-rules.txt
new file mode 100644
index 0000000..dbc21fc
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/keep-rules.txt
@@ -0,0 +1,16 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point and the target methods of invoke-custom because these methods
+# can not be known at compile time. Get rid of everything that is not reachable from there.
+-keep public class invokecustom.InvokeCustom {
+  public static void main(...);
+}
+
+-keepclasseswithmembers class * {
+  *** targetMethodTest*(...);
+}
+
+# allow access modification to enable minification
+-allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..dde8a9c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2017, 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.OutputMode;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.function.UnaryOperator;
+import org.hamcrest.core.CombinableMatcher;
+import org.hamcrest.core.IsInstanceOf;
+import org.hamcrest.core.StringContains;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.internal.matchers.ThrowableMessageMatcher;
+
+public class D8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<D8Command.Builder> {
+
+  class D8TestRunner extends TestRunner<D8TestRunner> {
+
+    D8TestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
+    }
+
+    @Override
+    D8TestRunner withMinApiLevel(int minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    }
+
+    D8TestRunner withClasspath(Path... classpath) {
+      return withBuilderTransformation(b -> {
+        try {
+          return b.addClasspathFiles(classpath);
+        } catch (IOException e) {
+          throw new AssertionError(e);
+        }
+      });
+    }
+
+
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      D8Command.Builder builder = D8Command.builder();
+      for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
+        builder = transformation.apply(builder);
+      }
+      // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+      builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel())));
+      D8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+      try {
+        ToolHelper.runD8(command, this::combinedOptionConsumer);
+      } catch (Unimplemented | CompilationError | InternalCompilerError re) {
+        throw re;
+      } catch (RuntimeException re) {
+        throw re.getCause() == null ? re : re.getCause();
+      }
+    }
+
+    D8TestRunner withIntermediate(boolean intermediate) {
+      return withBuilderTransformation(builder -> builder.setIntermediate(intermediate));
+    }
+
+    @Override
+    D8TestRunner self() {
+      return this;
+    }
+  }
+
+  @Override
+  D8TestRunner test(String testName, String packageName, String mainClass) {
+    return new D8TestRunner(testName, packageName, mainClass);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..0f7386e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2017, 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;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.UnaryOperator;
+import org.junit.Test;
+
+public class R8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<R8Command.Builder> {
+
+  private static Map<DexVm.Version, List<String>> alsoFailsOn =
+      ImmutableMap.of(
+          DexVm.Version.V4_4_4, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.V5_1_1, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.V6_0_1, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.V7_0_0, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.DEFAULT, ImmutableList.of(
+          )
+      );
+
+  @Test
+  public void invokeCustomWithShrinking() throws Throwable {
+    test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
+        .withMinApiLevel(AndroidApiLevel.P.getLevel())
+        .withBuilderTransformation(builder ->
+            builder.addProguardConfigurationFiles(
+                Paths.get(ToolHelper.EXAMPLES_ANDROID_P_DIR, "invokecustom/keep-rules.txt")))
+        .run();
+  }
+
+  class R8TestRunner extends TestRunner<R8TestRunner> {
+
+    R8TestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
+    }
+
+    @Override
+    R8TestRunner withMinApiLevel(int minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    }
+
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      try {
+        R8Command.Builder builder = R8Command.builder();
+        for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+          builder = transformation.apply(builder);
+        }
+        // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+        builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel())));
+        R8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+        ToolHelper.runR8(command, this::combinedOptionConsumer);
+      } catch (ExecutionException e) {
+        throw e.getCause();
+      }
+    }
+
+    @Override
+    R8TestRunner self() {
+      return this;
+    }
+  }
+
+  @Override
+  R8TestRunner test(String testName, String packageName, String mainClass) {
+    return new R8TestRunner(testName, packageName, mainClass);
+  }
+
+  @Override
+  boolean expectedToFail(String name) {
+    return super.expectedToFail(name) || failsOn(alsoFailsOn, name);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..06916a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -0,0 +1,273 @@
+// Copyright (c) 2017, 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
+import com.android.tools.r8.utils.DexInspector.InstructionSubject;
+import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class RunExamplesAndroidPTest
+      <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
+  static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_P_BUILD_DIR;
+
+  abstract class TestRunner<C extends TestRunner<C>> {
+    final String testName;
+    final String packageName;
+    final String mainClass;
+
+    Integer androidJarVersion = null;
+
+    final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
+    final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+    final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+
+    TestRunner(String testName, String packageName, String mainClass) {
+      this.testName = testName;
+      this.packageName = packageName;
+      this.mainClass = mainClass;
+    }
+
+    abstract C self();
+
+    C withDexCheck(Consumer<DexInspector> check) {
+      dexInspectorChecks.add(check);
+      return self();
+    }
+
+    C withClassCheck(Consumer<FoundClassSubject> check) {
+      return withDexCheck(inspector -> inspector.forAllClasses(check));
+    }
+
+    C withMethodCheck(Consumer<FoundMethodSubject> check) {
+      return withClassCheck(clazz -> clazz.forAllMethods(check));
+    }
+
+    <T extends InstructionSubject> C withInstructionCheck(
+        Predicate<InstructionSubject> filter, Consumer<T> check) {
+      return withMethodCheck(method -> {
+        if (method.isAbstract()) {
+          return;
+        }
+        Iterator<T> iterator = method.iterateInstructions(filter);
+        while (iterator.hasNext()) {
+          check.accept(iterator.next());
+        }
+      });
+    }
+
+    C withOptionConsumer(Consumer<InternalOptions> consumer) {
+      optionConsumers.add(consumer);
+      return self();
+    }
+
+    C withMainDexClass(String... classes) {
+      return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
+    }
+
+    C withInterfaceMethodDesugaring(OffOrAuto behavior) {
+      return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
+    }
+
+    C withTryWithResourcesDesugaring(OffOrAuto behavior) {
+      return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
+    }
+
+    C withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+      builderTransformations.add(builderTransformation);
+      return self();
+    }
+
+    void combinedOptionConsumer(InternalOptions options) {
+      for (Consumer<InternalOptions> consumer : optionConsumers) {
+        consumer.accept(options);
+      }
+    }
+
+    Path build() throws Throwable {
+      Path inputFile = getInputJar();
+      Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+      build(inputFile, out);
+      return out;
+    }
+
+    Path getInputJar() {
+      return Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION);
+    }
+
+    void run() throws Throwable {
+      if (minSdkErrorExpected(testName)) {
+        thrown.expect(ApiLevelException.class);
+      }
+
+      String qualifiedMainClass = packageName + "." + mainClass;
+      Path inputFile = getInputJar();
+      Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+      build(inputFile, out);
+
+      if (!ToolHelper.artSupported()) {
+        return;
+      }
+
+      if (!dexInspectorChecks.isEmpty()) {
+        DexInspector inspector = new DexInspector(out);
+        for (Consumer<DexInspector> check : dexInspectorChecks) {
+          check.accept(inspector);
+        }
+      }
+
+      execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out});
+    }
+
+    abstract C withMinApiLevel(int minApiLevel);
+
+    C withAndroidJar(int androidJarVersion) {
+      assert this.androidJarVersion == null;
+      this.androidJarVersion = androidJarVersion;
+      return self();
+    }
+
+    abstract void build(Path inputFile, Path out) throws Throwable;
+  }
+
+  private static List<String> minSdkErrorExpected =
+      ImmutableList.of(
+          "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
+
+  private static Map<DexVm.Version, List<String>> failsOn =
+      ImmutableMap.of(
+          DexVm.Version.V4_4_4, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.V5_1_1, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.V6_0_1, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.V7_0_0, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.DEFAULT, ImmutableList.of()
+      );
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
+    DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
+    return failsOn.containsKey(vmVersion)
+        && failsOn.get(vmVersion).contains(name);
+  }
+
+  boolean expectedToFail(String name) {
+    return failsOn(failsOn, name);
+  }
+
+  boolean minSdkErrorExpected(String testName) {
+    return minSdkErrorExpected.contains(testName);
+  }
+
+  @Test
+  public void invokeCustom() throws Throwable {
+    test("invokecustom", "invokecustom", "InvokeCustom")
+        .withMinApiLevel(AndroidApiLevel.P.getLevel())
+        .run();
+  }
+
+  @Test
+  public void invokeCustomErrorDueToMinSdk() throws Throwable {
+    test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
+        .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .run();
+  }
+
+  abstract RunExamplesAndroidPTest<B>.TestRunner<?> test(String testName, String packageName,
+      String mainClass);
+
+  void execute(
+      String testName,
+      String qualifiedMainClass, Path[] jars, Path[] dexes)
+      throws IOException {
+
+    boolean expectedToFail = expectedToFail(testName);
+    if (expectedToFail) {
+      thrown.expect(Throwable.class);
+    }
+    String output = ToolHelper.runArtNoVerificationErrors(
+        Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
+        qualifiedMainClass,
+        null);
+    if (!expectedToFail) {
+      ToolHelper.ProcessResult javaResult =
+          ToolHelper.runJava(
+              Arrays.stream(jars).map(path -> path.toString()).collect(Collectors.toList()),
+              qualifiedMainClass);
+      assertEquals("JVM run failed", javaResult.exitCode, 0);
+      assertTrue(
+          "JVM output does not match art output.\n\tjvm: "
+              + javaResult.stdout
+              + "\n\tart: "
+              + output.replace("\r", ""),
+          output.equals(javaResult.stdout.replace("\r", "")));
+    }
+  }
+
+  protected DexInspector getMainDexInspector(Path zip)
+      throws ZipException, IOException, ExecutionException {
+    try (ZipFile zipFile = new ZipFile(zip.toFile())) {
+      try (InputStream in =
+          zipFile.getInputStream(zipFile.getEntry(FileUtils.DEFAULT_DEX_FILENAME))) {
+        return new DexInspector(AndroidApp.fromDexProgramData(ByteStreams.toByteArray(in)));
+      }
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 79be8a8..595b753 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -59,9 +59,11 @@
   public static final String BUILD_DIR = "build/";
   public static final String EXAMPLES_DIR = "src/test/examples/";
   public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_DIR = "src/test/examplesAndroidP/";
   public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/";
   public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/";
   public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_BUILD_DIR = BUILD_DIR + "test/examplesAndroidP/";
   public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
 
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;