changeset 57058:2144055c4643 lworld

8230912: [lworld] InterpreterRuntime::uninitialized_static_value_field fails with fatal error: An exception should have been thrown above Reviewed-by: dsimms
author fparain
date Wed, 18 Sep 2019 11:23:41 -0400
parents c758fea7b325
children 1851f3116b2c
files src/hotspot/share/interpreter/interpreterRuntime.cpp src/hotspot/share/oops/instanceKlass.cpp test/hotspot/jtreg/runtime/valhalla/valuetypes/CircularityTest.java
diffstat 3 files changed, 221 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/src/hotspot/share/interpreter/interpreterRuntime.cpp	Wed Sep 18 09:23:07 2019 -0400
+++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp	Wed Sep 18 11:23:41 2019 -0400
@@ -380,24 +380,51 @@
 
 JRT_ENTRY(void, InterpreterRuntime::uninitialized_static_value_field(JavaThread* thread, oopDesc* mirror, int index))
   // The interpreter tries to access a flattenable static field that has not been initialized.
-  // This situation can happen only if the load or initialization of the field failed during step 8 of
-  // the initialization of the holder of the field. The code below tries to load and initialize
-  // the field's class again in order to throw likely the same exception or error as the one that caused
-  // the field initialization to fail.
+  // This situation can happen in different scenarios:
+  //   1 - if the load or initialization of the field failed during step 8 of
+  //       the initialization of the holder of the field, in this case the access to the field
+  //       must fail
+  //   2 - it can also happen when the initialization of the holder class triggered the initialization of
+  //       another class which accesses this field in its static initializer, in this case the
+  //       access must succeed to allow circularity
+  // The code below tries to load and initialize the field's class again before returning the default value.
+  // If the field was not initialized because of an error, a exception should be thrown.
+  // If the class is being initialized, the default value is returned.
   instanceHandle mirror_h(THREAD, (instanceOop)mirror);
   InstanceKlass* klass = InstanceKlass::cast(java_lang_Class::as_Klass(mirror));
-  int offset = klass->field_offset(index);
-  Klass* field_k = klass->get_value_field_klass_or_null(index);
-  if (field_k == NULL) {
-    field_k = SystemDictionary::resolve_or_fail(klass->field_signature(index)->fundamental_name(THREAD),
-        Handle(THREAD, klass->class_loader()),
-        Handle(THREAD, klass->protection_domain()),
-        true, CHECK);
-    assert(field_k != NULL, "Should have been loaded or an exception thrown above");
-    klass->set_value_field_klass(index, field_k);
+  if (klass->is_being_initialized() && klass->is_reentrant_initialization(THREAD)) {
+    int offset = klass->field_offset(index);
+    Klass* field_k = klass->get_value_field_klass_or_null(index);
+    if (field_k == NULL) {
+      field_k = SystemDictionary::resolve_or_fail(klass->field_signature(index)->fundamental_name(THREAD),
+          Handle(THREAD, klass->class_loader()),
+          Handle(THREAD, klass->protection_domain()),
+          true, CHECK);
+      assert(field_k != NULL, "Should have been loaded or an exception thrown above");
+      klass->set_value_field_klass(index, field_k);
+    }
+    field_k->initialize(CHECK);
+    oop defaultvalue = ValueKlass::cast(field_k)->default_value();
+    // It is safe to initialized the static field because 1) the current thread is the initializing thread
+    // and is the only one that can access it, and 2) the field is actually not initialized (i.e. null)
+    // otherwise the JVM should not be executing this code.
+    mirror->obj_field_put(offset, defaultvalue);
+    thread->set_vm_result(defaultvalue);
+  } else {
+    assert(klass->is_in_error_state(), "If not initializing, initialization must have failed to get there");
+    ResourceMark rm(THREAD);
+    const char* desc = "Could not initialize class ";
+    const char* className = klass->external_name();
+    size_t msglen = strlen(desc) + strlen(className) + 1;
+    char* message = NEW_RESOURCE_ARRAY(char, msglen);
+    if (NULL == message) {
+      // Out of memory: can't create detailed error message
+      THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
+    } else {
+      jio_snprintf(message, msglen, "%s%s", desc, className);
+      THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
+    }
   }
-  field_k->initialize(CHECK);
-  fatal("An exception should have been thrown above");
 JRT_END
 
 JRT_ENTRY(void, InterpreterRuntime::uninitialized_instance_value_field(JavaThread* thread, oopDesc* obj, int index))
--- a/src/hotspot/share/oops/instanceKlass.cpp	Wed Sep 18 09:23:07 2019 -0400
+++ b/src/hotspot/share/oops/instanceKlass.cpp	Wed Sep 18 11:23:41 2019 -0400
@@ -1141,7 +1141,9 @@
         }
         InstanceKlass::cast(klass)->initialize(CHECK);
         if (fs.access_flags().is_static()) {
-          java_mirror()->obj_field_put(fs.offset(), ValueKlass::cast(klass)->default_value());
+          if (java_mirror()->obj_field(fs.offset()) == NULL) {
+            java_mirror()->obj_field_put(fs.offset(), ValueKlass::cast(klass)->default_value());
+          }
         }
       }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/valhalla/valuetypes/CircularityTest.java	Wed Sep 18 11:23:41 2019 -0400
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2019, 2019, 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.
+ */
+package runtime.valhalla.valuetypes;
+
+import jdk.test.lib.Asserts;
+
+/*
+ * @test
+ * @summary Test initialization of static inline fields with circularity
+ * @library /test/lib
+ * @compile CircularityTest.java
+ * @run main/othervm -Xint -XX:+EnableValhalla runtime.valhalla.valuetypes.CircularityTest
+ */
+
+
+public class CircularityTest {
+    static boolean b = true;
+    static int counter = 0;
+    
+    static inline class A {
+	static B b;
+	static C c;
+	int i = 0;
+    }
+
+    static inline class B {
+	static {
+	    Asserts.assertNotNull(A.c, "Should have returned C's default value");
+	}
+	int i = 0;
+    }
+
+    static inline class C {
+	int i;
+	public C(int i) {
+	    this.i = i;
+	}
+    }
+
+    static inline class D {
+	static C c;
+	int i = 0;
+	static {
+	    if (CircularityTest.b) {
+		// throw an exception to cause D's initialization to fail
+		throw new RuntimeException();
+	    }
+	}
+    }
+
+    static inline class E {
+	static F f;
+	static C c;
+	int i = 0;
+    }
+
+    static inline class F {
+	int i = 0;
+	static {
+	    E.c = new C(5);
+	}
+    }
+
+    static inline class G {
+	static H h;
+	int i = 0;
+    }
+
+    static inline class H {
+	int i = 0;
+	static {
+	    if (CircularityTest.b) {
+		// throw an exception to cause H's initialization to fail
+		throw new RuntimeException();
+	    }
+	}
+    }
+
+    static inline class I {
+	static J j;
+	static H h;
+	int i = 0;
+    }
+
+    static inline class J {
+	int i = 0;
+	static {
+	    CircularityTest.counter = 1;
+	    H h = I.h;
+	    CircularityTest.counter = 2;
+	}
+    }
+    
+    static public void main(String[] args) {
+	Throwable exception = null;
+	// Test 1:
+	// Initialization of A will trigger initialization of B which, in its static
+	// initializer will access a static inline field c of A that has not been initialized
+	// yet. The access must succeed (no exception) because the field is being
+	// accessed during the initialization of D, by the thread initializing D,
+	// and the value must be the default value of C (not null).
+	try {
+	    A a = new A();
+	} catch (Throwable t) {
+	    exception = t;
+	}
+	Asserts.assertNull(exception, "Circularity should not have caused exceptions");
+	
+	// Test 2:
+	// Class D will fail to initialized (exception thrown in its static initializer).
+	// Attempt to access a static inline field of D *after* its failed initialization
+	// should trigger an exception.
+	exception = null;
+	try {
+	    D d = new D();
+	} catch (Throwable t) {
+	    // ignoring the exception thrown to cause initialization failure
+	}
+	try {
+	    C c = D.c;
+	} catch (Throwable t) {
+	    exception = t;
+	}
+	Asserts.assertNotNull(exception, "Accessing static fields of a class which failed to initialized should throw an exception");
+	Asserts.assertEquals(exception.getClass(), java.lang.NoClassDefFoundError.class, "Wrong exception class");
+	// Test 3:
+	// Initialization of E will trigger the initialization of F which, in its static initalizer,
+	// will initialized a static inline field of F before the JVM does. The JVM must not
+	// overwrite the value set by user code.
+	E e = new E();
+	Asserts.assertEquals(E.c.i, 5, "JVM must not overwrite fields initialized by user code");
+	
+	// Test 4:
+	// Initialization of G should fail because its static inline field h
+	exception = null;
+	try {
+	    G g = new G();
+	} catch(Throwable t) {
+	    exception = t;
+	}
+	Asserts.assertNotNull(exception, "G's initialization should have failed");
+	Asserts.assertEquals(exception.getClass(), java.lang.ExceptionInInitializerError.class, "Wrong exception");
+	
+	// Test 5:
+	// Initialization of of I should fail when J tries to access I.h
+	exception = null;
+	try {
+	    I i = new I();
+	} catch(Throwable t) {
+	    exception = t;
+	}
+	Asserts.assertNotNull(exception, "I's initialization should have failed");
+	Asserts.assertEquals(exception.getClass(), java.lang.NoClassDefFoundError.class, "Wrong exception");
+	Asserts.assertEquals(CircularityTest.counter, 1, "Didn't failed at the right place");
+    }    
+}