invokespecial of default methods in unrelated interfaces
Dan Smith
daniel.smith at oracle.com
Thu Jun 13 13:06:08 PDT 2013
On Jun 13, 2013, at 11:31 AM, Daniel Heidinga <Daniel_Heidinga at ca.ibm.com> wrote:
> We've put together a defender method test case using invokespecial and we'd like some confirmation of our spec interpretation. The testcase is:
>
> interface A {
> default void m(){ System.out.println("A"); };
> }
> interface B {
> default void m(){ System.out.println("B"); };
> interface C {
> default void m(){ System.out.println("C"); };
> }
> class D implements A, B {
> void m() {
> C.super.m(); // Note, D does not implement C
> }
> public static void main(String[] args) {
> new D().m();
> }
> }
>
> While javac won't compile this, it is legal bytecode (generated with ASM because class D is unrelated to interface C). Executing 'new D().m()' unfortunately crashes Hotspot b93.
>
> The lambda changes to the JVM spec for invokespecial indicate 'The named method is resolved (5.4.3.3)'. 5.4.3.3 states "Method resolution attempts to look up the referenced method in C and its superclasses". As C clearly has an implementation of m(), resolution should succeed and C.m() should be called. (As an aside, there is an issue with invokespecial referencing 5.4.3.3 for interface sends: "If C is an interface, method resolution throws an IncompatibleClassChangeError.")
>
> Does anyone disagree with this interpretation of the spec?
I was confused by the apparent anything-goes nature of the spec for invokespecial, too, until I found 4.9.2.
"Each invokespecial instruction must name an instance initialization method (§2.9), a method in the current class, or a method in a superclass of the current class."
Strictly speaking, this means that all superinterface calls are illegal. Of course, we need to change it to allow calls to methods in direct superinterfaces. That's on the radar for the next iteration of the spec.
Here's a write-up I did recently for our own VM people about how invokespecial is specified, what needs to change, and what isn't entirely resolved yet. You may find it useful, and I'm happy to hear any feedback you have.
> The JVMS 7 spec describes the behavior, roughly, as follows:
>
> 1) At link time (5.4.1), structural constraints are enforced (4.9.2): the referenced method must be one of: i) an instance initialization method, ii) a non-init method in the current class, or iii) a non-init method in a superclass of the current class. [Note: does "in" mean "declared in" or "referenced in"?]
>
> 2) The method reference is resolved (5.4.3.3). Various errors can occur. [Note: not sure on how this interacts with (1). I assume we have to at least resolve the class name before we can decide if the invocation is structurally well-formed or not...]
>
> 3) In case (i), if the resolved method is not declared by the named class, NSME.
>
> 4) If the resolved method is 'static', ICCE.
>
> 5) In cases (i) and (iii) where the resolved method is 'protected', the dynamic protected-access check is performed. [Note: this is strange because it doesn't identify the error that occurs and it doesn't show up in the list of Runtime Exceptions -- only in the Description section; also note that the HotSpot implementation of the check seems overly-aggressive: JDK-8009697.]
>
> 6) Selection occurs. [Note: Unlike invokevirtual, selection is static -- it need only occur once per instruction.]
> - In cases (i) and (ii): the resolved method is selected.
> - In case (iii) with ACC_SUPER not set: the resolved method is selected.
> - In case (iii) with ACC_SUPER set: we search for a matching name/descriptor starting at the immediate super and walking the superclass chain. If there is no matching superclass method (the resolved method is in an interface), AME. [Note: this is only relevant if the named class is _not_ the immediate super.]
>
> 7) If the objectref is null, NPE.
>
> 8) If the selected method is abstract, AME.
>
> 9) The selected method is invoked.
>
> Does that sound right? Consistent with HotSpot?
>
> ---
>
> Lambda Spec, 0.6.2 (Part J) makes the following changes:
>
> - The instruction may use a Methodref _or_ an InterfaceMethodref (4.9.1)
>
> - Rather than choosing nondeterministically among methods in a superinterface hierarchy, resolution chooses the "best" one, or reports an error if there is more than one "best" one.
>
> There are some problems here...
>
> ---
>
> Lambda Spec Part J _should_ do the following, too:
>
> - Case (ii) should not be read to exclude the possibility that the "current class" is an interface
>
> - Another case, (iv), is permitted by the structural constraints: a non-init method referenced via a _direct_ superinterface of the current class/interface. (It's not clear to me whether case (iii) is meant to refer to the _named_ class or the _resolved method's_ class. But this, for (iv), must be a restriction on the named class.)
>
> - Selection for case (iv) is just like selection for cases (i) and (ii) -- the resolved method is the selected method.
>
> - Selection for case (iii) needs to search superinterfaces before giving up, much like invokevirtual selection.
>
> I think that should address the bugs. Anything I missed?
>
> ---
>
> Alternative 1: Loose overriding for invokespecial
>
> This was part of Keith's prototype: invokespecial was modified to identify interface methods that override the resolved method (in the language-level sense) without relying on bridges.
>
> This is no longer what we want. The code changes should be removed, if they haven't been already.
>
> ---
>
> Alternative 2: Indirect superinterface referencing
>
> This would involve relaxing case (iv) so that the named class can be _any_ superinterface of the current class/interface. javac would never take advantage of the change, but it would be more consistent with the treatment of superclass methods.
>
> The downside is that we would then need to do the ACC_SUPER selection process (6). And it's not clear _which_ superinterface (or superclass) should act as the starting point for the selection search. (It's possible that more than one direct superinterface is a subtype of the named interface.)
>
> Also worth noting that, in retrospect, we really don't like the existing ability to name any superclass; if we were starting fresh, we'd probably force the named class to be the direct superclass.
>
> ---
>
> Alternative 3: Minimize resolution errors
>
> For invokevirtual and invokeinterface, Karen has pointed out that the resolution errors that occur when there is no "best" interface method are unnecessarily brittle: ultimately, selection will find a class method, or a "best" interface method, or report an error.
>
> So we could change resolution so that it simply picks an arbitrary interface method. (That's nondeterministic, and the nondeterminism can be observed -- class loader constraints depend on the choice -- but that's the way it has always been.)
>
> This would leave invokespecial with the responsibility of ensuring that the resolved method really is uniquely "best" in cases where the ACC_SUPER selection process does not kick in, and report an error otherwise. But that seems do-able. One way to formulate it would be to simply always do selection, with some extra parameters about where the search starts...
>
> invokestatic wouldn't have to worry about ambiguity, because it already reports an error if the resolved method appears in an interface other than the named interface.
I'd pay the most attention to Alternative 3. There does seem to be a lot of interest in refining resolution so that it's more permissive. (The presence of static and private methods complicates matters, though.)
—Dan
More information about the lambda-spec-observers
mailing list