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.

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