Merge "Revert "Revert "Full-stack integrity: check vdex contents."""
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 388ff06..6ad4eec 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -184,6 +184,7 @@
 ART_GTEST_heap_verification_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY
 ART_GTEST_verifier_deps_test_DEX_DEPS := VerifierDeps VerifierDepsMulti MultiDex
 ART_GTEST_dex_to_dex_decompiler_test_DEX_DEPS := VerifierDeps DexToDexDecompiler
+ART_GTEST_oatdump_app_test_DEX_DEPS := ProfileTestMultiDex
 
 # The elf writer test has dependencies on core.oat.
 ART_GTEST_elf_writer_test_HOST_DEPS := $(HOST_CORE_IMAGE_DEFAULT_64) $(HOST_CORE_IMAGE_DEFAULT_32)
@@ -303,6 +304,11 @@
   oatdumpd-target
 ART_GTEST_oatdump_image_test_HOST_DEPS := $(ART_GTEST_oatdump_test_HOST_DEPS)
 ART_GTEST_oatdump_image_test_TARGET_DEPS := $(ART_GTEST_oatdump_test_TARGET_DEPS)
+ART_GTEST_oatdump_app_test_HOST_DEPS := $(ART_GTEST_oatdump_test_HOST_DEPS) \
+  dex2oatd-host \
+  dex2oatds-host
+ART_GTEST_oatdump_app_test_TARGET_DEPS := $(ART_GTEST_oatdump_test_TARGET_DEPS) \
+  dex2oatd-target
 
 ART_GTEST_patchoat_test_HOST_DEPS := \
   $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS)
@@ -507,13 +513,19 @@
     $$(gtest_exe) \
     $$(ART_GTEST_$(1)_HOST_DEPS) \
     $(foreach file,$(ART_GTEST_$(1)_DEX_DEPS),$(ART_TEST_HOST_GTEST_$(file)_DEX))
+  ifneq (,$(DIST_DIR))
+    gtest_xml_output := --gtest_output=xml:$(DIST_DIR)/gtest/$(1)$$($(3)ART_PHONY_TEST_HOST_SUFFIX).xml
+  else
+    gtest_xml_output :=
+  endif
 
   ART_TEST_HOST_GTEST_DEPENDENCIES += $$(gtest_deps)
 
 .PHONY: $$(gtest_rule)
 ifeq (,$(SANITIZE_HOST))
+$$(gtest_rule): PRIVATE_XML_OUTPUT := $$(gtest_xml_output)
 $$(gtest_rule): $$(gtest_exe) $$(gtest_deps)
-	$(hide) ($$(call ART_TEST_SKIP,$$@) && $$< && \
+	$(hide) ($$(call ART_TEST_SKIP,$$@) && $$< $$(PRIVATE_XML_OUTPUT) && \
 		$$(call ART_TEST_PASSED,$$@)) || $$(call ART_TEST_FAILED,$$@)
 else
 # Note: envsetup currently exports ASAN_OPTIONS=detect_leaks=0 to suppress leak detection, as some
@@ -523,9 +535,10 @@
 # (with the x86-64 ABI, as this allows symbolization of both x86 and x86-64). We don't do this in
 # general as it loses all the color output, and we have our own symbolization step when not running
 # under ASAN.
+$$(gtest_rule): PRIVATE_XML_OUTPUT := $$(gtest_xml_output)
 $$(gtest_rule): $$(gtest_exe) $$(gtest_deps)
 	$(hide) ($$(call ART_TEST_SKIP,$$@) && set -o pipefail && \
-		ASAN_OPTIONS=detect_leaks=1 $$< 2>&1 | tee $$<.tmp.out >&2 && \
+		ASAN_OPTIONS=detect_leaks=1 $$< $$(PRIVATE_XML_OUTPUT) 2>&1 | tee $$<.tmp.out >&2 && \
 		{ $$(call ART_TEST_PASSED,$$@) ; rm $$<.tmp.out ; }) || \
 		( grep -q AddressSanitizer $$<.tmp.out && export ANDROID_BUILD_TOP=`pwd` && \
 			{ echo "ABI: 'x86_64'" | cat - $$<.tmp.out | development/scripts/stack | tail -n 3000 ; } ; \
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 8a604db..bd3a145 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -936,13 +936,13 @@
     // instances of this thread's stack.
     LOG(FATAL_WITHOUT_ABORT) << "Had a hard failure verifying all classes, and was asked to abort "
                              << "in such situations. Please check the log.";
-    abort();
+    _exit(1);
   } else if (number_of_soft_verifier_failures_ > 0 &&
              GetCompilerOptions().AbortOnSoftVerifierFailure()) {
     LOG(FATAL_WITHOUT_ABORT) << "Had " << number_of_soft_verifier_failures_ << " soft failure(s) "
                              << "verifying all classes, and was asked to abort in such situations. "
                              << "Please check the log.";
-    abort();
+    _exit(1);
   }
 
   if (compiler_options_->IsAnyCompilationEnabled()) {
diff --git a/oatdump/Android.bp b/oatdump/Android.bp
index 012100d..71e276d 100644
--- a/oatdump/Android.bp
+++ b/oatdump/Android.bp
@@ -121,6 +121,7 @@
         "art_gtest_defaults",
     ],
     srcs: [
+        "oatdump_app_test.cc",
         "oatdump_test.cc",
         "oatdump_image_test.cc",
     ],
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index f91f6e3..41133a8 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -1676,10 +1676,10 @@
     if ((method_access_flags & kAccNative) == 0) {
       ScopedObjectAccess soa(Thread::Current());
       Runtime* const runtime = Runtime::Current();
-      Handle<mirror::DexCache> dex_cache(
-          hs->NewHandle(runtime->GetClassLinker()->RegisterDexFile(*dex_file, nullptr)));
-      CHECK(dex_cache != nullptr);
       DCHECK(options_.class_loader_ != nullptr);
+      Handle<mirror::DexCache> dex_cache = hs->NewHandle(
+          runtime->GetClassLinker()->RegisterDexFile(*dex_file, options_.class_loader_->Get()));
+      CHECK(dex_cache != nullptr);
       return verifier::MethodVerifier::VerifyMethodAndDump(
           soa.Self(), vios, dex_method_idx, dex_file, dex_cache, *options_.class_loader_,
           class_def, code_item, nullptr, method_access_flags);
@@ -2998,7 +2998,7 @@
   // Need well-known-classes.
   WellKnownClasses::Init(self->GetJniEnv());
 
-  // Need to register dex files to get a working dex cache.
+  // Open dex files.
   OatFile* oat_file_ptr = oat_file.get();
   ClassLinker* class_linker = runtime->GetClassLinker();
   runtime->GetOatFileManager().RegisterOatFile(std::move(oat_file));
@@ -3006,9 +3006,6 @@
     std::string error_msg;
     const DexFile* const dex_file = OpenDexFile(odf, &error_msg);
     CHECK(dex_file != nullptr) << error_msg;
-    ObjPtr<mirror::DexCache> dex_cache =
-        class_linker->RegisterDexFile(*dex_file, nullptr);
-    CHECK(dex_cache != nullptr);
     class_path->push_back(dex_file);
   }
 
@@ -3019,6 +3016,13 @@
 
   jobject class_loader = class_linker->CreatePathClassLoader(self, *class_path);
 
+  // Need to register dex files to get a working dex cache.
+  for (const DexFile* dex_file : *class_path) {
+    ObjPtr<mirror::DexCache> dex_cache = class_linker->RegisterDexFile(
+        *dex_file, self->DecodeJObject(class_loader)->AsClassLoader());
+    CHECK(dex_cache != nullptr);
+  }
+
   return class_loader;
 }
 
diff --git a/oatdump/oatdump_app_test.cc b/oatdump/oatdump_app_test.cc
new file mode 100644
index 0000000..f125222
--- /dev/null
+++ b/oatdump/oatdump_app_test.cc
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "oatdump_test.h"
+
+namespace art {
+
+TEST_F(OatDumpTest, TestAppWithBootImage) {
+  std::string error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kDynamic, {}, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+}
+TEST_F(OatDumpTest, TestAppWithBootImageStatic) {
+  TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
+  std::string error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kStatic, {}, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+}
+
+TEST_F(OatDumpTest, TestPicAppWithBootImage) {
+  std::string error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kDynamic, {"--compile-pic"}, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+}
+TEST_F(OatDumpTest, TestPicAppWithBootImageStatic) {
+  TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
+  std::string error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kStatic, {"--compile-pic"}, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+}
+
+}  // namespace art
diff --git a/oatdump/oatdump_image_test.cc b/oatdump/oatdump_image_test.cc
index e9cc922..d054ece 100644
--- a/oatdump/oatdump_image_test.cc
+++ b/oatdump/oatdump_image_test.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 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.
diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h
index d0f05d9..d4bed6b 100644
--- a/oatdump/oatdump_test.h
+++ b/oatdump/oatdump_test.h
@@ -66,14 +66,15 @@
 
   // Linking flavor.
   enum Flavor {
-    kDynamic,  // oatdump(d)
-    kStatic,   // oatdump(d)s
+    kDynamic,  // oatdump(d), dex2oat(d)
+    kStatic,   // oatdump(d)s, dex2oat(d)s
   };
 
-  // Returns path to the oatdump binary.
-  std::string GetOatDumpFilePath(Flavor flavor) {
+  // Returns path to the oatdump/dex2oat binary.
+  std::string GetExecutableFilePath(Flavor flavor, const char* name) {
     std::string root = GetTestAndroidRoot();
-    root += "/bin/oatdump";
+    root += "/bin/";
+    root += name;
     if (kIsDebugBuild) {
       root += "d";
     }
@@ -85,6 +86,7 @@
 
   enum Mode {
     kModeOat,
+    kModeOatWithBootImage,
     kModeArt,
     kModeSymbolize,
   };
@@ -95,13 +97,56 @@
     kListAndCode
   };
 
+  std::string GetAppBaseName() {
+    // Use ProfileTestMultiDex as it contains references to boot image strings
+    // that shall use different code for PIC and non-PIC.
+    return "ProfileTestMultiDex";
+  }
+
+  std::string GetAppOdexName() {
+    return tmp_dir_ + "/" + GetAppBaseName() + ".odex";
+  }
+
+  bool GenerateAppOdexFile(Flavor flavor,
+                           const std::vector<std::string>& args,
+                           /*out*/ std::string* error_msg) {
+    std::string dex2oat_path = GetExecutableFilePath(flavor, "dex2oat");
+    std::vector<std::string> exec_argv = {
+        dex2oat_path,
+        "--runtime-arg",
+        "-Xms64m",
+        "--runtime-arg",
+        "-Xmx512m",
+        "--runtime-arg",
+        "-Xnorelocate",
+        "--boot-image=" + GetCoreArtLocation(),
+        "--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA)),
+        "--dex-file=" + GetTestDexFileName(GetAppBaseName().c_str()),
+        "--oat-file=" + GetAppOdexName(),
+        "--compiler-filter=speed"
+    };
+    exec_argv.insert(exec_argv.end(), args.begin(), args.end());
+
+    pid_t pid;
+    int pipe_fd;
+    bool result = ForkAndExec(exec_argv, &pid, &pipe_fd, error_msg);
+    if (result) {
+      close(pipe_fd);
+      int status = 0;
+      if (waitpid(pid, &status, 0) != -1) {
+        result = (status == 0);
+      }
+    }
+    return result;
+  }
+
   // Run the test with custom arguments.
   bool Exec(Flavor flavor,
             Mode mode,
             const std::vector<std::string>& args,
             Display display,
-            std::string* error_msg) {
-    std::string file_path = GetOatDumpFilePath(flavor);
+            /*out*/ std::string* error_msg) {
+    std::string file_path = GetExecutableFilePath(flavor, "oatdump");
 
     EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
 
@@ -133,6 +178,11 @@
         expected_prefixes.push_back("IMAGE LOCATION:");
         expected_prefixes.push_back("IMAGE BEGIN:");
         expected_prefixes.push_back("kDexCaches:");
+      } else if (mode == kModeOatWithBootImage) {
+        exec_argv.push_back("--boot-image=" + GetCoreArtLocation());
+        exec_argv.push_back("--instruction-set=" + std::string(
+            GetInstructionSetString(kRuntimeISA)));
+        exec_argv.push_back("--oat-file=" + GetAppOdexName());
       } else {
         CHECK_EQ(static_cast<size_t>(mode), static_cast<size_t>(kModeOat));
         exec_argv.push_back("--oat-file=" + core_oat_location_);
@@ -140,39 +190,10 @@
     }
     exec_argv.insert(exec_argv.end(), args.begin(), args.end());
 
-    bool result = true;
-    // We must set --android-root.
-    int link[2];
-    if (pipe(link) == -1) {
-      *error_msg = strerror(errno);
-      return false;
-    }
-
-    const pid_t pid = fork();
-    if (pid == -1) {
-      *error_msg = strerror(errno);
-      return false;
-    }
-
-    if (pid == 0) {
-      dup2(link[1], STDOUT_FILENO);
-      close(link[0]);
-      close(link[1]);
-      // change process groups, so we don't get reaped by ProcessManager
-      setpgid(0, 0);
-      // Use execv here rather than art::Exec to avoid blocking on waitpid here.
-      std::vector<char*> argv;
-      for (size_t i = 0; i < exec_argv.size(); ++i) {
-        argv.push_back(const_cast<char*>(exec_argv[i].c_str()));
-      }
-      argv.push_back(nullptr);
-      UNUSED(execv(argv[0], &argv[0]));
-      const std::string command_line(android::base::Join(exec_argv, ' '));
-      PLOG(ERROR) << "Failed to execv(" << command_line << ")";
-      // _exit to avoid atexit handlers in child.
-      _exit(1);
-    } else {
-      close(link[1]);
+    pid_t pid;
+    int pipe_fd;
+    bool result = ForkAndExec(exec_argv, &pid, &pipe_fd, error_msg);
+    if (result) {
       static const size_t kLineMax = 256;
       char line[kLineMax] = {};
       size_t line_len = 0;
@@ -188,7 +209,7 @@
             memmove(&line[0], &line[spaces], line_len);
           }
           ssize_t bytes_read =
-              TEMP_FAILURE_RETRY(read(link[0], &line[line_len], kLineMax - line_len));
+              TEMP_FAILURE_RETRY(read(pipe_fd, &line[line_len], kLineMax - line_len));
           if (bytes_read <= 0) {
             break;
           }
@@ -219,7 +240,7 @@
         EXPECT_GT(total, 0u);
       }
       LOG(INFO) << "Processed bytes " << total;
-      close(link[0]);
+      close(pipe_fd);
       int status = 0;
       if (waitpid(pid, &status, 0) != -1) {
         result = (status == 0);
@@ -236,6 +257,49 @@
     return result;
   }
 
+  bool ForkAndExec(const std::vector<std::string>& exec_argv,
+                   /*out*/ pid_t* pid,
+                   /*out*/ int* pipe_fd,
+                   /*out*/ std::string* error_msg) {
+    int link[2];
+    if (pipe(link) == -1) {
+      *error_msg = strerror(errno);
+      return false;
+    }
+
+    *pid = fork();
+    if (*pid == -1) {
+      *error_msg = strerror(errno);
+      close(link[0]);
+      close(link[1]);
+      return false;
+    }
+
+    if (*pid == 0) {
+      dup2(link[1], STDOUT_FILENO);
+      close(link[0]);
+      close(link[1]);
+      // change process groups, so we don't get reaped by ProcessManager
+      setpgid(0, 0);
+      // Use execv here rather than art::Exec to avoid blocking on waitpid here.
+      std::vector<char*> argv;
+      for (size_t i = 0; i < exec_argv.size(); ++i) {
+        argv.push_back(const_cast<char*>(exec_argv[i].c_str()));
+      }
+      argv.push_back(nullptr);
+      UNUSED(execv(argv[0], &argv[0]));
+      const std::string command_line(android::base::Join(exec_argv, ' '));
+      PLOG(ERROR) << "Failed to execv(" << command_line << ")";
+      // _exit to avoid atexit handlers in child.
+      _exit(1);
+      UNREACHABLE();
+    } else {
+      close(link[1]);
+      *pipe_fd = link[0];
+      return true;
+    }
+  }
+
   std::string tmp_dir_;
 
  private:
diff --git a/test/952-invoke-custom-kinds/build b/test/952-invoke-custom-kinds/build
deleted file mode 100644
index a02cdc3..0000000
--- a/test/952-invoke-custom-kinds/build
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2017 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.
-
-# Stop if something fails.
-set -e
-
-${DX} --dex --min-sdk-version=26 --output=classes.dex classes
-
-zip $TEST_NAME.jar classes.dex
diff --git a/test/952-invoke-custom-kinds/classes/Main.class b/test/952-invoke-custom-kinds/classes/Main.class
deleted file mode 100644
index 6bc04e3..0000000
--- a/test/952-invoke-custom-kinds/classes/Main.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class b/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class
deleted file mode 100644
index 5dfe958..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/Interface.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class b/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class
deleted file mode 100644
index a11ee69..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/InterfaceImplementor.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class
deleted file mode 100644
index e233feb..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$Interface.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class
deleted file mode 100644
index 41e1d43..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom$InterfaceImplementor.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class b/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class
deleted file mode 100644
index b8dcd55..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/InvokeCustom.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/Super.class b/test/952-invoke-custom-kinds/classes/invokecustom/Super.class
deleted file mode 100644
index 7906f99..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/Super.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class
deleted file mode 100644
index c3266e4..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator$1.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class b/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class
deleted file mode 100644
index 03dc233..0000000
--- a/test/952-invoke-custom-kinds/classes/invokecustom/TestGenerator.class
+++ /dev/null
Binary files differ
diff --git a/test/952-invoke-custom-kinds/expected.txt b/test/952-invoke-custom-kinds/expected.txt
deleted file mode 100644
index c41b5c6..0000000
--- a/test/952-invoke-custom-kinds/expected.txt
+++ /dev/null
@@ -1,40 +0,0 @@
-bsmLookupStatic []
-Hello World!
-bsmLookupStatic []
-true
-127
-c
-1024
-123456
-1.2
-123456789
-3.5123456789
-String
-bsmLookupStaticWithExtraArgs [1, 123456789, 123.456, 123456.789123]
-targetMethodTest3 from InvokeCustom
-bsmCreateCallSite [MethodHandle(InvokeCustom)void]
-targetMethodTest4 from Super
-bsmLookupStatic []
-targetMethodTest5 1000 + -923 = 77
-targetMethodTest5 returned: 77
-bsmLookupStatic []
-targetMethodTest6 8209686820727 + -1172812402961 = 7036874417766
-targetMethodTest6 returned: 7036874417766
-bsmLookupStatic []
-targetMethodTest7 0.50097656 * -0.50097656 = -0.2509775161743164
-targetMethodTest6 returned: -0.2509775161743164
-bsmLookupStatic []
-targetMethodTest8 First invokedynamic invocation
-bsmLookupStatic []
-targetMethodTest8 Second invokedynamic invocation
-bsmLookupStatic []
-targetMethodTest8 Dupe first invokedynamic invocation
-bsmLookupTest9 [MethodHandle()int, MethodHandle(int)void, MethodHandle(InvokeCustom)float, MethodHandle(InvokeCustom,float)void]
-targetMethodTest9 ()void
-checkStaticFieldTest9: old 0 new 1985229328 expected 1985229328 OK
-checkFieldTest9: old 0.0 new 1.99E-19 expected 1.99E-19 OK
-helperMethodTest9 in class invokecustom.InvokeCustom
-InvokeCustom.<init>(3)
-run() for Test9
-InvokeCustom.privateMethodTest9()
-targetMethodTest9()
diff --git a/test/952-invoke-custom-kinds/info.txt b/test/952-invoke-custom-kinds/info.txt
deleted file mode 100644
index 33b4cff..0000000
--- a/test/952-invoke-custom-kinds/info.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This test checks call sites and constant method handles in DEX files used
-by invoke-custom.
-
-The class files come from dalvik/dx/tests/135-invoke-custom.
diff --git a/test/952-invoke-custom/expected.txt b/test/952-invoke-custom/expected.txt
index be01c45..767cc7e 100644
--- a/test/952-invoke-custom/expected.txt
+++ b/test/952-invoke-custom/expected.txt
@@ -13,3 +13,8 @@
 9000
 TestLinkerUnrelatedBSM
 Winners 1 Votes 16
+TestInvocationKinds
+testStaticFieldAccessors
+testInstanceFieldAccessors
+testInvokeVirtual => max(77, -3) = 77
+testConstructor => class TestInvocationKinds$Widget
diff --git a/test/952-invoke-custom/src/Main.java b/test/952-invoke-custom/src/Main.java
index 2e1db82..0b1c1ff 100644
--- a/test/952-invoke-custom/src/Main.java
+++ b/test/952-invoke-custom/src/Main.java
@@ -86,5 +86,6 @@
         TestLinkerMethodMultipleArgumentTypes();
         TestLinkerUnrelatedBSM.test();
         TestInvokeCustomWithConcurrentThreads();
+        TestInvocationKinds.test();
     }
 }
diff --git a/test/952-invoke-custom/src/TestInvocationKinds.java b/test/952-invoke-custom/src/TestInvocationKinds.java
new file mode 100644
index 0000000..7b88c18
--- /dev/null
+++ b/test/952-invoke-custom/src/TestInvocationKinds.java
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+import annotations.BootstrapMethod;
+import annotations.CalledByIndy;
+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;
+
+class TestInvocationKinds extends TestBase {
+    private static int static_field;
+    private double instance_field;
+
+    static CallSite lookupStaticFieldGetter(
+            MethodHandles.Lookup lookup, String name, MethodType methodType) throws Throwable {
+        // methodType = "()LfieldType;"
+        MethodHandle mh =
+                lookup.findStaticGetter(TestInvocationKinds.class, name, methodType.returnType());
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestInvocationKinds.class,
+                    name = "lookupStaticFieldSetter"
+                ),
+        fieldOrMethodName = "static_field",
+        returnType = void.class,
+        parameterTypes = {int.class}
+    )
+    private static void setStaticField(int value) {
+        assertNotReached();
+    }
+
+    static CallSite lookupStaticFieldSetter(
+            MethodHandles.Lookup lookup, String name, MethodType methodType) throws Throwable {
+        // methodType = "(LfieldType;)V"
+        MethodHandle mh =
+                lookup.findStaticSetter(
+                        TestInvocationKinds.class, name, methodType.parameterType(0));
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestInvocationKinds.class,
+                    name = "lookupStaticFieldGetter"
+                ),
+        fieldOrMethodName = "static_field",
+        returnType = int.class,
+        parameterTypes = {}
+    )
+    private static int getStaticField() {
+        assertNotReached();
+        return 0;
+    }
+
+    static CallSite lookupInstanceFieldSetter(
+            MethodHandles.Lookup lookup, String name, MethodType methodType) throws Throwable {
+        // methodType = "(Lreceiver;LfieldType;)V"
+        MethodHandle mh =
+                lookup.findSetter(methodType.parameterType(0), name, methodType.parameterType(1));
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestInvocationKinds.class,
+                    name = "lookupInstanceFieldSetter"
+                ),
+        fieldOrMethodName = "instance_field",
+        returnType = void.class,
+        parameterTypes = {TestInvocationKinds.class, double.class}
+    )
+    private static void setInstanceField(TestInvocationKinds instance, double value) {
+        assertNotReached();
+        instance.instance_field = Double.NaN;
+    }
+
+    static CallSite lookupInstanceFieldGetter(
+            MethodHandles.Lookup lookup, String name, MethodType methodType) throws Throwable {
+        // methodType = "(Lreceiver;)LfieldType;"
+        MethodHandle mh =
+                lookup.findGetter(methodType.parameterType(0), name, methodType.returnType());
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestInvocationKinds.class,
+                    name = "lookupInstanceFieldGetter"
+                ),
+        fieldOrMethodName = "instance_field",
+        returnType = double.class,
+        parameterTypes = {TestInvocationKinds.class}
+    )
+    private static double getInstanceField(TestInvocationKinds instance) {
+        assertNotReached();
+        return Double.NaN;
+    }
+
+    private static void testStaticFieldAccessors() {
+        System.out.println("testStaticFieldAccessors");
+        setStaticField(3);
+        assertEquals(static_field, 3);
+        setStaticField(4);
+        assertEquals(static_field, 4);
+        assertEquals(static_field, getStaticField());
+        static_field = Integer.MAX_VALUE;
+        assertEquals(Integer.MAX_VALUE, getStaticField());
+    }
+
+    private static void testInstanceFieldAccessors() {
+        System.out.println("testInstanceFieldAccessors");
+        TestInvocationKinds instance = new TestInvocationKinds();
+        instance.instance_field = Double.MIN_VALUE;
+        setInstanceField(instance, Math.PI);
+        assertEquals(Math.PI, instance.instance_field);
+        instance.instance_field = Math.E;
+        assertEquals(Math.E, getInstanceField(instance));
+    }
+
+    private static CallSite lookupVirtual(
+            MethodHandles.Lookup lookup, String name, MethodType methodType) throws Throwable {
+        // To get the point-of-use and invokedynamic to work the methodType here has the
+        // receiver type as the leading paramter which needs to be dropped for findVirtual().
+        MethodType mt = methodType.dropParameterTypes(0, 1);
+        MethodHandle mh = lookup.findVirtual(TestInvocationKinds.class, name, mt);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(enclosingType = TestInvocationKinds.class, name = "lookupVirtual"),
+        fieldOrMethodName = "getMaxIntegerValue",
+        returnType = int.class,
+        parameterTypes = {TestInvocationKinds.class, int.class, int.class}
+    )
+    private static int maxIntegerValue(TestInvocationKinds receiver, int x, int y) {
+        assertNotReached();
+        return 0;
+    }
+
+    public int getMaxIntegerValue(int x, int y) {
+        return x > y ? x : y;
+    }
+
+    static void testInvokeVirtual() {
+        System.out.print("testInvokeVirtual => max(77, -3) = ");
+        TestInvocationKinds receiver = new TestInvocationKinds();
+        int result = maxIntegerValue(receiver, 77, -3);
+        System.out.println(result);
+    }
+
+    static class Widget {
+        int value;
+        public Widget(int value) {}
+    }
+
+    private static CallSite lookupConstructor(
+            MethodHandles.Lookup lookup, String name, MethodType methodType) throws Throwable {
+        // methodType = (constructorParams);classToBeConstructed
+        Class<?> cls = methodType.returnType();
+        MethodType constructorMethodType = methodType.changeReturnType(void.class);
+        MethodHandle mh = lookup.findConstructor(cls, constructorMethodType);
+        return new ConstantCallSite(mh);
+    }
+
+    @CalledByIndy(
+        bootstrapMethod =
+                @BootstrapMethod(
+                    enclosingType = TestInvocationKinds.class,
+                    name = "lookupConstructor"
+                ),
+        fieldOrMethodName = "unused",
+        returnType = Widget.class,
+        parameterTypes = {int.class}
+    )
+    private static Widget makeWidget(int v) {
+        assertNotReached();
+        return null;
+    }
+
+    static void testConstructor() {
+        System.out.print("testConstructor => ");
+        Widget receiver = makeWidget(3);
+        assertEquals(Widget.class, receiver.getClass());
+        System.out.println(receiver.getClass());
+    }
+
+    public static void test() {
+        System.out.println(TestInvocationKinds.class.getName());
+        testStaticFieldAccessors();
+        testInstanceFieldAccessors();
+        testInvokeVirtual();
+        testConstructor();
+    }
+}
diff --git a/test/952-invoke-custom/src/TestInvokeCustomWithConcurrentThreads.java b/test/952-invoke-custom/src/TestInvokeCustomWithConcurrentThreads.java
index 761d182..2ef7ff7 100644
--- a/test/952-invoke-custom/src/TestInvokeCustomWithConcurrentThreads.java
+++ b/test/952-invoke-custom/src/TestInvokeCustomWithConcurrentThreads.java
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
+import annotations.BootstrapMethod;
 import annotations.CalledByIndy;
-import annotations.LinkerMethodHandle;
-import annotations.MethodHandleKind;
 import java.lang.invoke.CallSite;
 import java.lang.invoke.ConstantCallSite;
 import java.lang.invoke.MethodHandle;
@@ -66,16 +65,15 @@
     }
 
     @CalledByIndy(
-        invokeMethodHandle =
-                @LinkerMethodHandle(
-                    kind = MethodHandleKind.INVOKE_STATIC,
+        bootstrapMethod =
+                @BootstrapMethod(
                     enclosingType = TestInvokeCustomWithConcurrentThreads.class,
                     name = "linkerMethod",
-                    argumentTypes = {MethodHandles.Lookup.class, String.class, MethodType.class}
+                    parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class}
                 ),
-        name = "setCalled",
+        fieldOrMethodName = "setCalled",
         returnType = int.class,
-        argumentTypes = {int.class}
+        parameterTypes = {int.class}
     )
     private static int setCalled(int index) {
         called[index].getAndIncrement();
diff --git a/test/952-invoke-custom/src/TestLinkerMethodMinimalArguments.java b/test/952-invoke-custom/src/TestLinkerMethodMinimalArguments.java
index 74ac3cd..ff598bb 100644
--- a/test/952-invoke-custom/src/TestLinkerMethodMinimalArguments.java
+++ b/test/952-invoke-custom/src/TestLinkerMethodMinimalArguments.java
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
+import annotations.BootstrapMethod;
 import annotations.CalledByIndy;
-import annotations.LinkerMethodHandle;
-import annotations.MethodHandleKind;
 import java.lang.invoke.CallSite;
 import java.lang.invoke.ConstantCallSite;
 import java.lang.invoke.MethodHandle;
@@ -32,16 +31,15 @@
     static final int FAILURE_TYPE_TARGET_METHOD_THROWS = 3;
 
     @CalledByIndy(
-        invokeMethodHandle =
-                @LinkerMethodHandle(
-                    kind = MethodHandleKind.INVOKE_STATIC,
+        bootstrapMethod =
+                @BootstrapMethod(
                     enclosingType = TestLinkerMethodMinimalArguments.class,
-                    argumentTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
+                    parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
                     name = "linkerMethod"
                 ),
-        name = "_add",
+        fieldOrMethodName = "_add",
         returnType = int.class,
-        argumentTypes = {int.class, int.class}
+        parameterTypes = {int.class, int.class}
     )
     private static int add(int a, int b) {
         assertNotReached();
diff --git a/test/952-invoke-custom/src/TestLinkerMethodMultipleArgumentTypes.java b/test/952-invoke-custom/src/TestLinkerMethodMultipleArgumentTypes.java
index acb6986..0015c00 100644
--- a/test/952-invoke-custom/src/TestLinkerMethodMultipleArgumentTypes.java
+++ b/test/952-invoke-custom/src/TestLinkerMethodMultipleArgumentTypes.java
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
+import annotations.BootstrapMethod;
 import annotations.CalledByIndy;
 import annotations.Constant;
-import annotations.LinkerMethodHandle;
-import annotations.MethodHandleKind;
 import java.lang.invoke.CallSite;
 import java.lang.invoke.ConstantCallSite;
 import java.lang.invoke.MethodHandle;
@@ -29,12 +28,11 @@
     private static int bootstrapRunCount = 0;
 
     @CalledByIndy(
-        invokeMethodHandle =
-                @LinkerMethodHandle(
-                    kind = MethodHandleKind.INVOKE_STATIC,
+        bootstrapMethod =
+                @BootstrapMethod(
                     enclosingType = TestLinkerMethodMultipleArgumentTypes.class,
                     name = "linkerMethod",
-                    argumentTypes = {
+                    parameterTypes = {
                         MethodHandles.Lookup.class,
                         String.class,
                         MethodType.class,
@@ -50,7 +48,10 @@
                         long.class
                     }
                 ),
-        methodHandleExtraArgs = {
+        fieldOrMethodName = "_add",
+        returnType = int.class,
+        parameterTypes = {int.class, int.class},
+        constantArgumentsForBootstrapMethod = {
             @Constant(intValue = -1),
             @Constant(intValue = 1),
             @Constant(intValue = (int) 'a'),
@@ -61,10 +62,7 @@
             @Constant(stringValue = "Hello"),
             @Constant(classValue = TestLinkerMethodMultipleArgumentTypes.class),
             @Constant(longValue = 123456789L)
-        },
-        name = "_add",
-        returnType = int.class,
-        argumentTypes = {int.class, int.class}
+        }
     )
     private static int add(int a, int b) {
         assertNotReached();
diff --git a/test/952-invoke-custom/src/TestLinkerUnrelatedBSM.java b/test/952-invoke-custom/src/TestLinkerUnrelatedBSM.java
index 3a63b33..139a172 100644
--- a/test/952-invoke-custom/src/TestLinkerUnrelatedBSM.java
+++ b/test/952-invoke-custom/src/TestLinkerUnrelatedBSM.java
@@ -14,20 +14,18 @@
  * limitations under the License.
  */
 
+import annotations.BootstrapMethod;
 import annotations.CalledByIndy;
 import annotations.Constant;
-import annotations.LinkerMethodHandle;
-import annotations.MethodHandleKind;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 
 class TestLinkerUnrelatedBSM extends TestBase {
     @CalledByIndy(
-        invokeMethodHandle =
-                @LinkerMethodHandle(
-                    kind = MethodHandleKind.INVOKE_STATIC,
+        bootstrapMethod =
+                @BootstrapMethod(
                     enclosingType = UnrelatedBSM.class,
-                    argumentTypes = {
+                    parameterTypes = {
                         MethodHandles.Lookup.class,
                         String.class,
                         MethodType.class,
@@ -35,10 +33,10 @@
                     },
                     name = "bsm"
                 ),
-        methodHandleExtraArgs = {@Constant(classValue = TestLinkerUnrelatedBSM.class)},
-        name = "_addf",
+        fieldOrMethodName = "_addf",
         returnType = float.class,
-        argumentTypes = {float.class, float.class}
+        parameterTypes = {float.class, float.class},
+        constantArgumentsForBootstrapMethod = {@Constant(classValue = TestLinkerUnrelatedBSM.class)}
     )
     private static float addf(float a, float b) {
         assertNotReached();
@@ -50,11 +48,10 @@
     }
 
     @CalledByIndy(
-        invokeMethodHandle =
-                @LinkerMethodHandle(
-                    kind = MethodHandleKind.INVOKE_STATIC,
+        bootstrapMethod =
+                @BootstrapMethod(
                     enclosingType = UnrelatedBSM.class,
-                    argumentTypes = {
+                    parameterTypes = {
                         MethodHandles.Lookup.class,
                         String.class,
                         MethodType.class,
@@ -62,10 +59,10 @@
                     },
                     name = "bsm"
                 ),
-        methodHandleExtraArgs = {@Constant(classValue = TestLinkerUnrelatedBSM.class)},
-        name = "_subf",
+        fieldOrMethodName = "_subf",
         returnType = float.class,
-        argumentTypes = {float.class, float.class}
+        parameterTypes = {float.class, float.class},
+        constantArgumentsForBootstrapMethod = {@Constant(classValue = TestLinkerUnrelatedBSM.class)}
     )
     private static float subf(float a, float b) {
         assertNotReached();
diff --git a/test/952-invoke-custom/src/annotations/LinkerMethodHandle.java b/test/952-invoke-custom/src/annotations/BootstrapMethod.java
similarity index 65%
rename from test/952-invoke-custom/src/annotations/LinkerMethodHandle.java
rename to test/952-invoke-custom/src/annotations/BootstrapMethod.java
index e0e56c5..c167830 100644
--- a/test/952-invoke-custom/src/annotations/LinkerMethodHandle.java
+++ b/test/952-invoke-custom/src/annotations/BootstrapMethod.java
@@ -21,18 +21,25 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
 
-/** Describe a linker method to a method. */
+/**
+ * Describes a bootstrap method that performs method handle resolution on behalf of an
+ * invoke-dynamic instruction.
+ */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
-public @interface LinkerMethodHandle {
-    MethodHandleKind kind();
-
+public @interface BootstrapMethod {
+    /** The class containing the bootstrap method. */
     Class<?> enclosingType();
 
+    /** The bootstrap method name. */
     String name();
 
+    /** The return type of the bootstrap method. */
     Class<?> returnType() default CallSite.class;
 
-    Class<?>[] argumentTypes() default {};
+    /** The parameter types of the bootstrap method. */
+    Class<?>[] parameterTypes() default {Lookup.class, String.class, MethodType.class};
 }
diff --git a/test/952-invoke-custom/src/annotations/CalledByIndy.java b/test/952-invoke-custom/src/annotations/CalledByIndy.java
index 17b8259..c4d13a2 100644
--- a/test/952-invoke-custom/src/annotations/CalledByIndy.java
+++ b/test/952-invoke-custom/src/annotations/CalledByIndy.java
@@ -28,15 +28,17 @@
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface CalledByIndy {
-    LinkerMethodHandle[] invokeMethodHandle() default {};
+    /** Resolver metadata for bootstrapping */
+    BootstrapMethod[] bootstrapMethod() default {};
 
-    LinkerFieldHandle[] fieldMethodHandle() default {};
+    /** Field or method name. */
+    String fieldOrMethodName();
 
-    String name();
-
+    /** Return type of method() or field getter() */
     Class<?> returnType() default void.class;
 
-    Class<?>[] argumentTypes() default {};
+    /** Types of parameters for method or field setter() */
+    Class<?>[] parameterTypes() default {};
 
-    Constant[] methodHandleExtraArgs() default {};
+    Constant[] constantArgumentsForBootstrapMethod() default {};
 }
diff --git a/test/952-invoke-custom/src/annotations/LinkerFieldHandle.java b/test/952-invoke-custom/src/annotations/LinkerFieldHandle.java
deleted file mode 100644
index a3efe24..0000000
--- a/test/952-invoke-custom/src/annotations/LinkerFieldHandle.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.invoke.CallSite;
-
-/** Describe a linker method to a field. */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.ANNOTATION_TYPE)
-public @interface LinkerFieldHandle {
-    MethodHandleKind kind();
-
-    Class<?> enclosingType();
-
-    String name();
-
-    Class<?> type() default CallSite.class;
-}
diff --git a/test/952-invoke-custom/src/annotations/MethodHandleKind.java b/test/952-invoke-custom/src/annotations/MethodHandleKind.java
deleted file mode 100644
index 5847e2f..0000000
--- a/test/952-invoke-custom/src/annotations/MethodHandleKind.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 annotations;
-
-/** MethodHandle invocations kinds supported by invokedynamic */
-public enum MethodHandleKind {
-    GET_FIELD,
-    GET_STATIC,
-    PUT_FIELD,
-    PUT_STATIC,
-    INVOKE_VIRTUAL,
-    INVOKE_STATIC,
-    INVOKE_SPECIAL,
-    INVOKE_CONSTRUCTOR,
-    INVOKE_INTERFACE
-}
diff --git a/test/952-invoke-custom/src/transformer/IndyTransformer.java b/test/952-invoke-custom/src/transformer/IndyTransformer.java
index 286c098..45cb476 100644
--- a/test/952-invoke-custom/src/transformer/IndyTransformer.java
+++ b/test/952-invoke-custom/src/transformer/IndyTransformer.java
@@ -15,11 +15,9 @@
  */
 package transformer;
 
+import annotations.BootstrapMethod;
 import annotations.CalledByIndy;
 import annotations.Constant;
-import annotations.LinkerFieldHandle;
-import annotations.LinkerMethodHandle;
-import annotations.MethodHandleKind;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.invoke.MethodType;
@@ -43,18 +41,17 @@
 /**
  * Class for inserting invoke-dynamic instructions in annotated Java class files.
  *
- * This class replaces static method invocations of annotated methods
- * with invoke-dynamic instructions. Suppose a method is annotated as:
+ * <p>This class replaces static method invocations of annotated methods with invoke-dynamic
+ * instructions. Suppose a method is annotated as: <code>
  *
  * @CalledByIndy(
- *     invokeMethodHandle =
- *         @LinkerMethodHandle(
- *              kind = MethodHandleKind.INVOKE_STATIC,
- *                  enclosingType = TestLinkerMethodMinimalArguments.class,
- *                  argumentTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
- *                  name = "linkerMethod"
- *              ),
- *     name = "magicAdd",
+ *      bootstrapMethod =
+ *          @BootstrapMethod(
+ *               enclosingType = TestLinkerMethodMinimalArguments.class,
+ *               parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class},
+ *               name = "linkerMethod"
+ *     ),
+ *     fieldOdMethodName = "magicAdd",
  *     returnType = int.class,
  *     argumentTypes = {int.class, int.class}
  * )
@@ -66,13 +63,11 @@
  *    return x + y;
  * }
  *
- * Then invokestatic bytecodes targeting the add() method will be
- * replaced invokedynamic instructions targetting the CallSite that is
- * construction by the bootstrap method described by the @CalledByIndy
- * annotation.
+ * </code> Then invokestatic bytecodes targeting the add() method will be replaced invokedynamic
+ * instructions targetting the CallSite that is construction by the bootstrap method described by
+ * the @CalledByIndy annotation.
  *
- * In the example above, this results in add() being replaced by
- * invocations of magicAdd().
+ * <p>In the example above, this results in add() being replaced by invocations of magicAdd().
  */
 class IndyTransformer {
 
@@ -101,7 +96,7 @@
                     if (opcode == org.objectweb.asm.Opcodes.INVOKESTATIC) {
                         CalledByIndy callsite = callsiteMap.get(name);
                         if (callsite != null) {
-                            insertIndy(callsite.name(), desc, callsite);
+                            insertIndy(callsite.fieldOrMethodName(), desc, callsite);
                             return;
                         }
                     }
@@ -109,80 +104,26 @@
                 }
 
                 private void insertIndy(String name, String desc, CalledByIndy callsite) {
-                    Handle bsm = buildBootstrapMethodHandle(callsite);
-                    Object[] bsmArgs = buildBootstrapArguments(callsite);
+                    Handle bsm = buildBootstrapMethodHandle(callsite.bootstrapMethod()[0]);
+                    Object[] bsmArgs =
+                            buildBootstrapArguments(callsite.constantArgumentsForBootstrapMethod());
                     mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
                 }
 
-                private Handle buildBootstrapMethodHandle(CalledByIndy callsite) {
-                    MethodHandleKind kind;
-                    if (callsite.fieldMethodHandle().length != 0) {
-                        return buildBootstrapMethodHandleForField(callsite.fieldMethodHandle()[0]);
-                    } else if (callsite.invokeMethodHandle().length != 0) {
-                        return buildBootstrapMethodHandleForMethod(
-                                callsite.invokeMethodHandle()[0]);
-                    } else {
-                        throw new Error("Missing linker method handle in CalledByIndy annotation");
-                    }
-                }
-
-                private Handle buildBootstrapMethodHandleForField(LinkerFieldHandle fieldHandle) {
-                    int handleKind;
-                    switch (fieldHandle.kind()) {
-                        case GET_FIELD:
-                            handleKind = Opcodes.H_GETFIELD;
-                            break;
-                        case GET_STATIC:
-                            handleKind = Opcodes.H_GETSTATIC;
-                            break;
-                        case PUT_FIELD:
-                            handleKind = Opcodes.H_PUTFIELD;
-                            break;
-                        case PUT_STATIC:
-                            handleKind = Opcodes.H_PUTSTATIC;
-                            break;
-                        default:
-                            throw new Error("Unknown field invocation kind: " + fieldHandle.kind());
-                    }
-                    Class<?> resolverClass = fieldHandle.enclosingType();
-                    String resolverMethod = fieldHandle.name();
-                    Class<?> resolverReturnType = fieldHandle.type();
-
-                    // TODO: arguments types to invoke resolver with (default + extra args).
-                    throw new Error("WIP");
-                }
-
-                private Handle buildBootstrapMethodHandleForMethod(
-                        LinkerMethodHandle methodHandle) {
-                    int handleKind;
-                    switch (methodHandle.kind()) {
-                        case INVOKE_CONSTRUCTOR:
-                            handleKind = Opcodes.H_NEWINVOKESPECIAL;
-                            break;
-                        case INVOKE_INTERFACE:
-                            handleKind = Opcodes.H_INVOKEINTERFACE;
-                            break;
-                        case INVOKE_SPECIAL:
-                            handleKind = Opcodes.H_INVOKESPECIAL;
-                            break;
-                        case INVOKE_STATIC:
-                            handleKind = Opcodes.H_INVOKESTATIC;
-                            break;
-                        case INVOKE_VIRTUAL:
-                            handleKind = Opcodes.H_INVOKEVIRTUAL;
-                            break;
-                        default:
-                            throw new Error(
-                                    "Unknown method invocation kind: " + methodHandle.kind());
-                    }
-                    String className = Type.getInternalName(methodHandle.enclosingType());
-                    String methodName = methodHandle.name();
+                private Handle buildBootstrapMethodHandle(BootstrapMethod bootstrapMethod) {
+                    String className = Type.getInternalName(bootstrapMethod.enclosingType());
+                    String methodName = bootstrapMethod.name();
                     String methodType =
                             MethodType.methodType(
-                                            methodHandle.returnType(), methodHandle.argumentTypes())
+                                            bootstrapMethod.returnType(),
+                                            bootstrapMethod.parameterTypes())
                                     .toMethodDescriptorString();
                     return new Handle(
-                            handleKind, className, methodName, methodType, false /* itf */);
+                            Opcodes.H_INVOKESTATIC,
+                            className,
+                            methodName,
+                            methodType,
+                            false /* itf */);
                 }
 
                 private Object decodeConstant(int index, Constant constant) {
@@ -211,11 +152,10 @@
                     }
                 }
 
-                private Object[] buildBootstrapArguments(CalledByIndy callsite) {
-                    Constant[] rawArgs = callsite.methodHandleExtraArgs();
-                    Object[] args = new Object[rawArgs.length];
-                    for (int i = 0; i < rawArgs.length; ++i) {
-                        args[i] = decodeConstant(i, rawArgs[i]);
+                private Object[] buildBootstrapArguments(Constant[] bootstrapMethodArguments) {
+                    Object[] args = new Object[bootstrapMethodArguments.length];
+                    for (int i = 0; i < bootstrapMethodArguments.length; ++i) {
+                        args[i] = decodeConstant(i, bootstrapMethodArguments[i]);
                     }
                     return args;
                 }
@@ -237,8 +177,8 @@
             if (calledByIndy == null) {
                 continue;
             }
-            if (calledByIndy.name() == null) {
-                throw new Error("CallByIndy annotation does not specify name");
+            if (calledByIndy.fieldOrMethodName() == null) {
+                throw new Error("CallByIndy annotation does not specify a field or method name");
             }
             final int PRIVATE_STATIC = Modifier.STATIC | Modifier.PRIVATE;
             if ((m.getModifiers() & PRIVATE_STATIC) != PRIVATE_STATIC) {