Community
Participate
Working Groups
Let's say we have the following project: src1/package1/A.java B.java src2/package2/C.java The output path of src1 is classes1 The output path of src2 is classes2 Therefore when compiling, Eclipse generates the following classes: ./classes1/package1/A.class B.class ./classes2/package2/C$MyInner.class C.class Here are the A, B, C classes: --------------------------------- package package1; abstract class A { protected final void outerMethod() { } } --------------------------------- package package1; public class B extends A { } --------------------------------- package package2; import package1.B; public class C extends B { private final MyInner myInner = new MyInner(); private class MyInner { public void innerMethod() { C.this.outerMethod(); } } public static void main(String[] args) { final C c = new C(); c.myInner.innerMethod(); } } --------------------------------- The problem is: although it compiles successfully, the output of the program is: java.lang.IllegalAccessError: tried to access class package1.A from class package2.C at package2.C.access$0(C.java:1) at package2.C$MyInner.innerMethod(C.java:13) at package2.C.main(C.java:22) Still with the Eclipse compiler, if classes A, B, C are in the same packages (package1 and package2) BUT in the SAME source folder (so the classes are generated in the same output folder), the code successfully executes. With Sun's javac, whether one or two source/output folders, no IllegalAccessError is thrown. -------------- Windows XP (no service pack) Eclipse 3.2M5 running with Java 1.5.0 update 6 JRE used: 1.4.2_08
The difference between our behavior and javac is that the access method on C doesn't use the same invocation for the outerMethod() call. They do the call using C as the declaring class and we do it using A. I found something quite interesting. If you move the classes from the two output folders into the same directory and you run from there, it works fine. We generate exactly the same bytecodes in both cases. The VM should behave the same. Might be a VM bug. Changing the class A to be public is fixing the issue as well. Our access method looks like this: // Method descriptor #33 (Lpackage2/C;)V // Stack: 1, Locals: 1 static synthetic void access$0(package2.C arg0); 0 aload_0 1 invokevirtual package1.A.foo() : void [35] 4 return Line numbers: [pc: 0, line: 1] javac access method looks like that: // Method descriptor #23 (Lpackage2/C;)V // Stack: 1, Locals: 1 static synthetic void access$100(package2.C arg0); 0 aload_0 1 invokevirtual package2.C.foo() : void [1] 4 return Line numbers: [pc: 0, line: 5] Note the different declaring class. Changing this should be enough to fix this issue.
Nice work Olivier! Yes, I also found out that moving the classes in the same package solves the problem! Interesting indeed. I also found that making A public solves the problem. This is how I'm temporarily working with Eclipse. In our project, I've privately checked-out several classes as a workaround to this problem. But I can't work that way in the long term since everyone don't use Eclipse in my team and my co-workers are reluctant to make A public since this would violate encapsulation and since having A package private is perfectly valid. When you write: > Note the different declaring class. Changing this should be enough to fix this > issue. does it mean that the Eclipse team will change the generated bytecode to use C as the declaring class, as does javac ? Overall, I think IllegalAccessError shoudn't occur if the compiler said it is OK. I think they should occur only when trying to violate access rules with reflection or if the bytecode was changed or if some code hasn't been recompiled...etc... Thanks again for your fantastic work.
We now have the same problem in HEAD of eclipse with the following mapping: package1.A => org.eclipse.jface.viewers.BaseLabelProvider package1.B => org.eclipse.jface.viewers.LabelProvider package2.C => org.eclipse.jdt.ui.examples.JavaElementLightweightDecorator (in org.eclipse.jdt.ui.tests plug-in) As I see it, this is a VM bug, since JLS3 6.6.2.1 explicitly allows this situation.
Reproduced with Sun jdk1.6.0_10-beta.
Reproduced with IBM jre 1.6.0_07-b06
I believe this is our bug, we should not reference a non accessible type in our bytecode. The fact it works at times feel like unspecified behavior from the VM.
Added regression test: InnerEmulationTest#test156
Fix got integrated with fix for bug 247292 (prereq). Once method and declaringClass can be disconnected, the fix is to use the access method declaring class instead of the target method declaring class. See CodeStream#generateSyntheticMethodAccess(...) invoke(Opcodes.OPC_invokevirtual, targetMethod, accessMethod.declaringClass); // target method declaring class may not be accessible (128563)
Released for 3.5M3. Fixed
Note that similar problem also exists with field access, the fix for this variation will be integrated into fix for bug 247612 (which provides the necessary infrastructure for an easy fix). e.g. package1/A.java [ package package1; abstract class A { protected int outerField; { } } ] package1/B.java [ package package1; public class B extends A { } ] package2/C.java [ package package2; import package1.B; public class C extends B { private final MyInner myInner = new MyInner(); private class MyInner { public void innerMethod() { int j = C.this.outerField; } } public static void main(String[] args) { final C c = new C(); c.myInner.innerMethod(); } }
Added regression test: InnerEmulationTest#test157
Also added: InnerEmulationTest#test158-160
Tests InnerEmulationTest#test157-160 are actually attached to bug 249107 where comment 10 got forked into
Also added InnerEmulationTest#test165
Verified for 3.5M3 using I20081026-2000 build.