On 24/05/2023 5:04 am, Сергей Цыпанов wrote:
Hi,

Verification can require classes to be loaded to perform the
verification - see JVMS 4.10 for all the details.

sorry, I still don't get it completely. Here's the byte code for 
ObjectReturner.getObject():

// access flags 0x1
public getObject()Ljava/lang/Object;
  L0
   LINENUMBER 43 L0
   NEW org/example/TestLoading$ChildClass
   DUP
   INVOKESPECIAL org/example/TestLoading$ChildClass.<init> ()V
   ARETURN
  L1
   LOCALVARIABLE this Lorg/example/TestLoading$ObjectReturner; L0 L1 0
   MAXSTACK = 2
   MAXLOCALS = 1

and this one is for BaseClassReturner.getObject():

// access flags 0x1
public getObject()Lorg/example/TestLoading$BaseClass;
  L0
   LINENUMBER 49 L0
   NEW org/example/TestLoading$ChildClass
   DUP
   INVOKESPECIAL org/example/TestLoading$ChildClass.<init> ()V
   ARETURN
  L1
   LOCALVARIABLE this Lorg/example/TestLoading$BaseClassReturner; L0 L1 0
   MAXSTACK = 2
   MAXLOCALS = 1

Apart from type of 'this' the only difference is return type, so I've 
referenced JVMS 4.10 in the part where return type is described.

There we have clause for areturn:

"An areturn instruction is type safe iff the enclosing method has a declared return 
type, ReturnType, that is a reference type, and one can validly pop a type matching 
ReturnType off the incoming operand stack."

and for return type:

"If the method returns a reference type, only an areturn instruction may be used, 
and the type of the returned value must be assignment compatible with the return 
descriptor of the method (§4.3.3)"

I guess the second one is a clue for to check whether returned value can be 
assigned to return type the VM should look into inheritance tree. Please 
correct me if idea is wrong.

For the Object case the type-checking is always obviously trivially correct so it doesn't need to load the actual return type. So the seems a reasonable conclusion.

If you take your test code and only have the BaseClassReturner and only load it then you see in the logging:

loading: TestLoading$BaseClassReturner...
[0.067s][info][class,init ] Start class verification for: TestLoading$BaseClassReturner [0.067s][info][verification ] Verifying class TestLoading$BaseClassReturner with new format [0.067s][info][verification ] Verifying method TestLoading$BaseClassReturner.<init>()V [0.067s][info][verification ] Verifying method TestLoading$BaseClassReturner.getObject()LTestLoading$BaseClass; [0.068s][info][class,load ] TestLoading$BaseClass source: file:/scratch/users/daholme/tests/ [0.068s][info][class,load ] TestLoading$ChildClass source: file:/scratch/users/daholme/tests/ [0.068s][info][class,init ] End class verification for: TestLoading$BaseClassReturner [0.068s][info][verification ] End class verification for: TestLoading$BaseClassReturner

So you can see the ChildClass being loaded.

David
-----



Regards,
Sergey


Hi,

On 23/05/2023 9:20 pm, Сергей Цыпанов wrote:

Hello,

originally this question was asked here: 
https://stackoverflow.com/q/76260269/12473843,
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?

Verification can require classes to be loaded to perform the
verification - see JVMS 4.10 for all the details.

Note you seem to be confusing class loading with class initialization
above. The rules for initialization are very precise; the rules for
loading are far more lax.

Cheers,
David
-----

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