JDK-6237654 : cached methodOops in universe class confuse RedefineClasses
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 1.4.2,5.0,6
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2005-03-08
  • Updated: 2010-04-02
  • Resolved: 2005-04-20
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other JDK 6
5.0u4Fixed 6 b33Fixed
Description
Misha reported the following RedefineClasses problem:

Date: Mon, 07 Mar 2005 22:44:39 -0800
From: ###@###.###
Subject: A problem with redefined java.lang.reflect.Method.invoke()

Hi guys,

I've recently discovered one more problem with method redefinition.
Actually, it should have been there forever, it's just that the code that
is problematic seems to be exercised quite rarely. It seems to me now
that I've seen problems like this in the past, but they were in big apps
like some App servers, that could just ignore such issues and proceed,
or some such.

The problem is that the VM initializes an internal variable, a methodOop
for java.lang.reflect.Method.invoke() method early, and then uses it
in some access rights checks. So if Method.invoke() is redefined, some
(but not all) reflection calls may fail. Look at the code below that is
from 1.4.2 JVM, vframe.cpp source file:


// Step back n frames, skip and pseudo frames in between.
// This function is used in Class.forName, Class.newInstance, Method.Invoke, 
// AccessController.doPrivileged.
//
// NOTE that in JDK 1.4 this has been exposed to Java as
// sun.reflect.Reflection.getCallerClass(), which can be inlined.
// Inlined versions must match this routine's logic. See, for example,
// Parse::inline_native_Reflection_getCallerClass in
// opto/library_call.cpp.
void vframeStreamCommon::security_get_caller_frame(int depth) {    
  while (depth-- > 0) {
    // skip Method.invoke() and auxiliary frames
    skip_method_invoke_and_aux_frames();
    if (at_end()) return;    
    // Skip real frame
    next();
  }

  // skip Method.invoke() and auxiliary frames
  skip_method_invoke_and_aux_frames();
}
  

void vframeStreamCommon::skip_method_invoke_and_aux_frames() {
  while (!at_end() &&
         (/*method() == Universe::reflect_invoke_method() */
		  //We replace the above code with the check that works for redefined java.lang.reflect.Method.invoke()
		  (method()->method_holder() == SystemDictionary::reflect_method_klass() &&
		   method()->name() == vmSymbols::invoke_name() &&
		   method()->signature() == vmSymbols::object_array_object_object_signature())
          || (Universe::is_gte_jdk14x_version() && UseNewReflection &&
              Klass::cast(method()->method_holder())->is_subclass_of(SystemDictionary::reflect_method_accessor_klass())))) {
    next();
  }
}


The 'skip_method_invoke_and_aux_frames()' above already contains the
original single code line commented out, and my fix for this problem
(not sure it's the best possible one, but anyway, it works) in the next 3
lines. I have a JFluid test which demonstrates how the original VM code
fails when Method.invoke() is redefined, and how this code works. It
looks like in Mustang the problem is there as well, though I didn't look
at its source code.

No doubt a simpler test for this bug can be created. It just looks like
the method that is invoked shouldn't be public and/or there should be
some other non-obvious condition, which you can find out by looking at
the relevant JDK code. Let me know if you would like me to do that.

Let me know what you think about this issue.

Misha

###@###.### 2005-03-08 19:18:11 GMT

Comments
SUGGESTED FIX See attached 6237654-cr2-full-webrev.tgz for the complete set of changes for the Tiger-Update4 version of this fix. ###@###.### 2005-04-07 17:24:29 GMT
07-04-2005

EVALUATION There are three methodOops cached in the universe class: static methodOop _finalizer_register_method; // static method for registering finalizable objects static methodOop _reflect_invoke_method; // method for security checks static methodOop _loader_addClass_method; // method for registering loaded classes in class loader vector These fields are initialized in universe.cpp:: universe_post_init() and in the regular system are expected to be unchanged over the life of the VM. However, the RedefineClasses() API can change some of these invariants from the regular system. There is a getter function for these fields. It looks like we will need to add a setter function and an equals function. The equals function can likely be optimized to do simple methodOop comparison if RedefineClasses has never been called. If RedefineClasses has been called, then a more complicated comparison is needed so the different versions of the methods all look "equal". The setter function will only be used by RedefineClasses to reset the value of the cached methodOoop after a RedefineClasses has finished. ###@###.### 2005-03-08 20:07:07 GMT On closer inspection, a distinction needs to made between the different types of users of these cached methodOops. The _finalizer_register_method and _loader_addClass_method users just care about the latest and greatest version of the method. The _reflect_invoke_method user cares about all versions of the method. For the _finalizer_register_method and _loader_addClass_method caches, a slightly different cache implementation will do the trick. If the klass and method index are cached instead of the methodOop, then the latest and greatest version of the methodOop is just one obj_at() call away from the cache user. For the _reflect_invoke_method cache, the klass and method index are also cached along with weak references to previous versions of the method. The weak references will allow old versions of the method to be GC'ed when they are no longer active. The cache will access the current version of the method using the klass and method index via an obj_at() call and, if needed, will search a growable array of weak references to older versions of the method. ###@###.### 2005-03-30 22:25:24 GMT
08-03-2005