I have an issue with a legacy code base. I want to start compiling it with 1.6 class format, but there is one problem which reveals itself only when I try to run the compiled code. I get the following exception:
java.lang.ClassFormatError: Illegal class modifiers in class FooBar 0x209
Googling this doesn't reveal a lot of details. According to this the issue might be related to mismatch between interface and implementation modifiers. And of course, it must be some new restriction which wasn't in 1.5.
The problematic is class huge and has a lot of inner classes and inner inner classes so the problem is hard to track down (it's one of the inner classes I'm sure). Needless to say, the class doesn't have any tests either so changing it requires extreme caution and is laborous.
So, does anyone have any exact information about 0x209 - what does the code mean specifically?
EDIT:
Thanks to Arne bumping us into the right direction we were able to track down the issue and make a workaround. The root cause is not quite clear but now we can avoid it.
We are using Doug Lea's ancient util.concurrent package in certain areas because some components are running in environments which only provide Java 1.1 (yes, it 's quite alright to laugh, I don't mind).
This same code (using the concurrent utils) is also used as a small component of another related software. Since Doug's code used some 1.2 features, we were also forced to modify certain parts of util.concurrent to make it 1.1 compatible with Sun's 1.1 backported collections package (can't find the link to those any more). Somehow it has caused this peculiar Eclipse compilation behavior which occurs when we change the class format to Java 1.6. This is the minimum code which causes the problem:
EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import com.sun.java.util.collections.Map;
public class FooBar
{
public static void main(String[] args) {
Map.Entry e = (Map.Entry)(new ConcurrentHashMap().entrySet().iterator().next());
}
}
After you compile this with Eclipse (with compilation set to 1.6, 1.5 works fine) and try to load the class from Sun's 1.6 JRE the problem occurs. The workaround: instead of looping through entries, we loop through keys and get values inside the loops with the keys.
Our setup here is so exotic that no wonder nobody else has bumped into this. I finally checked our build scripts and lo and behold, the ant-script has 1.6 source and target-settings. So apparently this is Eclipse-specific.
EDIT2:
I Looked closer to the Sun bug report I have linked here. The problem there is also related to com.sun.java.util.collections.Map.Entry. And that occurred with Sun's Javac. Interesting.
The code isn't a code but the internal representation of the class modifiers in the byte code of your class file.
There are eight different flags that could be set.
ACC_PUBLIC (0x0001)
ACC_FINAL (0x0010)
ACC_SUPER (0x0020)
ACC_INTERFACE (0x0200)
ACC_ABSTRACT (0x0400)
ACC_SYNTHETIC (0x1000)
ACC_ANNOTATION (0x2000)
ACC_ENUM (0x4000)
For example a public final class would have the modifier bytes 0x0011, an public abstract class 0x0401.
Your case 0x209 (or better: 0x0209) is an illegal modfier. It looks like an interface (0x0200), but the 0x0009 part isn't part of the specification. I would guess it's a compiler bug.
Maybe the following code helps to isolate the problem. It reads the class modifiers from a class file and verifies if the modifier is ok. If it is not it prints the filename and INVALID!!! Maybe you can use this tool on your class files to isolate the class that causes the bug.
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
String path = "D:/Arne/workspaces/IDEDeluxe/TestBytecode/bin/";
String[] fileNames = { "Main.class" };
for(String fileName : fileNames)
traceFile(path, fileName);
}
private static void traceFile(String path, String fileName) throws FileNotFoundException, IOException {
DataInputStream stream = new DataInputStream(new BufferedInputStream(new FileInputStream(path + fileName)));
trace(fileName, readClassAccessFlags(stream));
stream.close();
}
private static int readClassAccessFlags(DataInputStream stream) throws IOException {
skipHeader(stream);
skipConstantPool(stream);
return stream.readUnsignedShort();
}
private static void skipHeader(DataInputStream stream) throws IOException {
stream.readInt();
stream.readUnsignedShort();
stream.readUnsignedShort();
}
private static void skipConstantPool(DataInputStream stream) throws IOException {
int constantPoolCount = stream.readUnsignedShort();
for(int n = 1; n < constantPoolCount; n++) {
int tag = stream.readUnsignedByte();
switch(tag) {
case 7:
stream.readUnsignedShort();
break;
case 9:
case 10:
case 11:
stream.readUnsignedShort();
stream.readUnsignedShort();
break;
case 8:
stream.readUnsignedShort();
break;
case 3:
case 4:
stream.readInt();
break;
case 5:
case 6:
stream.readInt();
stream.readInt();
break;
case 12:
stream.readUnsignedShort();
stream.readUnsignedShort();
break;
case 1:
stream.readUTF();
break;
}
}
}
private static void trace(String fileName, int flags) {
System.out.print(fileName + ": " + Integer.toHexString(flags) + " - ");
if((flags & 0x0001) != 0)
flags -= 0x0001;
if((flags & 0x0010) != 0)
flags -= 0x0010;
if((flags & 0x0020) != 0)
flags -= 0x0020;
if((flags & 0x0200) != 0)
flags -= 0x0200;
if((flags & 0x0400) != 0)
flags -= 0x0400;
if((flags & 0x1000) != 0)
flags -= 0x1000;
if((flags & 0x2000) != 0)
flags -= 0x2000;
if((flags & 0x4000) != 0)
flags -= 0x4000;
if(flags == 0)
System.out.println("OK!");
else
System.out.println("INVALID!!!");
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With