Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IllegalAccessError while working on inheritance - Why?

I found this JDK bug and want to understand why it happens.

The scenario (taken from the bug report) is very simple: a class declaring a private method, and an interface declaring a public method with the same signature. It compiles without error.

However, when I run this code I am getting IllegalAccessError

interface I {
    public void m();
}

class A {
    private void m() {
        System.out.println("Inside Class A");
    }

}

abstract class B extends A implements I {
}

class C extends B {
    public void m() {
        System.out.println("Inside Class C");
    }
}

public class Test {
    public static void main(String... args) {
        B b = new C();
        b.m();
    }
}

Please help me understand why this error is there as my code is compiling fine.

Exception in thread "main" java.lang.IllegalAccessError:  
tried to access method A.m()V from class Test
    at Test.main(Test.java:25)
like image 785
Sachin Sachdeva Avatar asked Sep 27 '17 09:09

Sachin Sachdeva


People also ask

What is IllegalAccessError?

java.lang.IllegalAccessError. Thrown if an application attempts to access or modify a field, or to call a method that it does not have access to. Normally, this error is caught by the compiler; this error can only occur at run time if the definition of a class has incompatibly changed.

How do you fix No class Def Found error?

lang. NoClassDefFoundError, which means the Class Loader file responsible for dynamically loading classes can not find the . class file. So to remove this error, you should set your classpath to the location where your Class Loader is present.


3 Answers

It compiles as everything seems fine.

However b.m() is translated as searching the signature m(), in B, evidently first in A and (intended) later in the interfaces. In A a private m() is found and bang.

Inconsistent language behaviour, and theoretically avoidable by the compiler.


Reworded

During compilation the public interface method is found - fine. During runtime the (modifierless) signature is found in A where the method is private, never reaching the signature in the interface where the method is public.


[FYI] Disassembly with javap

invokevirtual method .../.../B.m:()V

Of course on a C object.

like image 60
Joop Eggen Avatar answered Oct 07 '22 17:10

Joop Eggen


This is a known issue and currently tracked here:

JDK-8021581 Private class methods interfere with invocations of interface methods

This ticket does contain a detailed analysis of the issue, discussion of compatibility concerns and risks of proposed solutions and is still open.

Older discussions of the topic can be found here:

JDK-6684387 IllegalAccessError for code passed by compiler
(that one was linked by shmosel in his comment - thanks for that)

JDK-6691741 JLS membership algorithm is too strong for JVMS method resolution


like image 30
7 revs, 2 users 90% Avatar answered Oct 07 '22 17:10

7 revs, 2 users 90%


It compiles because class B is an abstract class that declares it implements interface I - it assumes that the implementation will have the required method.

The type of object b is declared as B at compile time. You can see that it is B and not C if you play a bit as in the examples below:

To make it simple with an example, if you have a new method in class c

class C extends B {
    public void m() {
        System.out.println("C.m");
    }

    public void testFromC() {}
}

then trying to call this in your main, will not compile.

public static void main(String[] args) {
    B b = new C();
    b.testFromC(); // doesnt compile
}

While if you add a method in B, then it will be fine.

abstract class B extends A implements I {
    public void testFromB() { }
}

public static void main(String[] args) {
    B b = new C();
    b.testFromB(); // compiles
}

When it runs the program, treating as object of class B, it founds the implementation from A which is private. If you force b to be viewed as of type C by casting, it will work.

public static void main(String[] args) {
    B b = new C();
    ((C)b).testFromC();
}

Also, if you remove the private implementation of A for m it also would work without the casting.

class A {
    //private void m() {
    //System.out.println("A.m");
    //}
}

This works now:

public static void main(String[] args) {
    B b = new C();
    b.m();
}

So as I understand it right now, it looks like at runtime first it checks for method m on B or its parent, and if it finds nothing, it goes to implementation of B which is of class C.

like image 29
martidis Avatar answered Oct 07 '22 15:10

martidis