Bug 128563 - [compiler] Inner class compiles but IllegalAccessError if splitted with two output folders
Summary: [compiler] Inner class compiles but IllegalAccessError if splitted with two o...
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.2   Edit
Hardware: PC Windows XP
: P3 major (vote)
Target Milestone: 3.5 M3   Edit
Assignee: Philipe Mulet CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 158606
  Show dependency tree
 
Reported: 2006-02-19 19:02 EST by Thierry Herrmann CLA
Modified: 2008-10-28 13:04 EDT (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Thierry Herrmann CLA 2006-02-19 19:02:37 EST
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
Comment 1 Olivier Thomann CLA 2006-03-09 10:47:53 EST
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.
Comment 2 Thierry Herrmann CLA 2006-03-09 11:27:08 EST
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.
Comment 3 Markus Keller CLA 2006-09-25 13:06:41 EDT
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.
Comment 4 Philipe Mulet CLA 2008-09-15 07:02:49 EDT
Reproduced with Sun jdk1.6.0_10-beta.
Comment 5 Philipe Mulet CLA 2008-09-15 07:10:05 EDT
Reproduced with IBM jre 1.6.0_07-b06
Comment 6 Philipe Mulet CLA 2008-09-15 09:56:30 EDT
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.
Comment 7 Philipe Mulet CLA 2008-09-17 05:21:29 EDT
Added regression test: InnerEmulationTest#test156
Comment 8 Philipe Mulet CLA 2008-09-17 05:29:51 EDT
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)
Comment 9 Philipe Mulet CLA 2008-09-17 05:30:19 EDT
Released for 3.5M3.
Fixed
Comment 10 Philipe Mulet CLA 2008-09-19 06:32:58 EDT
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();
    }
}
Comment 11 Philipe Mulet CLA 2008-09-19 06:33:26 EDT
Added regression test: InnerEmulationTest#test157
Comment 12 Philipe Mulet CLA 2008-09-23 08:04:32 EDT
Also added: InnerEmulationTest#test158-160
Comment 13 Philipe Mulet CLA 2008-09-30 09:31:03 EDT
Tests InnerEmulationTest#test157-160 are actually attached to bug 249107 where comment 10 got forked into
Comment 14 Philipe Mulet CLA 2008-09-30 12:44:08 EDT
Also added InnerEmulationTest#test165
Comment 15 Kent Johnson CLA 2008-10-28 13:04:43 EDT
Verified for 3.5M3 using I20081026-2000 build.