*
The moose likes Java in General and the fly likes Is this a bug? Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


Win a copy of Murach's Java Servlets and JSP this week in the Servlets forum!
JavaRanch » Java Forums » Java » Java in General
Bookmark "Is this a bug?" Watch "Is this a bug?" New topic
Author

Is this a bug?

Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
Consider the following:



This compiles fine. I'm not sure why. Type parameters are indeed part of a method's signature, however after erasure both signatures will be identical because they have no arguments that use the type parameters. Furthermore, the following does not compile:



The only difference being the return type, which is something that is not part of a method's signature. Further investigation reveals that so long as the return type is different it compiles, if it's the same it does not and is considered a duplicate.

I'm sure one of these has to be a bug. Either both should compile or both shouldn't, the return type seems irrelevent as it's not part of the method signature. The reason I don't know which is because the relevent section of the JLS seems somewhat ambiguous.


8.4.2 Method Signature
It is a compile-time error to declare two methods with override-equivalent signatures (defined below) in a class.

Two methods have the same signature if they have the same name and argument types.

Two method or constructor declarations M and N have the same argument types if all of the following conditions hold:

* They have the same number of formal parameters (possibly zero)
* They have the same number of type parameters (possibly zero)
* Let <A1,...,An> be the formal type parameters of M and let <B1,...,Bn> be the formal type parameters of N. After renaming each occurrence of a Bi in N's type to Ai the bounds of corresponding type variables and the argument types of M and N are the same.

The signature of a method m1 is a subsignature of the signature of a method m2 if either

* m2 has the same signature as m1, or
* the signature of m1 is the same as the erasure of the signature of m2.

Discussion
The notion of subsignature defined here is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other.

Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.

Consider the example:

class CollectionConverter {
List toList(Collection c) {...}
}
class Overrider extends CollectionConverter{
List toList(Collection c) {...}
}

Now, assume this code was written before the introduction of genericity, and now the author of class CollectionConverter decides to generify the code, thus:

class CollectionConverter {
<T> List<T> toList(Collection<T> c) {...}
}

Without special dispensation, Overrider.toList() would no longer override CollectionConverter.toList(). Instead, the code would be illegal. This would significantly inhibit the use of genericity, since library writers would hesitate to migrate existing code.

Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.

The example:

class Point implements Move {
int x, y;
abstract void move(int dx, int dy);
void move(int dx, int dy) { x += dx; y += dy; }
}

causes a compile-time error because it declares two move methods with the same (and hence, override-equivalent) signature. This is an error even though one of the declarations is abstract.


The part that seems ambiguous is in bold. Actually, it doesn't seem so much ambiguous as it does a loophole. The bounds of the corresponding type variables will not be the same, however, the argument types will be. But why do the bounds of the corresponding type variables matter if they argument types are still the same? In other words, foo() is foo() is foo() regardless of whether or not T's upper bounds is a String or an Integer. Yet that one sentence seems to indicate that the type parameters must have the same bounds even if they're never used. If that's true, then there should be no error when the return type is identical. If that's not true and the arguments must be different then there should be an error when the return type is different.
Tony Morris
Ranch Hand

Joined: Sep 24, 2003
Posts: 1608
My initial thoughts invoke a vague recollection of two different definitions of "method signature". One was from the JLS 2e - where the method signature does not include the return type, and ther other was from the VMS - where the method signature does include the return type (and declared exceptions?).

If my recollection is correct, then I'm going to bet that somewhere in the VMS, you will find further insight. If my recollection is incorrect, ignore me


Tony Morris
Java Q&A (FAQ, Trivia)
Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
What is the VMS?

Edit: Nevermind. Virtual Machine Specification?

Assuming that is true, I don't see how the VMS is relevent to why this won't compile.

[ April 13, 2006: Message edited by: Ken Blair ]

Further edit: From the most current VMS on Sun's website:


The signature of a method consists of the name of the method and the number and type of formal parameters (�2.10.1) of the method. A class may not declare two methods with the same signature.


So it would seem to match the JLS's definition of what consists of a signature. Either both examples should fail or both should compile. I think both should fail becuase they appear to be override-equivalent. After erasure both will be foo() (return type not withstanding because it's irrelevent to signature) and as such it should be a compile-time error.
[ April 13, 2006: Message edited by: Ken Blair ]
Paul Clapham
Bartender

Joined: Oct 14, 2005
Posts: 18541
    
    8

So the original suspicious code compiles to a class? Have you tried decompiling it to see what methods are the end result of the compilation?
Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
Originally posted by Paul Clapham:
So the original suspicious code compiles to a class? Have you tried decompiling it to see what methods are the end result of the compilation?


Yes, of course you must toss in a "return null;" but that's irrelevent. It decompiles to:



The method signature is identical in the bytecode.
Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
Apparently noone has any further comment? Am I to take from this that we're not really sure whether or not it is a bug? I submitted it to Sun but haven't heard back.
Ernest Friedman-Hill
author and iconoclast
Marshal

Joined: Jul 08, 2003
Posts: 24183
    
  34

The following compiles and runs, producing "String\n1\n" in 1.5.0_05:



I don't know if this is a bug or not. Two distinct methods are compiled.


[Jess in Action][AskingGoodQuestions]
Ilja Preuss
author
Sheriff

Joined: Jul 11, 2001
Posts: 14112
This sounds like a bug to me - and a quite interesting.

Ernest, as an aside, you can write

m[0].invoke(foo, new Object[0])

in Java 5 as

m[0].invoke(foo)


The soul is dyed the color of its thoughts. Think only on those things that are in line with your principles and can bear the light of day. The content of your character is your choice. Day by day, what you do is who you become. Your integrity is your destiny - it is the light that guides your way. - Heraclitus
Jim Yingst
Wanderer
Sheriff

Joined: Jan 30, 2000
Posts: 18671
I agree with Ken's assessment. The compiler is failing to enforce the rule that "It is a compile-time error to declare two methods with override-equivalent signatures". Definitely a compiler bug, in my opinion. I note that IntelliJ correctly marks this in red and says "foo() clashes with foo(); both methods have same erasure". But if you ignore this, you can still compile & run, thanks to Sun's compiler bug.


"I'm not back." - Bill Harding, Twister
Jim Yingst
Wanderer
Sheriff

Joined: Jan 30, 2000
Posts: 18671
I also agree with Ken that the JVM spec shouldn't be relevant to the question of whether this should compile. But as Tony suggests, it may offer some insight.

[Ken]: So it would seem to match the JLS's definition of what consists of a signature.

No, the JVMS2 definition of signature matches the old JLS definition of signature, from JLS2. In JLS3 they added formal type parameters as part of the signature, and this is not reflected at all in the JVMS definition. I think this situation is like nested classes - it's mostly handled by the compiler, and the JVM doesn't need to know about it. If the compiler had correctly prevented two methods with the same erasure, we wouldn't have this problem.


When Tony says he remembers something in the JVMS about a different definition for signature, I think he may be thinking of the method descriptor. This includes parameter types and return type. Within the JVMS, this descriptor is not referred to as a signature; it's a separate concept. However it looks like javap takes this descriptor and labels it as "Signature" in its output, e.g.:

So perhaps this is where Tony got the idea that the JVM concept of signature was different? I believe this may be indicative that the JVM may be treating the return type as part of the signature - even though according to the formal JVM definition, it's not.

So, is there a bug in the JVM as well as the compiler? Has the JVM violated its rules?

JVMS2 2.10.2 says: "A class may not declare two methods with the same signature."

JVMS2 4.6 says: "No two methods in one class file may have the same name and descriptor."

These two statements are very similar, but not quite. The first does not include return type, and the second does. I believe the JVM would probably enforce the second rule (else how would it determine what methods is being called? Maybe just use the first one found?) but it is obviously not enforcing the first rule.

However note that both rules are a bit vague in that they just tell is that this "may not" happen. OK, so whose responisibility is it to prevent it? The JLS rule was more specific, in that it said that it was a compile-time error. Here though, it's not so clear. Is this something that the JVM is required to prevent? Or just something that anyone creating bytecode is supposed to prevent? The rules for verification of class files don't actually spell out that signature uniqueness is something that must be enforced as part of verification.

So, it looks like this rule is something that slipped through the cracks as far as the JVM is concerned. The engineers involved probably figured it was someone else's responsibility. Which is sort of true, but really, the JVM should probably catch this too, I think.
Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
Originally posted by Jim Yingst:
I also agree with Ken that the JVM spec shouldn't be relevant to the question of whether this should compile. But as Tony suggests, it may offer some insight.

[Ken]: So it would seem to match the JLS's definition of what consists of a signature.

No, the JVMS2 definition of signature matches the old JLS definition of signature, from JLS2. In JLS3 they added formal type parameters as part of the signature, and this is not reflected at all in the JVMS definition. I think this situation is like nested classes - it's mostly handled by the compiler, and the JVM doesn't need to know about it. If the compiler had correctly prevented two methods with the same erasure, we wouldn't have this problem.


Type parameters are erased at compile-time, the JVM never sees them.

Originally posted by Jim Yingst:
When Tony says he remembers something in the JVMS about a different definition for signature, I think he may be thinking of the method descriptor. This includes parameter types and return type. Within the JVMS, this descriptor is not referred to as a signature; it's a separate concept. However it looks like javap takes this descriptor and labels it as "Signature" in its output, e.g.:

So perhaps this is where Tony got the idea that the JVM concept of signature was different? I believe this may be indicative that the JVM may be treating the return type as part of the signature - even though according to the formal JVM definition, it's not.


Okay, but even if the JVM does allow it I don't think the JLS does and it shouldn't compile.

Originally posted by Jim Yingst:
So, is there a bug in the JVM as well as the compiler? Has the JVM violated its rules?

JVMS2 2.10.2 says: "A class may not declare two methods with the same signature."

JVMS2 4.6 says: "No two methods in one class file may have the same name and descriptor."

These two statements are very similar, but not quite. The first does not include return type, and the second does. I believe the JVM would probably enforce the second rule (else how would it determine what methods is being called? Maybe just use the first one found?) but it is obviously not enforcing the first rule.

However note that both rules are a bit vague in that they just tell is that this "may not" happen. OK, so whose responisibility is it to prevent it? The JLS rule was more specific, in that it said that it was a compile-time error. Here though, it's not so clear. Is this something that the JVM is required to prevent? Or just something that anyone creating bytecode is supposed to prevent? The rules for verification of class files don't actually spell out that signature uniqueness is something that must be enforced as part of verification.

So, it looks like this rule is something that slipped through the cracks as far as the JVM is concerned. The engineers involved probably figured it was someone else's responsibility. Which is sort of true, but really, the JVM should probably catch this too, I think.


So it might be something that is a bug in the JVM too? I only submitted one for the compiler not catching it, perhaps someone should submit one for the JVM as well if the JVMS does prohibit it.
[ April 20, 2006: Message edited by: Ken Blair ]
Ernest Friedman-Hill
author and iconoclast
Marshal

Joined: Jul 08, 2003
Posts: 24183
    
  34

Originally posted by Ken Blair:

Type parameters are erased at compile-time, the JVM never sees them.


Well, not exactly. The generic forms are the class file -- i.e., the constraints on the type parameters -- have a look at the constant pool with javap -c -verbose to find method signatures which include them.
[ April 20, 2006: Message edited by: Ernest Friedman-Hill ]
Jim Yingst
Wanderer
Sheriff

Joined: Jan 30, 2000
Posts: 18671
[Ken]: Okay, but even if the JVM does allow it I don't think the JLS does and it shouldn't compile.

Yes. I agreed with you on this point in my first post. My position on that hasn't changed. The second post, I was looking at thing from the JVM perspective. The compiler bug is still there.

[Ken]: So it might be something that is a bug in the JVM too?

Yes - though I would say the wording in the JVMS is more ambiguous, and the compiler bug is the bigger problem.
[ April 20, 2006: Message edited by: Jim Yingst ]
Ken Blair
Ranch Hand

Joined: Jul 15, 2003
Posts: 1078
Originally posted by Ernest Friedman-Hill:
Well, not exactly. The generic forms are the class file -- i.e., the constraints on the type parameters -- have a look at the constant pool with javap -c -verbose to find method signatures which include them.

[ April 20, 2006: Message edited by: Ernest Friedman-Hill ]


I see the erased types. I don't see any information on the type parameters, what they were, or if any ever even existed. What am I missing?
 
 
subject: Is this a bug?
 
Similar Threads
overriding and hiding
I just destroyed Polymorphism
Overriding rule for generics
Will there be questions on "effects of generics on overriding and overloading" on SCJP 5?
Java Generics Overriding