Source file launcher - Handling of pre-compiled classes of the source file
Jonathan Gibbons
jonathan.gibbons at oracle.com
Fri Sep 14 22:09:03 UTC 2018
Seth, this is not so much "compiler semantics" vs "launcher semantics"
... it's just "ClassLoader semantics", combined with the fact that an
empty class path defaults to the current directory.
In the source launcher, the compiler is not worried about the classes in
the current directory.
But the classes are executed in a class loader, in which the normal
semantics[1] are to check the parent class loader first, before checking
for a local definition. Using those normal semantics, any classes on the
application class path (defaulting to the current directory) are found
before those defined to the class loader. That's the problem that is
being encountered here.
-- Jon
[1]https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#loadClass(java.lang.String,boolean)
On 9/14/18 3:00 PM, seth lytle wrote:
> ah - compiler semantics vs launcher semantics, and the source code
> launcher lives in both worlds
>
> javac refuses to compile two sources for the same class
> javac will compile a source file for a class that exists on the
> classpath (and even overwrite a class on the classpath)
> java accepts two class files for the same class on the classpath
> (favors the first)
> java -jar accepts a jar containing classes that also exist on the
> classpath (favors the jar)
> java refuses to launch a source file for a class that exists on the
> classpath
>
> the source code launcher "feels" like an outlier to me, but it could
> certainly go either way
>
> and i guess the source code launcher is an outlier anyway, as it
> decouples the first toplevel class name from the file name
>
>
>
>
>
>
>
>
>
>
>
>
> On Fri, Sep 14, 2018 at 1:49 PM, Jonathan Gibbons
> <jonathan.gibbons at oracle.com <mailto:jonathan.gibbons at oracle.com>> wrote:
>
> Seth,
>
> The reason for reporting an error is to prevent the "wrong" class
> being used!
>
> -- Jon
>
>
> On 09/14/2018 10:14 AM, seth lytle wrote:
>
> this behavior of throwing an error if the class is found twice
> on the
> classpath strikes me as unusual - afaik, in all other cases,
> java is fine
> with finding multiple implementations on the classpath and
> uses the first
> found (similar to the unix path, which also allows overriding
> by providing
> an explicit path) and this is really one of the powerful
> features of java -
> allowing you to swap out one class for another seamlessly. and
> for rapid
> prototyping, "java -cp target/classes:$cp
> src/myPackage/Stuff.java" runs
> much faster than "mvn package
> -Dexec.mainClass=myPackage.Stuff", and doubly
> so for mvnDebug
>
>
>
>
>
>
>
>
>
>
> On Fri, Sep 14, 2018 at 3:33 AM, Peter Levart
> <peter.levart at gmail.com <mailto:peter.levart at gmail.com>>
> wrote:
>
> Hi Jaikiran,
>
> Forwarding to compiler-dev as the core of source file
> launcher feature is
> produced there...
>
> The check for main class is performed after compilation
> (which actually
> produces the main class name).
>
> I think it would be possible to check for all classes
> compiled from the
> source file after compilation without to much
> complication. The compilation
> produces classes and stores them into a Map<String,
> byte[]>. The keySet()
> of that map is a Set of compiled class names. Each of them
> could be tested
> via .getResource() invoked upon the application class
> loader. The error
> could even point to the URL of the conflicting class file
> that way...
>
> Regards, Peter
>
>
> On 09/14/2018 07:36 AM, Jaikiran Pai wrote:
>
> Please consider this trivial code C.java:
>
> public class C {
> public static void main(String[] args) throws Exception {
> System.out.println("main() execution started");
> }
> }
>
>
>
> ls
>
> C.java
>
> Similar to a previous discussion[1] while doing random
> testing, I ended
> up compiling C.java explicitly using javac:
>
>
> javac C.java
> ls
>
> C.java C.class
>
> and then at a later date tried to use the source file
> launcher feature
> of Java 11 (without realizing C.class was already present
> in the dir):
>
>
> java C.java
>
> This threw the error:
>
> error: class found on application class path: C
>
> Although the error isn't that clear for the reason I note
> in [2], having
> run into this before, I was aware what this meant and
> deleted the
> C.class and moved on. The important part here is that the
> source
> launcher noticed this condition and aborted even before it
> auto
> compiled(?) and launched and executed the main() of the
> program.
>
> Now consider a slight modification to that source file:
>
> public class C {
> public static void main(String[] args) throws Exception {
> System.out.println("main() execution started");
> final B b = new B();
> System.out.println("Done");
> }
>
> private static class B {
>
> }
> }
>
> Again at some point I compiled this explicitly using
> javac, so my
> directory is (among other things):
>
>
> ls
>
> C$B.class C.class C.java
>
> Then ran the source file launcher feature:
>
>
> java C.java
>
> error: class found on application class path: C
>
> As expected, ran into the same previous error. As before,
> in order to
> move on, deleted C.class:
>
>
> rm C.class
>
> but forgot to delete the nested static class that belonged
> to it. So the
> directory now contained:
>
>
> ls
>
> C$B.class C.java
>
> Now used the source launcher feature again:
>
>
> java C.java
>
> This time it failed with:
>
> main() execution started
> Exception in thread "main" java.lang.IllegalAccessError:
> failed to
> access class C$B from class C (C$B is in unnamed module of
> loader 'app';
> C is in unnamed module of loader
> com.sun.tools.javac.launcher.Main$MemoryClassLoader @1b1473ab)
> at C.main(C.java:4)
>
> The error message isn't clear to pinpoint the issue, but
> at least the
> reference to MemoryClassLoader was a hint that was enough
> for me to
> understand where to look. It did take me a few minutes to
> realize that
> C$B.class was lying around which I needed to remove too.
>
> However, IMO, the important part here is that unlike in
> the first case
> where the program itself wasn't launched and instead was
> aborted early,
> in this case the program did get executed (notice the
> System.out.println
> "main() execution started" message that got printed) and
> failed at runtime.
>
> Would it be possible to make these two behaviours
> consistent and detect
> such cases and abort early here too? Or would that add too
> much
> complexity to this feature?
>
> Finally, any thoughts on the error messages for this
> feature to make it
> a bit easier in terms of debugging (classloading) issues
> like these?
>
> [1]
> http://mail.openjdk.java.net/pipermail/jdk-dev/2018-June/001425.html
> <http://mail.openjdk.java.net/pipermail/jdk-dev/2018-June/001425.html>
> [2]
> http://mail.openjdk.java.net/pipermail/jdk-dev/2018-June/001438.html
> <http://mail.openjdk.java.net/pipermail/jdk-dev/2018-June/001438.html>
>
> -Jaikiran
>
>
>
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20180914/600fa199/attachment-0001.html>
More information about the compiler-dev
mailing list