/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ /* * @test * @bug 8163969 * @summary Test interface initialization states and when certain interfaces are initialized * in the presence of initialization errors. * @run main InterfaceInitializationStates */ import java.util.List; import java.util.Arrays; import java.util.ArrayList; public class InterfaceInitializationStates { static List<Class<?>> cInitOrder = new ArrayList<>(); // K interface with a default method has an initialization error interface K { boolean v = InterfaceInitializationStates.out(K.class); static final Object CONST = InterfaceInitializationStates.someMethod(); default int method() { return 2; } } // I is initialized when CONST is used, and doesn't trigger initialization of K, // I also doesn't get an initialization error just because K has an initialization error. interface I extends K { boolean v = InterfaceInitializationStates.out(I.class); static final Object CONST = InterfaceInitializationStates.someMethod(); } // L can be fully initialized even though it extends an interface that has an // initialization error interface L extends K { boolean v = InterfaceInitializationStates.out(L.class); default void lx() {} static void func() { System.out.println("Calling function on interface with bad super interface."); } } // Another interface needing initialization. // Initialization of this interface does not occur with ClassLIM because K throws // an initialization error, so the interface initialization is abandoned interface M { boolean v = InterfaceInitializationStates.out(M.class); default void mx() {} } static class ClassLIM implements L, I, M { boolean v = InterfaceInitializationStates.out(ClassLIM.class); int callMethodInK() { return method(); } static { // Since interface initialization of K fails, this should never be called System.out.println("Initializing C, but L is still good"); L.func(); } } // Finally initialize M static class ClassM implements M { boolean v = InterfaceInitializationStates.out(ClassM.class); } // Iunlinked is testing initialization like interface I, except interface I is linked when // ClassLIM is linked. // Iunlinked is not linked already when K gets an initialization error. Linking Iunlinked // should succeed because it does not depend on the initialization state of K for linking. interface Iunlinked extends K { boolean v = InterfaceInitializationStates.out(Iunlinked.class); } // More tests. What happens if we use K for parameters and return types? // K is a symbolic reference in the constant pool and the initialization error only // matters when it's used. interface Iparams { boolean v = InterfaceInitializationStates.out(Iparams.class); K the_k = null; K m(K k); // abstract default K method() { return new K(){}; } } static class ClassIparams implements Iparams { boolean v = InterfaceInitializationStates.out(ClassIparams.class); public K m(K k) { return k; } } public static void main(java.lang.String[] unused) { // The rule this tests is the last sentence of JLS 12.4.1: // When a class is initialized, its superclasses are initialized (if they have not // been previously initialized), as well as any superinterfaces (s8.1.5) that declare any // default methods (s9.4.3) (if they have not been previously initialized). Initialization // of an interface does not, of itself, cause initialization of any of its superinterfaces. // Trigger initialization. // Now L is fully_initialized even though K should // throw an error during initialization. boolean v = L.v; L.func(); try { ClassLIM c = new ClassLIM(); // is K initialized, with a perfectly good L in the middle // was bug: this used to succeed and be able to callMethodInK(). throw new RuntimeException("FAIL exception not thrown for class"); } catch (ExceptionInInitializerError e) { System.out.println("ExceptionInInitializerError thrown as expected"); } // Test that K already has initialization error so gets ClassNotFoundException because // initialization was attempted with ClassLIM. try { Class.forName("InterfaceInitializationStates$K", true, InterfaceInitializationStates.class.getClassLoader()); throw new RuntimeException("FAIL exception not thrown for forName(K)"); } catch(ClassNotFoundException e) { throw new RuntimeException("ClassNotFoundException should not be thrown"); } catch(NoClassDefFoundError e) { System.out.println("NoClassDefFoundError thrown as expected"); } new ClassM(); // Initialize I, which doesn't cause K (super interface) to be initialized. // Since the initialization of I does _not_ cause K to be initialized, it does // not get NoClassDefFoundError because K is erroneous. // But the initialization of I throws RuntimeException, so we expect // ExceptionInInitializerError. try { Object ii = I.CONST; throw new RuntimeException("FAIL exception not thrown for I's initialization"); } catch (ExceptionInInitializerError e) { System.out.println("ExceptionInInitializerError as expected"); } // Initialize Iunlinked. No exception should be thrown even if K // (its super interface) is in initialization_error state. boolean bb = Iunlinked.v; // This should be okay boolean value = Iparams.v; System.out.println("value is " + value); ClassIparams p = new ClassIparams(); try { // Now we get an error because K got an initialization_error K kk = p.method(); throw new RuntimeException("FAIL exception not thrown for calling method for K"); } catch(NoClassDefFoundError e) { System.out.println("NoClassDefFoundError thrown as expected"); } // Check expected class initialization order List<Class<?>> expectedCInitOrder = Arrays.asList(L.class, K.class, M.class, ClassM.class, I.class, Iunlinked.class, Iparams.class, ClassIparams.class); if (!cInitOrder.equals(expectedCInitOrder)) { throw new RuntimeException( String.format("Class initialization array %s not equal to expected array %s", cInitOrder, expectedCInitOrder)); } } static boolean out(Class c) { System.out.println("#: initializing " + c.getName()); cInitOrder.add(c); return true; } static Object someMethod() { throw new RuntimeException(); } }