8203343: VM.{metaspace|classloaders|classhierarchy...} jcmd should show invocation targets for Generated{Method|Constructor}AccessorImpl classes
authorstuefe
Thu, 14 Jun 2018 21:49:46 +0200
changeset 50572 6d4332f746ad
parent 50571 80945f661b80
child 50573 64ca9fbcf85f
8203343: VM.{metaspace|classloaders|classhierarchy...} jcmd should show invocation targets for Generated{Method|Constructor}AccessorImpl classes Reviewed-by: coleenp, sspitsyn, dholmes
src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp
src/hotspot/share/memory/heapInspection.cpp
src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.cpp
src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.hpp
src/hotspot/share/oops/reflectionAccessorImplKlassHelper.cpp
src/hotspot/share/oops/reflectionAccessorImplKlassHelper.hpp
test/hotspot/jtreg/serviceability/dcmd/vm/ShowReflectionTargetTest.java
--- a/src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp	Wed Jun 13 16:32:02 2018 -0700
+++ b/src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp	Thu Jun 14 21:49:46 2018 +0200
@@ -30,6 +30,7 @@
 #include "memory/allocation.hpp"
 #include "memory/resourceArea.hpp"
 #include "runtime/safepoint.hpp"
+#include "oops/reflectionAccessorImplKlassHelper.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/ostream.hpp"
 
@@ -202,9 +203,11 @@
       }
 
       if (print_classes) {
-
         if (_classes != NULL) {
           for (LoadedClassInfo* lci = _classes; lci; lci = lci->_next) {
+            // Non-anonymous classes should live in the primary CLD of its loader
+            assert(lci->_cld == _cld, "must be");
+
             branchtracker.print(st);
             if (lci == _classes) { // first iteration
               st->print("%*s ", indentation, "Classes:");
@@ -212,9 +215,15 @@
               st->print("%*s ", indentation, "");
             }
             st->print("%s", lci->_klass->external_name());
+
+            // Special treatment for generated core reflection accessor classes: print invocation target.
+            if (ReflectionAccessorImplKlassHelper::is_generated_accessor(lci->_klass)) {
+              st->print(" (invokes: ");
+              ReflectionAccessorImplKlassHelper::print_invocation_target(st, lci->_klass);
+              st->print(")");
+            }
+
             st->cr();
-            // Non-anonymous classes should live in the primary CLD of its loader
-            assert(lci->_cld == _cld, "must be");
           }
           branchtracker.print(st);
           st->print("%*s ", indentation, "");
--- a/src/hotspot/share/memory/heapInspection.cpp	Wed Jun 13 16:32:02 2018 -0700
+++ b/src/hotspot/share/memory/heapInspection.cpp	Thu Jun 14 21:49:46 2018 +0200
@@ -30,6 +30,7 @@
 #include "memory/heapInspection.hpp"
 #include "memory/resourceArea.hpp"
 #include "oops/oop.inline.hpp"
+#include "oops/reflectionAccessorImplKlassHelper.hpp"
 #include "runtime/os.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
@@ -490,6 +491,12 @@
   if (klass->is_interface()) {
     st->print(" (intf)");
   }
+  // Special treatment for generated core reflection accessor classes: print invocation target.
+  if (ReflectionAccessorImplKlassHelper::is_generated_accessor(klass)) {
+    st->print(" (invokes: ");
+    ReflectionAccessorImplKlassHelper::print_invocation_target(st, klass);
+    st->print(")");
+  }
   st->print("\n");
 
   // Print any interfaces the class has.
--- a/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.cpp	Wed Jun 13 16:32:02 2018 -0700
+++ b/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.cpp	Thu Jun 14 21:49:46 2018 +0200
@@ -24,13 +24,9 @@
  */
 #include "precompiled.hpp"
 
-#include "classfile/systemDictionary.hpp"
 #include "memory/metaspace/printMetaspaceInfoKlassClosure.hpp"
 #include "memory/resourceArea.hpp"
-#include "oops/constantPool.inline.hpp"
-#include "oops/instanceKlass.hpp"
-#include "oops/klass.hpp"
-#include "utilities/constantTag.hpp"
+#include "oops/reflectionAccessorImplKlassHelper.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/ostream.hpp"
 
@@ -54,6 +50,14 @@
     _out->print(UINTX_FORMAT_W(4) ": ", _num_classes);
     ResourceMark rm;
     _out->print("%s", k->external_name());
+
+    // Special treatment for generated core reflection accessor classes: print invocation target.
+    if (ReflectionAccessorImplKlassHelper::is_generated_accessor(k)) {
+      _out->print(" (invokes: ");
+      ReflectionAccessorImplKlassHelper::print_invocation_target(_out, k);
+      _out->print(")");
+    }
+
   }
 }
 
--- a/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.hpp	Wed Jun 13 16:32:02 2018 -0700
+++ b/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.hpp	Thu Jun 14 21:49:46 2018 +0200
@@ -40,7 +40,10 @@
   outputStream* const _out;
   const bool          _do_print;
 
+  bool print_reflection_invocation_target(outputStream* out, InstanceKlass* magic_accessor_impl_class);
+
 public:
+
   uintx _num_classes;
   uintx _num_instance_classes;
   uintx _num_array_classes;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/oops/reflectionAccessorImplKlassHelper.cpp	Thu Jun 14 21:49:46 2018 +0200
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "classfile/systemDictionary.hpp"
+#include "memory/resourceArea.hpp"
+#include "oops/reflectionAccessorImplKlassHelper.hpp"
+#include "utilities/constantTag.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+// This code extracts name of target class, method and signature from the constant pool of a class
+// assumed to be of type jdk/internal/reflect/Generated{SerializationConstructor|Constructor|Method}AccessorXXX.
+// Since this may be affected by bitrot if these classes change, extra care is taken to make the
+// release build of this coding robust.
+
+// We extract target class name, method name and sig from the constant pool of the Accessor class.
+// This is an excerpt of the Constant pool (see jdk/internal/reflect/MethodAccessorGenerator.java:)
+
+// (^  = Only present if generating SerializationConstructorAccessor)
+// 1    [UTF-8] [This class's name]
+// 2    [CONSTANT_Class_info] for above
+// 3    [UTF-8] "jdk/internal/reflect/{MethodAccessorImpl,ConstructorAccessorImpl,SerializationConstructorAccessorImpl}"
+// 4    [CONSTANT_Class_info] for above
+// 5    [UTF-8] [Target class's name]
+// 6    [CONSTANT_Class_info] for above
+// 7^   [UTF-8] [Serialization: Class's name in which to invoke constructor]
+// 8^   [CONSTANT_Class_info] for above
+// 9    [UTF-8] target method or constructor name
+// 10   [UTF-8] target method or constructor signature
+
+// Note that these strings are found at slightly different slots depending on the class type:
+// - MethodAccessorImpl, ConstructoreAccessorImpl: slots 5, 7 and 8.
+// - SerializationConstructorAccessorImpl: slots 5, 9 and 10.
+// Unfortunately SerializationConstructorAccessorImpl is a child of ConstructoreAccessorImpl and there
+//  is no easy way to tell them apart. So we examine parent class name.
+
+enum cpi_slots {
+  cpi_slot_parent_class_name = 3,
+  cpi_slot_target_class_name = 5,
+  cpi_slot_target_method_name = 7,
+  cpi_slot_target_method_name_sca = 9, // SerializationConstructorAccessor case, see above
+  cpi_slot_target_method_sig = 8,
+  cpi_slot_target_method_sig_sca = 10  // SerializationConstructorAccessor case, see above
+};
+
+// Returns a string, resource-area allocated, from an UTF8 slot in the constant pool in the
+// given Klass*.
+static const char* get_string_from_cp_with_checks(const InstanceKlass* k, int cpi) {
+  const char* s = NULL;
+  const ConstantPool* const cp = k->constants();
+
+  assert(cp != NULL, "No cp?");
+  assert(cp->is_within_bounds(cpi), "Unexpected constant pool layout for \"%s\", child class of Generated{Method|Constructor}AccessorImplXXX"
+         " (cpi %d out of bounds for [0..%d)).", k->external_name(), cpi, cp->length());
+  assert(cp->tag_at(cpi).is_utf8(), "Unexpected constant pool layout for \"%s\", child class of Generated{Method|Constructor}AccessorImplXXX"
+         " (no UTF8 at cpi %d (%u)).", k->external_name(), cpi, cp->tag_at(cpi).value());
+
+  // Be nice in release: lets not crash, just return NULL.
+  if (cp != NULL && cp->is_within_bounds(cpi) && cp->tag_at(cpi).is_utf8()) {
+    s = cp->symbol_at(cpi)->as_C_string();
+  }
+
+  return s;
+}
+
+// helper, returns true if class name of given class matches a given prefix
+static bool classname_matches_prefix(const Klass* k, const char* prefix) {
+  const char* classname = k->external_name();
+  if (classname != NULL) {
+    if (::strncmp(classname, prefix, strlen(prefix)) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+// Returns true if k is of type jdk/internal/reflect/GeneratedMethodAccessorXXX.
+bool ReflectionAccessorImplKlassHelper::is_generated_method_accessor(const InstanceKlass* k) {
+  return k->super() == SystemDictionary::reflect_MethodAccessorImpl_klass() &&
+         classname_matches_prefix(k, "jdk.internal.reflect.GeneratedMethodAccessor");
+}
+
+// Returns true if k is of type jdk/internal/reflect/GeneratedConstructorAccessorXXX.
+bool ReflectionAccessorImplKlassHelper::is_generated_constructor_accessor(const InstanceKlass* k) {
+  return k->super() == SystemDictionary::reflect_ConstructorAccessorImpl_klass() &&
+         classname_matches_prefix(k, "jdk.internal.reflect.GeneratedConstructorAccessor");
+}
+
+// Returns true if k is of type jdk/internal/reflect/GeneratedSerializationConstructorAccessorXXX.
+bool ReflectionAccessorImplKlassHelper::is_generated_method_serialization_constructor_accessor(const InstanceKlass* k) {
+  // GeneratedSerializationConstructorAccessor is not a direct subclass of ConstructorAccessorImpl
+  const Klass* sk = k->super();
+  if (sk != NULL && sk->super() == SystemDictionary::reflect_ConstructorAccessorImpl_klass() &&
+      classname_matches_prefix(k, "jdk.internal.reflect.GeneratedSerializationConstructorAccessor")) {
+    return true;
+  }
+  return false;
+}
+
+const char* ReflectionAccessorImplKlassHelper::get_target_class_name(const InstanceKlass* k) {
+  return get_string_from_cp_with_checks(k, cpi_slot_target_class_name);
+}
+
+const char* ReflectionAccessorImplKlassHelper::get_target_method_name(const InstanceKlass* k) {
+  const int target_method_name_cpi =
+      is_generated_method_serialization_constructor_accessor(k) ? cpi_slot_target_method_name_sca : cpi_slot_target_method_name;
+  return get_string_from_cp_with_checks(k, target_method_name_cpi);
+}
+
+const char* ReflectionAccessorImplKlassHelper::get_target_method_signature(const InstanceKlass* k) {
+  const int target_method_name_cpi =
+      is_generated_method_serialization_constructor_accessor(k) ? cpi_slot_target_method_sig_sca : cpi_slot_target_method_sig;
+  return get_string_from_cp_with_checks(k, target_method_name_cpi);
+}
+
+// Returns true if this is either one of jdk/internal/reflect/Generated{SerializationConstructor|Constructor|Method}AccessorXXX
+// and it is safe to call print_invocation_target(k)
+bool ReflectionAccessorImplKlassHelper::is_generated_accessor(const Klass* k) {
+  if (k != NULL && k->is_instance_klass()) {
+    const InstanceKlass* ik = InstanceKlass::cast(k);
+    if (ik->is_initialized()) {
+      return is_generated_method_accessor(ik) ||
+             is_generated_constructor_accessor(ik) ||
+             is_generated_method_serialization_constructor_accessor(ik);
+    }
+  }
+  return false;
+}
+void ReflectionAccessorImplKlassHelper::print_invocation_target(outputStream* out, Klass* k) {
+  assert(ReflectionAccessorImplKlassHelper::is_generated_accessor(k), "Invariant");
+  InstanceKlass* ik = InstanceKlass::cast(k);
+  ResourceMark rm;
+  const char* target_class_name = ReflectionAccessorImplKlassHelper::get_target_class_name(ik);
+  const char* target_method_name = ReflectionAccessorImplKlassHelper::get_target_method_name(ik);
+  const char* target_method_signature = ReflectionAccessorImplKlassHelper::get_target_method_signature(ik);
+  out->print("%s::%s %s",
+      target_class_name != NULL ? target_class_name : "?",
+      target_method_name != NULL ? target_method_name : "?",
+      target_method_signature != NULL ? target_method_signature : "?");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/oops/reflectionAccessorImplKlassHelper.hpp	Thu Jun 14 21:49:46 2018 +0200
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef HOTSPOT_SHARE_OOPS_REFLECTIONACCESSORIMPLKLASSHELPER_HPP
+#define HOTSPOT_SHARE_OOPS_REFLECTIONACCESSORIMPLKLASSHELPER_HPP
+
+#include "memory/allocation.hpp"
+
+class InstanceKlass;
+
+// Helper for classes derived from jdk/internal/reflect/{Method|Constructor}AccessorImpl:
+// offers convenience functions to extract the names of target class/method/signature
+// from the constant pool of these classes.
+class ReflectionAccessorImplKlassHelper: public AllStatic {
+
+  // Returns true if k is of type jdk/internal/reflect/GeneratedMethodAccessorXXX.
+  static bool is_generated_method_accessor(const InstanceKlass* k);
+
+  // Returns true if k is of type jdk/internal/reflect/GeneratedConstructorAccessorXXX.
+  static bool is_generated_constructor_accessor(const InstanceKlass* k);
+
+  // Returns true if k is of type jdk/internal/reflect/GeneratedSerializationConstructorAccessorXXX.
+  static bool is_generated_method_serialization_constructor_accessor(const InstanceKlass* k);
+
+  // Assuming k is of type jdk/internal/reflect/Generated{SerializationConstructor|Constructor|Method}AccessorXXX,
+  // the name of the target class as resource-area allocated string.
+  static const char* get_target_class_name(const InstanceKlass* k);
+
+  // Assuming k is of type jdk/internal/reflect/Generated{SerializationConstructor|Constructor|Method}AccessorXXX,
+  // the name of the target method as resource-area allocated string.
+  static const char* get_target_method_name(const InstanceKlass* k);
+
+  // Assuming k is of type jdk/internal/reflect/Generated{SerializationConstructor|Constructor|Method}AccessorXXX,
+  // the signature of the target method as resource-area allocated string.
+  static const char* get_target_method_signature(const InstanceKlass* k);
+
+public:
+
+  // Returns true if k is of type jdk/internal/reflect/Generated{SerializationConstructor|Constructor|Method}AccessorXXX
+  // and it is safe to call print_invocation_target(k)
+  static bool is_generated_accessor(const Klass* k);
+
+  // Assuming k is of type jdk/internal/reflect/Generated{SerializationConstructor|Constructor|Method}AccessorXXX,
+  // print out target class, method, signature in one line.
+  static void print_invocation_target(outputStream* out, Klass* k);
+
+};
+
+
+
+
+#endif /* HOTSPOT_SHARE_OOPS_REFLECTIONACCESSORIMPLKLASSHELPER_HPP */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/dcmd/vm/ShowReflectionTargetTest.java	Thu Jun 14 21:49:46 2018 +0200
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import jdk.test.lib.dcmd.CommandExecutor;
+import jdk.test.lib.dcmd.JMXExecutor;
+import jdk.test.lib.process.OutputAnalyzer;
+
+/*
+ * @test
+ * @summary Test that various diagnostic commands which can show core reflection
+ *          invocation targets do so correctly (See: JDK-8203343).
+ * @library /test/lib
+ * @run testng/othervm -Dsun.reflect.noInflation=true ShowReflectionTargetTest
+ * @author stuefe
+ */
+
+public class ShowReflectionTargetTest {
+
+    @SuppressWarnings("unused")
+    private static class Dummy {
+        int _i;
+        public Dummy(int i) { _i = i; }
+        public int get_i() { return _i; }
+    }
+
+    public void run(CommandExecutor executor) throws Exception {
+        // Do some reflection; since we set -Dsun.reflect.noInflation=true, this should
+        // immediately generate Generated{Method|Constructor}Accessor objects.
+        Class<?> c = Class.forName("ShowReflectionTargetTest$Dummy");
+        Constructor<?> ctor = c.getConstructor(int.class);
+        Method m = c.getMethod("get_i");
+
+        Object o = ctor.newInstance(17);
+        int j = ((Integer)m.invoke(o)).intValue();
+        Assert.assertEquals(j, 17);
+
+        // Now invoke VM.class_hierarchy and check its output.
+        // Should show reflection targets, e.g.:
+        // ....
+        //        |--jdk.internal.reflect.MagicAccessorImpl/null
+        //        |  |--jdk.internal.reflect.FieldAccessorImpl/null
+        //        |  |  |--jdk.internal.reflect.UnsafeFieldAccessorImpl/null
+        //        |  |  |  |--jdk.internal.reflect.UnsafeStaticFieldAccessorImpl/null
+        //        |  |  |  |  |--jdk.internal.reflect.UnsafeQualifiedStaticFieldAccessorImpl/null
+        //        |  |  |  |  |  |--jdk.internal.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl/null
+        //        |  |--jdk.internal.reflect.ConstructorAccessorImpl/null
+        //        |  |  |--jdk.internal.reflect.DelegatingConstructorAccessorImpl/null
+        //        |  |  |--jdk.internal.reflect.NativeConstructorAccessorImpl/null
+        // >       |  |  |--jdk.internal.reflect.GeneratedConstructorAccessor1/0x00007f75f04889b0 (invokes: java/lang/management/ManagementPermission::<init> (Ljava/lang/String;)V)
+        // >       |  |  |--jdk.internal.reflect.GeneratedConstructorAccessor2/0x00007f75f0494990 (invokes: ShowReflectionTargetTest$Dummy::<init> (I)V)
+        //        |  |  |--jdk.internal.reflect.BootstrapConstructorAccessorImpl/null
+        //        |  |--jdk.internal.reflect.MethodAccessorImpl/null
+        // >       |  |  |--jdk.internal.reflect.GeneratedMethodAccessor1/0x00007f75f0494450 (invokes: ShowReflectionTargetTest$Dummy::get_i ()I)
+        //        |  |  |--jdk.internal.reflect.DelegatingMethodAccessorImpl/null
+        // ...
+
+        OutputAnalyzer output = executor.execute("VM.class_hierarchy");
+
+        output.shouldMatch(".*jdk.internal.reflect.GeneratedConstructorAccessor.*invokes.*ShowReflectionTargetTest\\$Dummy::<init>.*");
+        output.shouldMatch(".*jdk.internal.reflect.GeneratedMethodAccessor.*invokes.*ShowReflectionTargetTest\\$Dummy::get_i.*");
+
+    }
+
+    @Test
+    public void jmx() throws Exception {
+        run(new JMXExecutor());
+    }
+
+}
+
+
+