On 25/05/2023 7:21 pm, Raffaello Giulietti wrote:

Yes, ChildClass.class is removed from the filesystem.


And here's, the relevant info when running with -Xlog:class+init, showing that verification succeeds for both TestLoading$ObjectReturner and TestLoading$BaseClassReturner:

loading: TestLoading$ObjectReturner...
[0.039s][info][class,init] Start class verification for: TestLoading$ObjectReturner [0.039s][info][class,init] End class verification for: TestLoading$ObjectReturner [0.039s][info][class,init] 500 Initializing 'TestLoading$ObjectReturner'(no method) (0x0000000801001800)
loading: TestLoading$BaseClassReturner...
[0.039s][info][class,init] Start class verification for: TestLoading$BaseClassReturner [0.039s][info][class,init] End class verification for: TestLoading$BaseClassReturner [0.039s][info][class,init] 501 Initializing 'TestLoading$BaseClassReturner'(no method) (0x0000000801001a08)

Can you enable -xlog:verification and class+load too please.

Thanks,
David
-----




On 2023-05-25 04:57, David Holmes wrote:
On 25/05/2023 12:34 am, Raffaello Giulietti wrote:
As mentioned in my previous email, if you move the member class ChildClass out of TestLoading (out of the nest), and make it a top-level class like

     public class ChildClass extends TestLoading.BaseClass {
     }

and change

     URL classFileB = TestLoading.class.getResource(TestLoading.class.getSimpleName() + "$ChildClass.class");

to

     URL classFileB = TestLoading.class.getResource(ChildClass.class.getSimpleName() + ".class");

rebuild everything and run, nothing is thrown.

     deleting: <path-to>/ChildClass.class
     loading: TestLoading$ObjectReturner...
     loading: TestLoading$BaseClassReturner...

I can't see any substantial difference, except that the nest rooted at TestLoading lacks a member in the original setup and lacks nothing in this setup.

What's an explanation for this difference?

Are you sure it actually deletes the file? What do you see when you enable class+load/init and verification logging?

David
-----




Greetings
Raffaello


On 2023-05-24 00:35, Remi Forax wrote:


----- Original Message -----
From: "David Holmes" <david.hol...@oracle.com>
To: "Raffaello Giulietti" <raffaello.giulie...@oracle.com>, "core-libs-dev" <core-libs-dev@openjdk.org>
Sent: Wednesday, May 24, 2023 12:23:24 AM
Subject: Re: Classes used in method body are loaded lazily or eagerly depending on method return type

On 24/05/2023 12:50 am, Raffaello Giulietti wrote:
I think the problem here is that you are deleting a class in a nest.

During the verification of BaseClassReturner.getObject(), access control
(JVMS, 5.4.4, second half) determines that the nest is broken, as
ChildClass is not present anymore.

Not sure access control gets involved at this stage of the verification
process. But in any case turning on logging does not show anything
related to nestmates happening between BaseClass and ChildClass. It
seems to just be the resolution of the return type during verification
of the method, that causes the loading of ChildClass and the subsequent
CNFE if it has been removed.

If you move ChildClass out of TestLoading so that it becomes a top-level
class extending TestLoading.BaseClass, and if you adapt the line that
initializes the local var classFileB to refer to the new location, the
code will not throw, despite ChildClass being deleted.

My simplified test shows it still throws when verifying BaseClassReturner.

Nestmate checking is done lazily, so if you do not call a method/access a field of a nested class, the VM should not trigger a class loading.

Moreover, if you test with Java 8 (nestmates were introduced in Java 11), it failed too. That's another clue that the error is not related to nestmates checking.



Cheers,
David

regards,
Rémi



Greetings
Raffaello



On 2023-05-23 13:20, Сергей Цыпанов wrote:
Hello,

originally this question was asked here:
https://urldefense.com/v3/__https://stackoverflow.com/q/76260269/12473843__;!!ACWV5N9M2RV99hQ!Mb5nhj7EbuftWzF7s4GX9auUZZlyPyCUnLs64c4mkmSGJm4pw0CNgRzQR5wOYuApyE_kHSAnVxGyTM9PHz5StCppGw$
 ,
the code sample for reproduction will be put below or can be found via
the link

The issue is about eager/lazy loading of a class depending on method
return type.
If one runs the code below with Java 11-19 it will fail with
NoClassDefFoundError (this is expected as delete class file for
ChildClass):

java.lang.NoClassDefFoundError: org/example/TestLoading$ChildClass
     at java.base/java.lang.Class.forName0(Native Method)
     at java.base/java.lang.Class.forName(Class.java:390)
     at java.base/java.lang.Class.forName(Class.java:381)
     at org.example.TestLoading.loadMyClass(TestLoading.java:29)
     at org.example.TestLoading.main(TestLoading.java:23)
Caused by: java.lang.ClassNotFoundException:
org.example.TestLoading$ChildClass
     at
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
     at
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
     at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
     ... 5 more

As of Java 20 chapter 12.4.1 of JLS states:
---------------------------------------------------------
A class or interface T will be initialized immediately before the
first occurrence of any one of the following:

- T is a class and an instance of T is created.
- a static method declared by T is invoked.
- a static field declared by T is assigned.
- a static field declared by T is used and the field is not a constant
variable (§4.12.4).

When a class is initialized, its superclasses are initialized (if they have not been previously initialized), as well as any superinterfaces
(§8.1.5) that declare any default methods (§9.4.3) (if they have not
been previously initialized).
Initialization of an interface does not, of itself, cause
initialization of any of its superinterfaces.
A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it, even though it might
be referred to through the name of a subclass, a subinterface, or a
class that implements an interface.
Invocation of certain reflective methods in class Class and in package
java.lang.reflect also causes class or interface initialization.
A class or interface will not be initialized under any other
circumstance.
---------------------------------------------------------
With the code snippet we see that calling
Class.forName(ObjectReturner.class.getName()) succeeds and
Class.forName(BaseClassReturner.class.getName()) fails even though
both declare returning an instance of ChildClass.
This failure is unexpected as in the code below we don't fulfill any
requirement for class loading as of JLS 12.4.1, but the JVM still
tries to load the class.

I suspect it might be related to class file validation and/or
security, because when we run the code with -Xlog:class+init there's a
reference to LinkageError in loading log:

loading: org.example.TestLoading$BaseClassReturner...
[0.277s][info][class,init] Start class verification for:
org.example.TestLoading$BaseClassReturner
[0.277s][info][class,init] 771 Initializing
'java/lang/ReflectiveOperationException'(no method) (0x0000000800004028)
[0.277s][info][class,init] 772 Initializing
'java/lang/ClassNotFoundException'(no method) (0x0000000800004288)
[0.277s][info][class,init] 773 Initializing
'java/lang/LinkageError'(no method)
(0x00000008000044f8)                                  <----
[0.277s][info][class,init] 774 Initializing
'java/lang/NoClassDefFoundError'(no method) (0x0000000800004758)
[0.277s][info][class,init] Verification for
org.example.TestLoading$BaseClassReturner has exception pending
'java.lang.NoClassDefFoundError org/example/TestLoading$ChildClass'
[0.277s][info][class,init] End class verification for:
org.example.TestLoading$BaseClassReturner


So I've got three questions about this:
- Does class loading depend on method's return type?
- Which part of JLS/JVM spec describes eager class loading in this case? - Could one point out the particular piece of the VM code responsible
for class loading in this case?

Regards,
Sergey Tsypanov

Code snippet for reproduction:

public class TestLoading {

    public static void main(String[] args) throws Exception {
      Class.forName(BaseClass.class.getName());
      URL classFileB =
TestLoading.class.getResource(TestLoading.class.getSimpleName() +
"$ChildClass.class");
      if (classFileB != null) {
        if (!"file".equals(classFileB.getProtocol())) {
          throw new UnsupportedOperationException();
        }
        Path path = new File(classFileB.getPath()).toPath();
        System.out.println("deleting: " + path);
        Files.delete(path);
      }

      loadMyClass(ObjectReturner.class.getName());
      loadMyClass(BaseClassReturner.class.getName());
    }

    private static void loadMyClass(String name) {
      System.out.println("loading: " + name + "...");
      try {
        Class.forName(name);
      } catch (Throwable e) {
        e.printStackTrace(System.out);
      }
    }

    public static class BaseClass {
    }

    public static class ChildClass extends BaseClass {
    }

    public static class ObjectReturner {
      public Object getObject() {
        return new ChildClass();
      }
    }

    public static class BaseClassReturner {
      public BaseClass getObject() {
        return new ChildClass();
      }
    }
}

Reply via email to