Fix issue with obsolete methods and const-method-handle

We were incorrectly using the non-obsolete version of the method to
search for the target of const-method-handle instructions. This meant
that obsolete methods would see the wrong method-handle or crash.

Since no java code (currently) compiles to dex code using
'const-method-handle' we needed to use ASM to construct the test
class.

Bug: 73744024
Test: ./test.py --host -j50

Change-Id: I14480170e12eee7f2df5ac084254039a040848c3
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 6f9cad8..d8f858e 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -83,10 +83,14 @@
 #define BRANCH_INSTRUMENTATION(offset)                                                         \
   do {                                                                                         \
     if (UNLIKELY(instrumentation->HasBranchListeners())) {                                     \
-      instrumentation->Branch(self, method, dex_pc, offset);                                   \
+      instrumentation->Branch(self, shadow_frame.GetMethod(), dex_pc, offset);                 \
     }                                                                                          \
     JValue result;                                                                             \
-    if (jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, &result)) {          \
+    if (jit::Jit::MaybeDoOnStackReplacement(self,                                              \
+                                            shadow_frame.GetMethod(),                          \
+                                            dex_pc,                                            \
+                                            offset,                                            \
+                                            &result)) {                                        \
       if (interpret_one_instruction) {                                                         \
         /* OSR has completed execution of the method.  Signal mterp to return to caller */     \
         shadow_frame.SetDexPC(dex::kDexNoIndex);                                               \
@@ -98,7 +102,7 @@
 #define HOTNESS_UPDATE()                                                                       \
   do {                                                                                         \
     if (jit != nullptr) {                                                                      \
-      jit->AddSamples(self, method, 1, /*with_backedges*/ true);                               \
+      jit->AddSamples(self, shadow_frame.GetMethod(), 1, /*with_backedges*/ true);             \
     }                                                                                          \
   } while (false)
 
@@ -200,7 +204,6 @@
 
   uint32_t dex_pc = shadow_frame.GetDexPC();
   const auto* const instrumentation = Runtime::Current()->GetInstrumentation();
-  ArtMethod* method = shadow_frame.GetMethod();
   const uint16_t* const insns = accessor.Insns();
   const Instruction* inst = Instruction::At(insns + dex_pc);
   uint16_t inst_data;
@@ -390,7 +393,7 @@
         const size_t ref_idx = inst->VRegA_11x(inst_data);
         ObjPtr<mirror::Object> obj_result = shadow_frame.GetVRegReference(ref_idx);
         if (do_assignability_check && obj_result != nullptr) {
-          ObjPtr<mirror::Class> return_type = method->ResolveReturnType();
+          ObjPtr<mirror::Class> return_type = shadow_frame.GetMethod()->ResolveReturnType();
           // Re-load since it might have moved.
           obj_result = shadow_frame.GetVRegReference(ref_idx);
           if (return_type == nullptr) {
@@ -535,7 +538,9 @@
       case Instruction::CONST_METHOD_HANDLE: {
         PREAMBLE();
         ClassLinker* cl = Runtime::Current()->GetClassLinker();
-        ObjPtr<mirror::MethodHandle> mh = cl->ResolveMethodHandle(self, inst->VRegB_21c(), method);
+        ObjPtr<mirror::MethodHandle> mh = cl->ResolveMethodHandle(self,
+                                                                  inst->VRegB_21c(),
+                                                                  shadow_frame.GetMethod());
         if (UNLIKELY(mh == nullptr)) {
           HANDLE_PENDING_EXCEPTION();
         } else {
@@ -547,7 +552,9 @@
       case Instruction::CONST_METHOD_TYPE: {
         PREAMBLE();
         ClassLinker* cl = Runtime::Current()->GetClassLinker();
-        ObjPtr<mirror::MethodType> mt = cl->ResolveMethodType(self, inst->VRegB_21c(), method);
+        ObjPtr<mirror::MethodType> mt = cl->ResolveMethodType(self,
+                                                              inst->VRegB_21c(),
+                                                              shadow_frame.GetMethod());
         if (UNLIKELY(mt == nullptr)) {
           HANDLE_PENDING_EXCEPTION();
         } else {
diff --git a/test/1948-obsolete-const-method-handle/build b/test/1948-obsolete-const-method-handle/build
new file mode 100644
index 0000000..ac0dcd9
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/build
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Make us exit on a failure
+set -e
+
+mkdir classes
+./util-src/build-classes $PWD/classes
+
+${DX} --dex --min-sdk-version=28 --output=classes.dex classes
+
+zip $TEST_NAME.jar classes.dex
diff --git a/test/1948-obsolete-const-method-handle/expected.txt b/test/1948-obsolete-const-method-handle/expected.txt
new file mode 100644
index 0000000..a0b80a4
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/expected.txt
@@ -0,0 +1,6 @@
+Do nothing
+Hello
+transforming calling function
+Hello
+Do nothing
+Goodbye
diff --git a/test/1948-obsolete-const-method-handle/info.txt b/test/1948-obsolete-const-method-handle/info.txt
new file mode 100644
index 0000000..ef8eb69
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/info.txt
@@ -0,0 +1,6 @@
+Tests that obsolete methods work correctly in the presence of const-method-type.
+
+const-method-type and the invoke-custom/polymorphic opcodes are new in dex 39.
+It is almost impossible to get them emmited from normal java code so we don't
+have any coverage on them. To get this coverage we use ASM to build a class file
+that contains the required opcodes.
diff --git a/test/1948-obsolete-const-method-handle/run b/test/1948-obsolete-const-method-handle/run
new file mode 100755
index 0000000..9eacb4c
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Squash the exit status and put it in expected
+./default-run --jvmti "$@"
diff --git a/test/1948-obsolete-const-method-handle/util-src/build-classes b/test/1948-obsolete-const-method-handle/util-src/build-classes
new file mode 100755
index 0000000..1b2d79a
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/build-classes
@@ -0,0 +1,45 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+function fail() {
+  echo Build failed: $1 1>&2
+  exit 1
+}
+
+D8=${ANDROID_HOST_OUT}/bin/d8
+if [[ ! -x "${D8}" ]]; then
+  fail "Must have a runnable d8 binary!"
+fi
+
+if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
+  fail "ANDROID_BUILD_TOP is not defined. Try running 'lunch' first."
+fi
+
+SCRIPT_PATH=$( cd $(dirname $0) ; pwd -P )
+ASM_CLASSPATH="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-6.0.jar"
+SRC_PATH="${SCRIPT_PATH}/src"
+BUILD_PATH="${1:-${SCRIPT_PATH}/classes}"
+
+if [[ ! -d "${BUILD_PATH}" ]]; then
+    mkdir "$BUILD_PATH" || exit 1
+fi
+
+# Build the initial class files.
+(cd "${SRC_PATH}" && javac -cp "${ASM_CLASSPATH}" -d "${BUILD_PATH}" Main.java art/*.java art/constmethodhandle/*.java) || fail "javac error"
+# Modify the class files using ASM
+(cd "${SCRIPT_PATH}" && java -cp "${ASM_CLASSPATH}:${BUILD_PATH}" art.constmethodhandle.TestGenerator "${BUILD_PATH}" "$D8") || fail "generator failure"
+# Remove the modification classes. We don't need nor want them for the actual test.
+(cd ${BUILD_PATH} && find . -name "TestGenerator*.class" | xargs rm) || fail "Cleanup failure"
diff --git a/test/1948-obsolete-const-method-handle/util-src/info.txt b/test/1948-obsolete-const-method-handle/util-src/info.txt
new file mode 100644
index 0000000..330659a
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/info.txt
@@ -0,0 +1,7 @@
+This builds the class files for test 1948. It does this by first compiling the
+contents of src/. Then it uses TestGenerator to add additional functions to the
+TestInvoker and Test1948 classes. These additions are a new method that loads a
+constant method-handle and invokes it in TestInvoker and functions that will
+return the dex & class bytes for the redefinition that changes the target of the
+methodHandle TestInvoker loads.
+
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/Main.java b/test/1948-obsolete-const-method-handle/util-src/src/Main.java
new file mode 100644
index 0000000..9a1af2b
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Throwable {
+    art.Test1948.run();
+  }
+}
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/art/Redefinition.java b/test/1948-obsolete-const-method-handle/util-src/src/art/Redefinition.java
new file mode 100644
index 0000000..1eec70b
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/art/Test1948.java b/test/1948-obsolete-const-method-handle/util-src/src/art/Test1948.java
new file mode 100644
index 0000000..f5414fb
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/src/art/Test1948.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+import art.constmethodhandle.TestInvoke;
+import java.util.*;
+import java.lang.reflect.*;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+public class Test1948 {
+  // These are initialized by a method added by test_generator.
+  // They will contain the dex bytes of TestInvoker but with the method handle changed to pointing
+  // to sayBye.
+  public static final byte[] CLASS_BYTES;
+  public static final byte[] DEX_BYTES;
+  static {
+    try {
+      // TestGenerator will add functions that get the base64 string of these functions. When we
+      // compile this the functions haven't been generated yet though so just do things this way.
+      MethodHandle getClassBase64 = MethodHandles.lookup().findStatic(
+          Test1948.class, "getClassBase64", MethodType.methodType(String.class));
+      MethodHandle getDexBase64 = MethodHandles.lookup().findStatic(
+          Test1948.class, "getDexBase64", MethodType.methodType(String.class));
+      CLASS_BYTES = Base64.getDecoder().decode((String)getClassBase64.invokeExact());
+      DEX_BYTES = Base64.getDecoder().decode((String)getDexBase64.invokeExact());
+    } catch (Throwable e) {
+      throw new Error("Failed to initialize statics: ", e);
+    }
+  }
+
+  public static void run() throws Throwable {
+    // NB Because we aren't using desugar we cannot use capturing-lambda or string concat anywhere
+    // in this test! Version 9+ javac turns these into invokedynamics using bootstrap methods not
+    // currently present in android.
+    new TestInvoke().runTest(
+        new Runnable() { public void run() { System.out.println("Do nothing"); } });
+    new TestInvoke().runTest(
+        new Runnable() {
+          public void run() {
+            System.out.println("transforming calling function");
+            Redefinition.doCommonClassRedefinition(TestInvoke.class, CLASS_BYTES, DEX_BYTES);
+          }
+        });
+    new TestInvoke().runTest(
+        new Runnable() { public void run() { System.out.println("Do nothing"); } });
+  }
+}
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/BaseTestInvoke.java b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/BaseTestInvoke.java
new file mode 100644
index 0000000..c38f387
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/BaseTestInvoke.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art.constmethodhandle;
+
+public class BaseTestInvoke {
+  // Simply used to make sure that everything links right when building.
+  public void runTest(Runnable preCall) {
+    throw new Error("Should not be called!");
+  }
+}
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/Responses.java b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/Responses.java
new file mode 100644
index 0000000..2495647
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/Responses.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art.constmethodhandle;
+
+// We call one of these using a constant method-handle. Which one is called is changed by the
+// redefinition.
+public class Responses {
+  public static void sayHi() {
+    System.out.println("Hello");
+  }
+
+  public static void sayBye() {
+    System.out.println("Goodbye");
+  }
+}
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestGenerator.java b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestGenerator.java
new file mode 100644
index 0000000..40b5cf9
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestGenerator.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art.constmethodhandle;
+
+import java.io.*;
+import java.util.*;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.nio.file.*;
+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;
+
+// This test will modify in place the compiled java files to fill in the transformed version and
+// fill in the TestInvoker.runTest function with a load-constant of a method-handle. It will use d8
+// (passed in as an argument) to create the dex we will transform TestInvoke into.
+public class TestGenerator {
+
+  public static void main(String[] args) throws IOException {
+    if (args.length != 2) {
+      throw new Error("Unable to convert class to dex without d8 binary!");
+    }
+    Path base = Paths.get(args[0]);
+    String d8Bin = args[1];
+
+    Path initTestInvoke = base.resolve(TestGenerator.class.getPackage().getName().replace('.', '/'))
+                              .resolve(TestInvoke.class.getSimpleName() + ".class");
+    byte[] initClass = new FileInputStream(initTestInvoke.toFile()).readAllBytes();
+
+    // Make the initial version of TestInvoker
+    generateInvoker(initClass, "sayHi", new FileOutputStream(initTestInvoke.toFile()));
+
+    // Make the final 'class' version of testInvoker
+    ByteArrayOutputStream finalClass = new ByteArrayOutputStream();
+    generateInvoker(initClass, "sayBye", finalClass);
+
+    Path initTest1948 = base.resolve("art").resolve(art.Test1948.class.getSimpleName() + ".class");
+    byte[] finalClassBytes = finalClass.toByteArray();
+    byte[] finalDexBytes = getFinalDexBytes(d8Bin, finalClassBytes);
+    generateTestCode(
+        new FileInputStream(initTest1948.toFile()).readAllBytes(),
+        finalClassBytes,
+        finalDexBytes,
+        new FileOutputStream(initTest1948.toFile()));
+  }
+
+  // Modify the Test1948 class bytecode so it has the transformed version of TestInvoker as a string
+  // constant.
+  private static void generateTestCode(
+      byte[] initClass, byte[] transClass, byte[] transDex, OutputStream out) throws IOException {
+    ClassReader cr = new ClassReader(initClass);
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+    cr.accept(
+        new ClassVisitor(Opcodes.ASM6, cw) {
+          @Override
+          public void visitEnd() {
+            generateStringAccessorMethod(
+                cw, "getDexBase64", Base64.getEncoder().encodeToString(transDex));
+            generateStringAccessorMethod(
+                cw, "getClassBase64", Base64.getEncoder().encodeToString(transClass));
+            super.visitEnd();
+          }
+        }, 0);
+    out.write(cw.toByteArray());
+  }
+
+  // Insert a string accessor method so we can get the transformed versions of TestInvoker.
+  private static void generateStringAccessorMethod(ClassVisitor cv, String name, String ret) {
+    MethodVisitor mv = cv.visitMethod(
+        Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
+        name, "()Ljava/lang/String;", null, null);
+    mv.visitLdcInsn(ret);
+    mv.visitInsn(Opcodes.ARETURN);
+    mv.visitMaxs(-1, -1);
+  }
+
+  // Use d8bin to convert the classBytes into a dex file bytes. We need to do this here because we
+  // need the dex-file bytes to be used by the test class to redefine TestInvoker. We use d8 because
+  // it doesn't require setting up a directory structures or matching file names like dx does.
+  // TODO We should maybe just call d8 functions directly?
+  private static byte[] getFinalDexBytes(String d8Bin, byte[] classBytes) throws IOException {
+    Path tempDir = Files.createTempDirectory("FinalTestInvoker_Gen");
+    File tempInput = Files.createTempFile(tempDir, "temp_input_class", ".class").toFile();
+
+    OutputStream tempClassStream = new FileOutputStream(tempInput);
+    tempClassStream.write(classBytes);
+    tempClassStream.close();
+    tempClassStream = null;
+
+    Process d8Proc = new ProcessBuilder(d8Bin,
+                                        // Put classes.dex in the temp-dir we made.
+                                        "--output", tempDir.toAbsolutePath().toString(),
+                                        "--min-api", "28",  // Allow the new invoke ops.
+                                        "--no-desugaring",  // Don't try to be clever please.
+                                        tempInput.toPath().toAbsolutePath().toString())
+        .inheritIO()  // Just print to stdio.
+        .start();
+    int res;
+    try {
+      res = d8Proc.waitFor();
+    } catch (Exception e) {
+      System.out.println("Failed to dex: ".concat(e.toString()));
+      e.printStackTrace();
+      res = -123;
+    }
+    tempInput.delete();
+    try {
+      if (res == 0) {
+        byte[] out = new FileInputStream(tempDir.resolve("classes.dex").toFile()).readAllBytes();
+        tempDir.resolve("classes.dex").toFile().delete();
+        return out;
+      }
+    } finally {
+      tempDir.toFile().delete();
+    }
+    throw new Error("Failed to get dex file! " + res);
+  }
+
+  private static void generateInvoker(
+      byte[] inputClass,
+      String toCall,
+      OutputStream output) throws IOException {
+    ClassReader cr = new ClassReader(inputClass);
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+    cr.accept(
+        new ClassVisitor(Opcodes.ASM6, cw) {
+          @Override
+          public void visitEnd() {
+            generateRunTest(cw, toCall);
+            super.visitEnd();
+          }
+        }, 0);
+    output.write(cw.toByteArray());
+  }
+
+  // Creates the following method:
+  //   public runTest(Runnable preCall) {
+  //     preCall.run();
+  //     MethodHandle mh = <CONSTANT MH>;
+  //     mh.invokeExact();
+  //   }
+  private static void generateRunTest(ClassVisitor cv, String toCall) {
+    MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC,
+                                      "runTest", "(Ljava/lang/Runnable;)V", null, null);
+    MethodType mt = MethodType.methodType(Void.TYPE);
+    Handle mh = new Handle(
+        Opcodes.H_INVOKESTATIC,
+        Type.getInternalName(Responses.class),
+        toCall,
+        mt.toMethodDescriptorString(),
+        false);
+    String internalName = Type.getInternalName(Runnable.class);
+    mv.visitVarInsn(Opcodes.ALOAD, 1);
+    mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, internalName, "run", "()V", true);
+    mv.visitLdcInsn(mh);
+    mv.visitMethodInsn(
+        Opcodes.INVOKEVIRTUAL,
+        Type.getInternalName(MethodHandle.class),
+        "invokeExact",
+        "()V",
+        false);
+    mv.visitInsn(Opcodes.RETURN);
+    mv.visitMaxs(-1, -1);
+  }
+}
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestInvoke.java b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestInvoke.java
new file mode 100644
index 0000000..eaa856e
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestInvoke.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art.constmethodhandle;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+
+public class TestInvoke extends BaseTestInvoke {
+  // THIS IS GENERATED BY ASM
+  // @Override
+  // public void runTest(Runnable preCall) {
+  //   preCall.run();
+  //   // MAGIC! Replaced with a static method handle pointing at
+  //   // art.constmethodhandle.Responses.sayHi // (or art.constmethodhandle.Responses.sayBye in the
+  //   // redefined version).
+  //   MethodHandle handle = CONSTANT_MH<Responses.sayHi>;
+  //   handle.invokeExact();
+  // }
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 464449c..cf781d7 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -20,6 +20,7 @@
 # Dependencies for actually running a run-test.
 TEST_ART_RUN_TEST_DEPENDENCIES := \
   $(HOST_OUT_EXECUTABLES)/dx \
+  $(HOST_OUT_EXECUTABLES)/d8 \
   $(HOST_OUT_EXECUTABLES)/hiddenapi \
   $(HOST_OUT_EXECUTABLES)/jasmin \
   $(HOST_OUT_EXECUTABLES)/smali \