|
Description
|
There appears to be a flaw in java.lang.reflect.Proxy w.r.t. the spec, which
states that:
"When two or more interfaces of a proxy class contain a method with the same
name and parameter signature, the order of the proxy class's interfaces
becomes significant. When such a duplicate method is invoked on a proxy
instance, the Method customer passed to the invocation handler will not
necessarily be the one whose declaring class is assignable from the
reference type of the interface that the proxy's method was invoked through.
This limitation exists because the corresponding method implementation in
the generated proxy class cannot determine which interface it was invoked
through. Therefore, when a duplicate method is invoked on a proxy instance,
the Method customer for the method in the foremost interface that contains the
method (either directly or inherited through a superinterface) in the proxy
class's list of interfaces is passed to the invocation handler's invoke
method, regardless of the reference type through which the method invocation
occurred."
While this sounds customer , it does not state the order in which inherited
interfaces are considered (i.e., hierarchy depth first or breadth first),
which means that if you have for instance this interface hierarchy:
C
|
A B
|____|
|
PI
where PI extends A and B, and A extends C, and both C and B declares method
'foo(bar)', and we then want a proxy instance for PI, the spec becomes
ambigous (imho).
Also, in the Sun implementation, which delegates the creation of the Proxy
class to sun.misc.ProxyGenerator, this is implemented by doing 'getMethods'
on the requested proxy insterfaces. This amplifies the problem, since the
order of the returned methods from getMethods is unspecified.
In the HotSpot VM, the order is actually dependent on the order in which the
VM's UTF-8 constants are created, i.e., order will be different if a program
loads other classes which uses the same name-type id's in random order.
While HotSpot still will give consistent order between methods declared in
different classes in a hietrarchy, this illustrates the potential pitfalls
of relying on the order of methods returned from getMethods.
We would like to see both the order in which Proxy interfaces are
considered, and the order of getMethods, specified in the API docs, since
code both in the Sun class libraries as well as user code (incorrectly)
tends to rely on just a particular order for these.
|
|
Evaluation
|
This specification flaw dates back to the 1.3 release, when the Proxy class
was introduced.
-- xxxxx@xxxxx 2002/12/5
I agree that the described issue is an ambiguity in the Proxy spec (as described below, I believe that it is an ambiguity shared with Class.getMethod). The excerpt of the spec quoted in the Description was intended to resolve the similar issue with respect to multiple proxy interfaces that each have a method with the same signature. But when a single proxy interface inherits two different methods with the same signature (as with PI in the example given), it is unspecified which of the two will be chosen to pass to InvocationHandler.invoke.
[Note: If the proxy interface overrides the methods (that would otherwise be multiply-inherited) with its own declared method of the same signature, then there is no ambiguity: the single method that is a member of PI will be chosen. Whether or not a subinterface's author overrides a method may depend on something like whether or not the author wants to provide the subinterface with its own, specialized method comment for the method-- a change that, in either direction, might seem relatively harmless from a semantic perspective... but with that view, code that depends on the exact declaring class of a method passed to InvocationHandler.invoke could be considered fragile.]
The fact that sun.misc.ProxyGenerator uses Class.getMethods to collect the methods from each proxy interface is actually _not_ what's important here. That loop invokes Class.getMethods for each proxy interface in order, recognizes duplicates (which must all be processed to reconcile conflicting checked exception declarations), and remembers the first proxy interface in which it found each unique method signature (note that ProxyGenerator.ProxyMethod doesn't even retain the declaring class of the methods found). What _is_ important is how the generated proxy class conjures the Method object to pass to the invocation handler: in its static initializer, the generated proxy class invokes
<interface>.getMethod(<name>, <parameter_types>)
where <interface> is the Class object for the proxy interface in which a method with signature <name>(<parameter_types>) was first found (in the loop described above). Therefore, in Sun's J2SE implementation, the behavior of a dynamic proxy class in this ambiguous situation depends on the behavior of Class.getMethod in the same situation.
See the Comments section of 4471738 for some strongly related discussion. 4471738 was a bug in 1.4beta in which (because of the new 1.4 reflection implementation) Class.getMethod was behaving quite non-deterministically, and thus also affecting Proxy behavior. With 4471738, Class.getMethod was in violation of its spec, because by the JLS, if an interface overrides a method from a superinterface, the interface does not have the overridden method as a member. But after that bug was fixed, there remains (from the 4471738 Comments) the Class.getMethod and Proxy spec ambiguity issues of multiple direct superinterface search order and the depth-first vs. breadth-first superinterface search order, which are also the issues of this bug report.
In fixing 4471738 for 1.4fcs, it was decided that the Class.getMethod spec would not be explicitly clarified on these issues, but the implementation would obey what would seem to be the desirable clarification anyway: declaration order, depth-first searching of superinterfaces (with direct superclass preceding direct superinterfaces). That algorithm seemed intuitive, and it is consistent with what Proxy does for multiple proxy interfaces.
So it would seem that a possible fix for this bug report would be to tighten the specification of Class.getMethod to state the order in which the superinterfaces are searched (declaration order, as with Class.getInterfaces, and depth-first), and also to tighten the Proxy/InvocationHandler spec to state that for a proxy interface that has multiple methods with the same signature (through inheritance), the same algorithm is used to choose the Method to pass to InvocationHandler.invoke.
Are either or both of those spec clarifications desirable? I'm inclined to think that improving the determinism of these APIs would be a good thing. The merits of tightening Class.getMethod would need to be reviewed by other interested parties. And I think that it would be strongly advised to make this kind of spec change in a dot release like Tiger, as opposed to a dot-dot release like Mantis. (At any rate, Sun's existing implementations of Class.getMethod and java.lang.reflect.Proxy in 1.4 should already obey the proposed tighter behavior described above.)
Regarding Class.getMethods, tightening its specification simiarly would seem to be a more significant change because the existing spec is quite explicit that the order of the returned methods is _not_ specified. Also, completely specifying the order of the methods returned by Class.getMethods would raise quite a few more issues than those described above-- such as an order for overloaded methods-- that might seem unnecessarily complicating (perhaps why the order was explicitly not specified to begin with). And specifying only partial ordering restrictions on Class.getMethods might seem rather awkward, too.
[Incidentally, note that for Tiger, I presume that the Proxy spec will likely have to be updated to accomodate methods with the same signature (name and parameter type list) but different return values-- a situation that causes Proxy to throw an IllegalArgumentException today.]
xxxxx@xxxxx 2002-12-05
More extensive consultation with xxxxx@xxxxx subsequent to the previous Evaluation entry resulted in the conclusion that this bug will NOT be fixed with tightened specifications as was suggested above.
The reason is that we do not want to convey an impression that the distinction between different Method objects that could be chosen (by Class.getMethod or a dynamic proxy class implementation) with the current specification should be considered a semantically meaningful distinction for the sake of method invocation. When an interface method is invoked at runtime, all that's important is the method's name and descriptor (parameter types and return type), not its declaring interface. The declaring interface that is exposed by a java.lang.reflect.Method object (and its object equivalence semantics) is a declaration-structure artifact that applications not performing declaration structure analysis (and thus not interested in all such Methods) should not be concerned with.
As described above, code that relies on the declaring interface of a method obtained from Class.getMethod or a dynamic proxy class is brittle, because it is sensitive to (even binary-compatible) movement of the declaration of the method up or down the type inheritance hierarchy (which can happen for reasons as seemingly harmless as wanting to add or remove method doc comments). The specification tightening proposed above might imply that such reliance is no longer brittle, whereas it would, in reality, remain brittle. More helpful might be a utility function for comparing Method objects for equality solely on the basis of their names and descriptors, which is all that an InvocationHandler implementation should care about.
[In a sense, this bug stems from a conceptual conflation in the design of the java.lang.reflect.Method API. A Method object represents (must represent) a method declaration, which is useful for structural reflection, but not a perfect match for method invocation-- but Method.invoke is, in fact, the API for reflective method invocation too. For example, there is no reflective representation of an inherited member method. If, in the interface hierarchy given in the Description, B and C (the interfaces that actually declare "foo" methods) are non-public but A and PI are public, there is no way to invoke the "foo" method on an instance of PI reflectively from an arbitrary package (assuming that the implementing class is also non-public), whereas it is quite possible to do so in source code, using references of type A or PI, which inherit "foo". For the purposes of reflective method invocation, all that code really should care about is the method's name and descriptor, but there's no representation of that concept in the java.lang.reflect APIs.]
xxxxx@xxxxx 2003-08-20
|