Community
Participate
Working Groups
I tried to reduce this to a simple reproducible example but I was unable to do so because the same source file would sometimes compile and work correctly and sometimes compile incorrectly and I couldn't determine what conditions caused it. When I tried to simplify my problem code I couldn't reproduce the problem. Hopefully you'll be able to do something with what I could submit. First, the problem relates to the code generated for an Enum that implements an interface one method having a generic return type and implementing the method in each enum value instead of one implementing method for the whole enum class. In this code, there is a class called "Shipment". It has an field called workflowType whose type is an enum class called WorkflowType. The enum class implements an interface ValuedEnum<String>. The interface has a method called "getInternalValue()" whose return type is the generic type used for the interface, in this case String. There is a class called TestEnumImplementGenerics which instantiates the Shipment class, sets the workflowType to the Buyer enum value, invokes the getInternalValue() method and stores the result in a String (forcing the cast from Object). When I first ran the TestEnumImplementGenerics class, I got the following error: Value 1 Exception in thread "main" java.lang.AbstractMethodError: com.aex.repair.ship.data.Shipment$WorkflowType.getInternalValue()Ljava/lang/Object; at com.aex.repair.ship.data.TestEnumImplementGenerics.main(TestEnumImplementGenerics.java:25) I went in the Shipment class and added "public abstract String getInternalValue();" to the WorkflowType enum. When I ran the test, the output was: Value 1 BUYER I then commented out the line I just added. I still received the same output: Value 1 BUYER I then cleaned the project (no code changes) - just a recompilation. Now I get the error again: Value 1 Exception in thread "main" java.lang.AbstractMethodError: com.aex.repair.ship.data.Shipment$WorkflowType.getInternalValue()Ljava/lang/Object; at com.aex.repair.ship.data.TestEnumImplementGenerics.main(TestEnumImplementGenerics.java:25) This may also be a problem with the incremental compiler. I'm took a copy of the java and class files when it worked and when it failed. I'll attach those. Maybe someone can look at the results and figure out how to reproduce it and what's going wrong.
Created attachment 19521 [details] Contains the working and failing versions of the java and class files This tar/gz file contains a directory called "generics". Inside is a directory called "fails" and one called "passes". The only difference between them is the generated class files for one of the enum types. bash-2.05b$ diff -r /tmp/generics/fails /tmp/generics/passes Files /tmp/generics/fails/com/aex/repair/ship/data/Shipment$1.class and /tmp/generics/passes/com/aex/repair/ship/data/Shipment$1.class differ Files /tmp/generics/fails/com/aex/repair/ship/data/Shipment$2.class and /tmp/generics/passes/com/aex/repair/ship/data/Shipment$2.class differ
What build are you using?
Sorry. 3.1M6
Ok, I finally have a small test case which illustrates the problem on my machine. I'm using 3.1M6 on Gentoo Linux. 1. First you need to create two classes ClassWithBadEnum.java and Placeholder.java in package "bad": ClassWithBadEnum.java: package bad; public class ClassWithBadEnum { public interface EnumInterface<T extends Object> { public T getMethod(); } public enum EnumClass implements EnumInterface<String> { ENUM1 { public String getMethod() { return "ENUM1";} }, ENUM2 { public String getMethod() { return "ENUM2";} }; } private EnumClass enumVar; public EnumClass getEnumVar() { return enumVar; } public void setEnumVar(EnumClass enumVar) { this.enumVar = enumVar; } public static void main(String... argv) { int a = 1; ClassWithBadEnum badEnum = new ClassWithBadEnum(); badEnum.setEnumVar(ClassWithBadEnum.EnumClass.ENUM1); // Should fail if bug manifests itself because there will be two getInternalValue() methods // one returning an Object instead of a String System.out.println("Should fail with a java.lang.AbstractMethodError if the bug manifests itself"); String s3 = badEnum.getEnumVar().getMethod(); System.out.println("Didn't fail"); System.out.println(s3); } } Placeholder.java: package bad; public class Placeholder { public static void main(String... argv) { ClassWithBadEnum badEnum = new ClassWithBadEnum(); badEnum.setEnumVar(ClassWithBadEnum.EnumClass.ENUM1); } } Even though the Placeholder class doesn't do anything, I couldn't get the bug to manifest itself without having another class referencing the problem class. 2. Run class ClassWithBadEnum and get the following output: Should fail with a java.lang.AbstractMethodError if the bug manifests itself Didn't fail ENUM1 3. Clean project using Project>Clean... 4. Run class ClassWithBadEnum again with the following output: Should fail with a java.lang.AbstractMethodError if the bug manifests itself Exception in thread "main" java.lang.AbstractMethodError: bad.ClassWithBadEnum$EnumClass.getMethod()Ljava/lang/Object; at bad.ClassWithBadEnum.main(ClassWithBadEnum.java:26) 5. Make any change (such as changing the value of variable "a" from 1 to 2 in ClassWithBadEnum). 6. Run class ClassWithBadEnum and get the following output: Should fail with a java.lang.AbstractMethodError if the bug manifests itself Didn't fail ENUM1
I'm getting the same error running Eclipse 3.1M6 on Windows XP Pro testing the case described by Jim: the bytecode generated by cleaning the project fails whereas the incrementally generated class file does not.
Reproduced
Problem is that in clean build scenario, first enum constant ENUM1 generated class: ClassWithBadEnum$1 is missing definition for method: #getMethod(); hence the AbstractMethodError.
Actually problem is that an offending extra bridge method got inserted on class for ENUM1.
GOOD // Method descriptor #18 ()Ljava/lang/String; // Stack: 1, Locals: 1 public String getMethod(); 0 ldc <String "ENUM1"> [20] 2 areturn Line numbers: [pc: 0, line: 9] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Lp/ClassWithBadEnum$1; BAD // Method descriptor #21 ()Ljava/lang/Object; // Stack: 1, Locals: 1 public bridge synthetic Object getMethod(); 0 aload_0 [local_0] 1 invokevirtual p/ClassWithBadEnum$1.getMethod()Ljava/lang/String; [23] 4 areturn Line numbers: [pc: 0, line: 1]
Ignore comments from comment#7 to comment#9. Problem is that somehow the bridge methods did not get generated in clean build situation, but fine incrementally. Also trying to recreate the problem in batch mode did not reproduce it either.
Issue seems located in method verifier. Put a breakpoint in MethodVerifier#checkMethods() When recreating (in runtime workbench, clean build), first hit of the breakpoint is during remote resolution of ENUM1 field (when accessed from Placeholder). void checkMethods() { boolean mustImplementAbstractMethods = mustImplementAbstractMethods(); boolean skipInheritedMethods = mustImplementAbstractMethods && canSkipInheritedMethods(); // have a single concrete superclass so only check overridden methods mustImplementAbstractMethods == false skipInheritedMethods == false Then later on, we hit the breakpoint again when processing the same local type; however this time around mustImplementAbstractMethods == false skipInheritedMethods == true !?!? Q1. why do we verify it twice ? Q2. why different setup then ?
Before debugging, close all editors in runtime workspace to avoid confusion. Actual data is then: - hit on #checkMethods() for ENUM1 occurs through Placeholder forward ref. Then: mustImplementAbstractMethods == true skipInheritedMethods == true Then later on, we hit the breakpoint again when processing ENUM2 in context of its declaring class (no reprocessing of ENUM1). However then: mustImplementAbstractMethods == true skipInheritedMethods == false ?!? This is the scenario in which then ENUM2 gets a bridge method, whereas ENUM1 did not get any. Suspecting it has to do with abstractness of superclass EnumClass
Indeed, in between the 2 hits, we enter the following code which toggles abstractness of enum superclass. boolean mustImplementAbstractMethod(ReferenceBinding declaringClass) { if (!this.type.isEnum()) return super.mustImplementAbstractMethod(declaringClass); if (this.type.isAnonymousType()) return true; // body of enum constant must implement any inherited abstract methods if (this.type.isAbstract()) return false; // is an enum that has since been tagged as abstract by the code below // enum type needs to implement abstract methods if one of its constants does not supply a body TypeDeclaration typeDeclaration = this.type.scope.referenceContext; FieldDeclaration[] fields = typeDeclaration.fields; int length = typeDeclaration.fields == null ? 0 : typeDeclaration.fields.length; if (length == 0) return true; // has no constants so must implement the method itself for (int i = 0; i < length; i++) { FieldDeclaration fieldDecl = fields[i]; if (fieldDecl.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT) if (!(fieldDecl.initialization instanceof QualifiedAllocationExpression)) return true; } // tag this enum as abstract since an abstract method must be implemented AND all enum constants define an anonymous body // as a result, each of its anonymous constants will see it as abstract and must implement each inherited abstract method this.type.modifiers |= IConstants.AccAbstract; return false; }
I have a fix. I moved the abstract bit setting to ClassScope#checkAndSetModifiers, where it should have lived initially. Suspect a proper testcase needs to written as a builder test, since ClassWithBadEnum needs to be requested as source, and not initially on the command line as when using batch test.
Added EnumTest#test104. Important point is to have a forward reference from Placeholder to ENUM1. Fixed
Verified in i20050527-0010