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(); >> } >> } >> }