I have here an issue that is driving me crazy - and I have the hope
someone reading this here may have an idea what is wrong. So basically I
have this code in Groovy:
G4410JavaStringProducer jsp = new G4410JavaStringProducer()
which atm gets compiled to something like this:
> ALOAD 1
> LDC 3
> AALOAD
> INVOKESTATIC groovy/bugs/test.$get$$class$groovy$bugs$G4410JavaStringProducer ()Ljava/lang/Class;
> INVOKEINTERFACE org/codehaus/groovy/runtime/callsite/CallSite.callConstructor (Ljava/lang/Object;)Ljava/lang/Object;
> INVOKESTATIC groovy/bugs/test.$get$$class$groovy$bugs$G4410JavaStringProducer ()Ljava/lang/Class;
> INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
> CHECKCAST groovy/bugs/G4410JavaStringProducer
> ASTORE 6
ugly and inefficient, but I am working on that... anyway.. the problem
is that this code makes problems. G4410JavaStringProducer is package
private class in groovy.bugs and this class is in the same package... I
veryfied that the callConstructor call works, that the castToType call
works as well... then my debugger jumps to
ClassLoader#checkPackageAccess for the class
groovy.bugs.G4410JavaStringProducer with no SecurityManager set.
And the next thing my debugger stops is is then suddenly the constructor
of IllegalAccessError with
java.lang.IllegalAccessError: tried to access class
groovy.bugs.G4410JavaStringProducer from class groovy.bugs.test
the parent stack frame for the constructor call is the stack frame of
the groovy program, so there is nothing in between.
So now... what is this error? afaik the program is allowed to access the
class. I was not able to reproduce the problem in pure Java. It is a
totally strange thing to me.
If I change the program to
def jsp = new G4410JavaStringProducer()
then the CHECKCAST and the castToType call will not in there and the
error vanishes as well... and the only thing I can say here atm is that
I am absolutely clueless as of what the problem is.
Does anyone at least have a hint what could be wrong here? I am
thankful for every idea.
bye Jochen
--
Jochen "blackdrag" Theodorou
The Groovy Project Tech Lead (http://groovy.codehaus.org)
http://blackdragsview.blogspot.com/
I can now exclude castToType from causing the problem. I modified the
bytecode generation to have no longer the castToType call and I get
still the error. If I remove the checkcast call as well, then the error
vanishes, as I already said
... still clueless
funny thing is... if I change the code to
G4410JavaStringProducer jsp = null
then the castToType call and CHECKCAST are still in, but I get no error.
This time the constructor call is missing.... wtf?
How did you try to reproduce the problem in Java? What does javac
produce for your code? I remember having a vaguely similar problem,
but it involved reflection and private inner classes, and you don't
seem to be using any of those.
Cheers,
Alessio
I did not try to insert the constructor call as of yet. I mostly tried
variants of the groovy code in Java, one time forced the CHECKCAST call,
since I thought this is the culprit. but no success.
javac produces:
NEW G4410JavaStringProducer
DUP
INVOKESPECIAL G4410JavaStringProducer.<init> ()V
ASTORE 2
You might try this Java
Object o = new G4410JavaStringProducer();
G4410JavaStringProducer jsp = (G4410JavaStringProducer)o;
John Wilson
I tried it now with this Java code:
> CallSite[] csa = (new CallSiteArray(X.class,new String[1])).array;
> Object tmp = csa[0].callConstructor(G4410JavaStringProducer.class);
> tmp = ScriptBytecodeAdapter.castToType(tmp, G4410JavaStringProducer.class);
> G4410JavaStringProducer jsp = (G4410JavaStringProducer) tmp;
which is roughly what Groovy produces transfered to Java and this works
without problem. The generated bytecode for this is:
> ALOAD 2
> ICONST_0
> AALOAD
> LDC Lgroovy/bugs/G4410JavaStringProducer;.class
> INVOKEINTERFACE org/codehaus/groovy/runtime/callsite/CallSite.callConstructor (Ljava/lang/Object;)Ljava/lang/Object;
> ASTORE 3
> ALOAD 3
> LDC Lgroovy/bugs/G4410JavaStringProducer;.class
> INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
> ASTORE 3
> ALOAD 3
> CHECKCAST groovy/bugs/G4410JavaStringProducer
> ASTORE 4
for a direct compare here again the code from groovy:
> ALOAD 1
> LDC 3
> AALOAD
> INVOKESTATIC groovy/bugs/test.$get$$class$groovy$bugs$G4410JavaStringProducer ()Ljava/lang/Class;
> INVOKEINTERFACE org/codehaus/groovy/runtime/callsite/CallSite.callConstructor (Ljava/lang/Object;)Ljava/lang/Object;
> ASTORE 5
> ALOAD 5
> INVOKESTATIC groovy/bugs/test.$get$$class$groovy$bugs$G4410JavaStringProducer ()Ljava/lang/Class;
> INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
> CHECKCAST groovy/bugs/G4410JavaStringProducer
> ASTORE 6
apart from some obvious differences in the register numbers, one time
storing the result, the other time not the biggest difference is the
call to
groovy/bugs/test.$get$$class$groovy$bugs$G4410JavaStringProducer()
instead of the LDC, which is because groovy does here atm no generate
java5 bytecode.
oh, and the Java version has class name X, in Groovy it is test ;)
I tried it already before my first post, no problem with that.
John Wilson
just tried the ibm jdk6_60 and it shows the same problem, with a bit
less informative error message.
INVOKESTATIC
groovy/bugs/test.$get$$class$groovy$bugs$G4410JavaStringProducer
()Ljava/lang/Class;
can trigger the loading of class groovy/bugs/test.
So the error can perhaps come from the static init of groovy/bugs/test
R�mi
maybe it got a bit messie due to my many experiments, but the class
groovy.bugs.test is the class executing the failing code. So this cannot
be. And even if it were there, then it would show up in the stack trace,
which it does not. Also the here called method is loading the class
groovy.bugs.G4410JavaStringProducer, to whom the access fails, since it
is package private... Only that the access should normally be ok, and
that the error is not risen in that method or directly after it.
Just to rule this out: are you sure the two classes (test and
G4410JavaStringProducer) are loaded by the same classloader?
If I execute that from eclipse, then it is not the same loader. In case
of the junit test I think it is. But in case of eclipse the loader for
test is a child of the loader for the G4410JavaStringProducer class
bye blackdrag
Hmm. I doubt them being in a parent-child relationship changes
anything wrt determining whether the two classes are in the same
package. The JVM specification says [1]:
"A class or interface C is accessible to a class or interface D if and
only if either of the following conditions are true:
* C is public.
* C and D are members of the same runtime package (§5.3)."
And "runtime package" is so defined [2]:
"The runtime package of a class or interface is determined by the
package name and defining class loader of the class or interface".
I interpret that to mean that two classes at runtime are considered to
be in the same package if and only if they have the same package name
and they've been loaded by the very same classloader.
So, that might be the problem. Maybe with JUnit you get two classloaders too.
Cheers,
Alessio
[1] http://java.sun.com/docs/books/jvms/second_edition/html/ConstantPool.doc.html#75929
[2] http://java.sun.com/docs/books/jvms/second_edition/html/ConstantPool.doc.html#72007
ok, but shouldn't the error then happen whenever I have a CHECKCAST,
because the class loading for this does not work? But I already produced
a variant containing he CHECKCAST and not giving the IllegalAccessError.
bye Jochen
I'm not an expert about that. Generally linking is done very lazily by
the JVM, so it might not occur if you CHECKCAST a null pointer. When
instead you have a live object, a CHECKCAST might count as an access
to the referenced class and trigger the illegal access error. I'm
guessing, mind you; let's see if someone who knows the JVM better has
something different to say.
Cheers,
Alessio
but why is the access illegal? The class might be package private, but
the calling class is in the same package and thus allowed to access that
class. If not the Java version of it would not even compile.
As I said at runtime "same package" means "same package name" AND
"same classloader" or at least this is my interpretation of the jvm
spec, so if you have two different classloaders loading your two
classes like you said, they're not considered to be in the same
package. In the Java version evidently there's only one classloader
involved.
Alessio
In fact, I think I managed to reproduce it. I have this structure:
<default package>/
Main2.java
pkg/
A.java
B.java
Main.java has the following main method:
------------------------
public static void main(String[] args) {
try {
ClassLoader a = new URLClassLoader(new URL[] { new
URL("file:///home/alessio/prova1/") });
System.out.println("a = " + a);
a.loadClass("pkg.A");
ClassLoader b = new URLClassLoader(new URL[] { new
URL("file:///home/alessio/prova2/") }, a);
System.out.println("b = " + b);
b.loadClass("pkg.B").getMethod("foo").invoke(null);
} catch(Exception e) {
e.printStackTrace();
}
}
--------------------------
A.java is
------------------
package pkg;
class A {
static { System.out.println("A loaded by " + A.class.getClassLoader()); }
public String aaa() { return "foo"; }
}
----------------
B.java is
------------------
package pkg;
public class B {
static { System.out.println("B loaded by " + B.class.getClassLoader()); }
public static void foo() {
Object o = new A();
System.out.println(((A) o).aaa());
}
}
------------------
I compile them, then move A.class to ~/prova1/pkg/ and B.class in
~/prova2/pkg/ and then I issue the command:
alessio@astalla-laptop:~$ java -cp workspace/prova/bin Main2
Main2 loaded by sun.misc.Launcher$AppClassLoader@13f5d07
a = java.net.URLClassLoader@1eed786
b = java.net.URLClassLoader@12dacd1
B loaded by java.net.URLClassLoader@12dacd1
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at Main2.main(Main2.java:13)
Caused by: java.lang.IllegalAccessError: tried to access class pkg.A
from class pkg.B
at pkg.B.foo(B.java:10)
... 5 more
Granted, this is not caused by a CHECKCAST, but the principle is the same.
Bye,
Alessio
in the meantime I did something similar... and got the same result... so
it seems the issue is related to that. Looks like not having package
private in Groovy by default was an even better decision than I always
thought of.
thank you very much... it makes much more sense to me now.
Especially as I could verify that indeed two class loaders are involved
in the junit test case. So it seems that is the background to this
error. Thanks a lot.
No problem. It has been a nice issue to think about :)
In general, I have found that some of the abstractions introduced by
javac leak or fall apart in certain corner cases, that are a lot more
common when dynamic languages are involved.
Peace,
Alessio