--- a/src/hotspot/share/classfile/resolutionErrors.hpp Mon Oct 23 09:33:14 2017 -0400
+++ b/src/hotspot/share/classfile/resolutionErrors.hpp Mon Oct 23 14:57:05 2017 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2017, 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
@@ -33,6 +33,12 @@
// ResolutionError objects are used to record errors encountered during
// constant pool resolution (JVMS 5.4.3).
+// This value is added to the cpCache index of an invokedynamic instruction when
+// storing the resolution error resulting from that invokedynamic instruction.
+// This prevents issues where the cpCache index is the same as the constant pool
+// index of another entry in the table.
+const int CPCACHE_INDEX_MANGLE_VALUE = 1000000;
+
class ResolutionErrorTable : public Hashtable<ConstantPool*, mtClass> {
public:
@@ -73,6 +79,14 @@
// RedefineClasses support - remove obsolete constant pool entry
void delete_entry(ConstantPool* c);
+
+ // This function is used to encode an index to differentiate it from a
+ // constant pool index. It assumes it is being called with a cpCache index
+ // (that is less than 0).
+ static int encode_cpcache_index(int index) {
+ assert(index < 0, "Unexpected non-negative cpCache index");
+ return index + CPCACHE_INDEX_MANGLE_VALUE;
+ }
};
--- a/src/hotspot/share/interpreter/linkResolver.cpp Mon Oct 23 09:33:14 2017 -0400
+++ b/src/hotspot/share/interpreter/linkResolver.cpp Mon Oct 23 14:57:05 2017 +0000
@@ -25,6 +25,7 @@
#include "precompiled.hpp"
#include "classfile/defaultMethods.hpp"
#include "classfile/javaClasses.hpp"
+#include "classfile/resolutionErrors.hpp"
#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionary.hpp"
#include "classfile/vmSymbols.hpp"
@@ -1696,8 +1697,22 @@
Handle bootstrap_specifier;
// Check if CallSite has been bound already:
ConstantPoolCacheEntry* cpce = pool->invokedynamic_cp_cache_entry_at(index);
+ int pool_index = cpce->constant_pool_index();
+
if (cpce->is_f1_null()) {
- int pool_index = cpce->constant_pool_index();
+ if (cpce->indy_resolution_failed()) {
+ ConstantPool::throw_resolution_error(pool,
+ ResolutionErrorTable::encode_cpcache_index(index),
+ CHECK);
+ }
+
+ // The initial step in Call Site Specifier Resolution is to resolve the symbolic
+ // reference to a method handle which will be the bootstrap method for a dynamic
+ // call site. If resolution for the java.lang.invoke.MethodHandle for the bootstrap
+ // method fails, then a MethodHandleInError is stored at the corresponding bootstrap
+ // method's CP index for the CONSTANT_MethodHandle_info. So, there is no need to
+ // set the indy_rf flag since any subsequent invokedynamic instruction which shares
+ // this bootstrap method will encounter the resolution of MethodHandleInError.
oop bsm_info = pool->resolve_bootstrap_specifier_at(pool_index, THREAD);
wrap_invokedynamic_exception(CHECK);
assert(bsm_info != NULL, "");
@@ -1722,7 +1737,31 @@
tty->print(" BSM info: "); bootstrap_specifier->print();
}
- resolve_dynamic_call(result, bootstrap_specifier, method_name, method_signature, current_klass, CHECK);
+ resolve_dynamic_call(result, bootstrap_specifier, method_name,
+ method_signature, current_klass, THREAD);
+ if (HAS_PENDING_EXCEPTION && PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) {
+ int encoded_index = ResolutionErrorTable::encode_cpcache_index(index);
+ bool recorded_res_status = cpce->save_and_throw_indy_exc(pool, pool_index,
+ encoded_index,
+ pool()->tag_at(pool_index),
+ CHECK);
+ if (!recorded_res_status) {
+ // Another thread got here just before we did. So, either use the method
+ // that it resolved or throw the LinkageError exception that it threw.
+ if (!cpce->is_f1_null()) {
+ methodHandle method( THREAD, cpce->f1_as_method());
+ Handle appendix( THREAD, cpce->appendix_if_resolved(pool));
+ Handle method_type(THREAD, cpce->method_type_if_resolved(pool));
+ result.set_handle(method, appendix, method_type, THREAD);
+ wrap_invokedynamic_exception(CHECK);
+ } else {
+ assert(cpce->indy_resolution_failed(), "Resolution failure flag not set");
+ ConstantPool::throw_resolution_error(pool, encoded_index, CHECK);
+ }
+ return;
+ }
+ assert(cpce->indy_resolution_failed(), "Resolution failure flag wasn't set");
+ }
}
void LinkResolver::resolve_dynamic_call(CallInfo& result,
--- a/src/hotspot/share/oops/constantPool.hpp Mon Oct 23 09:33:14 2017 -0400
+++ b/src/hotspot/share/oops/constantPool.hpp Mon Oct 23 14:57:05 2017 +0000
@@ -865,11 +865,13 @@
static oop resolve_bootstrap_specifier_at_impl(const constantPoolHandle& this_cp, int index, TRAPS);
// Exception handling
- static void throw_resolution_error(const constantPoolHandle& this_cp, int which, TRAPS);
static Symbol* exception_message(const constantPoolHandle& this_cp, int which, constantTag tag, oop pending_exception);
static void save_and_throw_exception(const constantPoolHandle& this_cp, int which, constantTag tag, TRAPS);
public:
+ // Exception handling
+ static void throw_resolution_error(const constantPoolHandle& this_cp, int which, TRAPS);
+
// Merging ConstantPool* support:
bool compare_entry_to(int index1, const constantPoolHandle& cp2, int index2, TRAPS);
void copy_cp_to(int start_i, int end_i, const constantPoolHandle& to_cp, int to_i, TRAPS) {
--- a/src/hotspot/share/oops/cpCache.cpp Mon Oct 23 09:33:14 2017 -0400
+++ b/src/hotspot/share/oops/cpCache.cpp Mon Oct 23 14:57:05 2017 +0000
@@ -23,6 +23,7 @@
*/
#include "precompiled.hpp"
+#include "classfile/resolutionErrors.hpp"
#include "interpreter/bytecodeStream.hpp"
#include "interpreter/bytecodes.hpp"
#include "interpreter/interpreter.hpp"
@@ -110,6 +111,10 @@
OrderAccess::release_store(&_f1, f1);
}
+void ConstantPoolCacheEntry::set_indy_resolution_failed() {
+ OrderAccess::release_store(&_flags, _flags | (1 << indy_resolution_failed_shift));
+}
+
// Note that concurrent update of both bytecodes can leave one of them
// reset to zero. This is harmless; the interpreter will simply re-resolve
// the damaged entry. More seriously, the memory synchronization is needed
@@ -318,6 +323,25 @@
return;
}
+ if (indy_resolution_failed()) {
+ // Before we got here, another thread got a LinkageError exception during
+ // resolution. Ignore our success and throw their exception.
+ ConstantPoolCache* cpCache = cpool->cache();
+ int index = -1;
+ for (int i = 0; i < cpCache->length(); i++) {
+ if (cpCache->entry_at(i) == this) {
+ index = i;
+ break;
+ }
+ }
+ guarantee(index >= 0, "Didn't find cpCache entry!");
+ int encoded_index = ResolutionErrorTable::encode_cpcache_index(
+ ConstantPool::encode_invokedynamic_index(index));
+ Thread* THREAD = Thread::current();
+ ConstantPool::throw_resolution_error(cpool, encoded_index, THREAD);
+ return;
+ }
+
const methodHandle adapter = call_info.resolved_method();
const Handle appendix = call_info.resolved_appendix();
const Handle method_type = call_info.resolved_method_type();
@@ -389,6 +413,40 @@
}
}
+bool ConstantPoolCacheEntry::save_and_throw_indy_exc(
+ const constantPoolHandle& cpool, int cpool_index, int index, constantTag tag, TRAPS) {
+
+ assert(HAS_PENDING_EXCEPTION, "No exception got thrown!");
+ assert(PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass()),
+ "No LinkageError exception");
+
+ // Use the resolved_references() lock for this cpCache entry.
+ // resolved_references are created for all classes with Invokedynamic, MethodHandle
+ // or MethodType constant pool cache entries.
+ objArrayHandle resolved_references(Thread::current(), cpool->resolved_references());
+ assert(resolved_references() != NULL,
+ "a resolved_references array should have been created for this class");
+ ObjectLocker ol(resolved_references, THREAD);
+
+ // if f1 is not null or the indy_resolution_failed flag is set then another
+ // thread either succeeded in resolving the method or got a LinkageError
+ // exception, before this thread was able to record its failure. So, clear
+ // this thread's exception and return false so caller can use the earlier
+ // thread's result.
+ if (!is_f1_null() || indy_resolution_failed()) {
+ CLEAR_PENDING_EXCEPTION;
+ return false;
+ }
+
+ Symbol* error = PENDING_EXCEPTION->klass()->name();
+ Symbol* message = java_lang_Throwable::detail_message(PENDING_EXCEPTION);
+ assert("message != NULL", "Missing detail message");
+
+ SystemDictionary::add_resolution_error(cpool, index, error, message);
+ set_indy_resolution_failed();
+ return true;
+}
+
Method* ConstantPoolCacheEntry::method_if_resolved(const constantPoolHandle& cpool) {
// Decode the action of set_method and set_interface_call
Bytecodes::Code invoke_code = bytecode_1();
--- a/src/hotspot/share/oops/cpCache.hpp Mon Oct 23 09:33:14 2017 -0400
+++ b/src/hotspot/share/oops/cpCache.hpp Mon Oct 23 14:57:05 2017 +0000
@@ -31,6 +31,7 @@
#include "oops/oopHandle.hpp"
#include "runtime/orderAccess.hpp"
#include "utilities/align.hpp"
+#include "utilities/constantTag.hpp"
class PSPromotionManager;
@@ -50,8 +51,8 @@
// _f1 [ entry specific ] metadata ptr (method or klass)
// _f2 [ entry specific ] vtable or res_ref index, or vfinal method ptr
// _flags [tos|0|F=1|0|0|0|f|v|0 |0000|field_index] (for field entries)
-// bit length [ 4 |1| 1 |1|1|1|1|1|1 |-4--|----16-----]
-// _flags [tos|0|F=0|M|A|I|f|0|vf|0000|00000|psize] (for method entries)
+// bit length [ 4 |1| 1 |1|1|1|1|1|1 |1 |-3-|----16-----]
+// _flags [tos|0|F=0|M|A|I|f|0|vf|indy_rf|000|00000|psize] (for method entries)
// bit length [ 4 |1| 1 |1|1|1|1|1|1 |-4--|--8--|--8--]
// --------------------------------
@@ -71,6 +72,7 @@
// f = field or method is final
// v = field is volatile
// vf = virtual but final (method entries only: is_vfinal())
+// indy_rf = call site specifier method resolution failed
//
// The flags after TosState have the following interpretation:
// bit 27: 0 for fields, 1 for methods
@@ -185,6 +187,7 @@
is_final_shift = 22, // (f) is the field or method final?
is_volatile_shift = 21, // (v) is the field volatile?
is_vfinal_shift = 20, // (vf) did the call resolve to a final method?
+ indy_resolution_failed_shift= 19, // (indy_rf) did call site specifier resolution fail ?
// low order bits give field index (for FieldInfo) or method parameter size:
field_index_bits = 16,
field_index_mask = right_n_bits(field_index_bits),
@@ -281,6 +284,13 @@
const CallInfo &call_info // Call link information
);
+ // Return TRUE if resolution failed and this thread got to record the failure
+ // status. Return FALSE if another thread succeeded or failed in resolving
+ // the method and recorded the success or failure before this thread had a
+ // chance to record its failure.
+ bool save_and_throw_indy_exc(const constantPoolHandle& cpool, int cpool_index,
+ int index, constantTag tag, TRAPS);
+
// invokedynamic and invokehandle call sites have two entries in the
// resolved references array:
// appendix (at index+0)
@@ -342,12 +352,14 @@
bool is_f1_null() const { Metadata* f1 = f1_ord(); return f1 == NULL; } // classifies a CPC entry as unbound
int f2_as_index() const { assert(!is_vfinal(), ""); return (int) _f2; }
Method* f2_as_vfinal_method() const { assert(is_vfinal(), ""); return (Method*)_f2; }
+ intx flags_ord() const { return (intx)OrderAccess::load_acquire(&_flags); }
int field_index() const { assert(is_field_entry(), ""); return (_flags & field_index_mask); }
int parameter_size() const { assert(is_method_entry(), ""); return (_flags & parameter_size_mask); }
bool is_volatile() const { return (_flags & (1 << is_volatile_shift)) != 0; }
bool is_final() const { return (_flags & (1 << is_final_shift)) != 0; }
bool is_forced_virtual() const { return (_flags & (1 << is_forced_virtual_shift)) != 0; }
bool is_vfinal() const { return (_flags & (1 << is_vfinal_shift)) != 0; }
+ bool indy_resolution_failed() const { intx flags = flags_ord(); return (flags & (1 << indy_resolution_failed_shift)) != 0; }
bool has_appendix() const { return (!is_f1_null()) && (_flags & (1 << has_appendix_shift)) != 0; }
bool has_method_type() const { return (!is_f1_null()) && (_flags & (1 << has_method_type_shift)) != 0; }
bool is_method_entry() const { return (_flags & (1 << is_field_entry_shift)) == 0; }
@@ -356,6 +368,7 @@
bool is_double() const { return flag_state() == dtos; }
TosState flag_state() const { assert((uint)number_of_states <= (uint)tos_state_mask+1, "");
return (TosState)((_flags >> tos_state_shift) & tos_state_mask); }
+ void set_indy_resolution_failed();
// Code generation support
static WordSize size() {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/BootstrapMethod/BSMCalledTwice.java Mon Oct 23 14:57:05 2017 +0000
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2017, 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 8174954
+ * @library /test/lib
+ * @modules java.base/jdk.internal.org.objectweb.asm
+ * @compile -XDignore.symbol.file BSMCalledTwice.java
+ * @run main BSMCalledTwice
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.*;
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+import jdk.internal.org.objectweb.asm.*;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+
+// BSMCalledTwice generates a class file named "TestC.class" that contains
+// bytecodes that represent the following program
+//
+// public class TestC {
+// public static void main(java.lang.String[] arg) {
+// for (int i=0; i < 2; i++) {
+// try {
+// String f = "friend";
+//
+// // The "hello " + f in the following statement produces an
+// // invokedynamic with a BSM of
+// // StringConcatFactory.java/makeConcatWithConstants.
+// // The ASM below erroneously puts 2 static arguments, "hello "
+// // and "goodbye" on the stack for the BSM. Causing a exception to
+// // be thrown when creatingthe CallSite object.
+// System.out.println("hello " + f); <--------------- invokedynamic
+//
+// } catch (Error e) {
+// System.out.println("Caught Error:");
+// System.out.println(e.getMessage());
+// e.printStackTrace();
+// }
+// }
+// }
+// }
+//
+public class BSMCalledTwice implements Opcodes {
+ static final String classTestCName = "TestC";
+
+ public static int count_makeSite(String text) {
+ int count = 0;
+ String text_ptr = text;
+ while (text_ptr.indexOf("makeSite") != -1) {
+ text_ptr = text_ptr.substring(text_ptr.indexOf("makeSite") + 1);
+ count++;
+ }
+ return count;
+ }
+
+ public static void main(String[] args) throws Exception {
+ ClassLoader cl = new ClassLoader() {
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ if (findLoadedClass(name) != null) {
+ return findLoadedClass(name);
+ }
+
+ if (classTestCName.equals(name)) {
+ byte[] classFile = null;
+ try {
+ classFile = dumpTestC();
+ } catch (Exception e) {
+ }
+ return defineClass(classTestCName, classFile, 0, classFile.length);
+ }
+ return super.loadClass(name);
+ }
+ };
+
+ cl.loadClass(classTestCName);
+ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, "-cp", ".", classTestCName);
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ String test_output = output.getOutput();
+ if (test_output == null) {
+ throw new RuntimeException("Test failed, null test output");
+ }
+ // "makeSite" is currently listed twice in the exception stacks for each
+ // failing call to the BootstrapMethod. So more that two calls means
+ // that the BootstrapMethod was called more than once.
+ int count = count_makeSite(test_output);
+ if (count < 1 || count > 2) {
+ throw new RuntimeException("Test failed, bad number of calls to BootstrapMethod");
+ }
+ output.shouldHaveExitValue(0);
+ }
+
+ public static byte[] dumpTestC () throws Exception {
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(53, ACC_PUBLIC + ACC_SUPER, classTestCName, null, "java/lang/Object", null);
+
+ cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup",
+ "java/lang/invoke/MethodHandles", "Lookup",
+ ACC_PUBLIC + ACC_FINAL + ACC_STATIC);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ Label l2 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Error");
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ISTORE, 1);
+ Label l3 = new Label();
+ mv.visitLabel(l3);
+ mv.visitFrame(Opcodes.F_APPEND,1, new Object[] {Opcodes.INTEGER}, 0, null);
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitInsn(ICONST_2);
+ Label l4 = new Label();
+ mv.visitJumpInsn(IF_ICMPGE, l4);
+ mv.visitLabel(l0);
+ mv.visitLdcInsn("friend");
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInvokeDynamicInsn("makeConcatWithConstants",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ new Handle(Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/StringConcatFactory",
+ "makeConcatWithConstants",
+ "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"),
+ new Object[]{"hello \u0001", "goodbye"});
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitLabel(l1);
+ Label l5 = new Label();
+ mv.visitJumpInsn(GOTO, l5);
+ mv.visitLabel(l2);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Error"});
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("Caught Error:");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Error", "getMessage", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Error", "printStackTrace", "()V", false);
+ mv.visitLabel(l5);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitIincInsn(1, 1);
+ mv.visitJumpInsn(GOTO, l3);
+ mv.visitLabel(l4);
+ mv.visitFrame(Opcodes.F_CHOP,1, null, 0, null);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 3);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ try(FileOutputStream fos = new FileOutputStream(new File("TestC.class"))) {
+ fos.write(cw.toByteArray());
+ }
+ return cw.toByteArray();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/modules/AccessCheck/MethodAccessReadTwice.java Mon Oct 23 14:57:05 2017 +0000
@@ -0,0 +1,167 @@
+/*
+ Copyright (c) 2017, 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 8174954
+ * @summary Test that invokedynamic instructions, that initially throw IAE exceptions
+ * because of a missing module read edge, behave correctly when executed
+ * after the module read edge is added.
+ * @compile ModuleLibrary.java
+ * p2/c2.java
+ * p5/c5.java
+ * p7/c7.java
+ * @run main/othervm MethodAccessReadTwice
+ */
+
+import java.lang.module.Configuration;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.ModuleLayer;
+import java.lang.Module;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+// defines first_mod --> packages p5
+// defines second_mod --> package p2, p2 is exported to first_mod
+// defines third_mod --> packages p7
+
+public class MethodAccessReadTwice {
+
+ // Create a Layer over the boot layer.
+ // Define modules within this layer to test access between
+ // publicly defined classes within packages of those modules.
+ public void createLayerOnBoot() throws Throwable {
+
+ // Define module: first_mod
+ // Can read: java.base
+ // Packages: p5
+ // Packages exported: p5 is exported unqualifiedly
+ ModuleDescriptor descriptor_first_mod =
+ ModuleDescriptor.newModule("first_mod")
+ .requires("java.base")
+ .exports("p5")
+ .build();
+
+ // Define module: second_mod
+ // Can read: java.base
+ // Packages: p2
+ // Packages exported: p2 is exported to first_mod
+ ModuleDescriptor descriptor_second_mod =
+ ModuleDescriptor.newModule("second_mod")
+ .requires("java.base")
+ .exports("p2")
+ .build();
+
+ // Define module: third_mod
+ // Can read: java.base
+ // Packages: p7
+ // Packages exported: p7 is exported unqualifiedly
+ ModuleDescriptor descriptor_third_mod =
+ ModuleDescriptor.newModule("third_mod")
+ .requires("java.base")
+ .exports("p7")
+ .build();
+
+ // Set up a ModuleFinder containing all modules for this layer
+ ModuleFinder finder = ModuleLibrary.of(descriptor_first_mod,
+ descriptor_second_mod,
+ descriptor_third_mod);
+
+ // Resolves "first_mod", "second_mod", and "third_mod"
+ Configuration cf = ModuleLayer.boot()
+ .configuration()
+ .resolve(finder, ModuleFinder.of(),
+ Set.of("first_mod", "second_mod", "third_mod"));
+
+ // Map each module to this class loader
+ Map<String, ClassLoader> map = new HashMap<>();
+ ClassLoader loader = MethodAccessReadTwice.class.getClassLoader();
+ map.put("first_mod", loader);
+ map.put("second_mod", loader);
+ map.put("third_mod", loader);
+
+ // Create Layer that contains first_mod, second_mod, and third_mod
+ ModuleLayer layer = ModuleLayer.boot().defineModules(cf, map::get);
+
+ Class p2_c2_class = loader.loadClass("p2.c2");
+ Class p5_c5_class = loader.loadClass("p5.c5");
+ Class p7_c7_class = loader.loadClass("p7.c7");
+
+ Module first_mod = p5_c5_class.getModule();
+ Module second_mod = p2_c2_class.getModule();
+ Module third_mod = p7_c7_class.getModule();
+
+ p5.c5 c5_obj = new p5.c5();
+ p2.c2 c2_obj = new p2.c2();
+ p7.c7 c7_obj = new p7.c7();
+
+ // Test that if an invokedynamic instruction gets an IAE exception because
+ // of a module read issue, and then the read issue is fixed, that
+ // re-executing the same invokedynamic instruction will get the same IAE.
+
+ // First access check for p5.c5 --> call to method5 --> tries to access p2.c2
+ try {
+ // Should throw IAE because p5.c5's module cannot read p2.c2's module.
+ c5_obj.method5(c2_obj);
+ throw new RuntimeException("Test Failed, module first_mod should not have access to p2.c2");
+ } catch (IllegalAccessError e) {
+ String message = e.getMessage();
+ if (!(message.contains("cannot access") &&
+ message.contains("because module first_mod does not read module second_mod"))) {
+ throw new RuntimeException("Wrong message: " + message);
+ } else {
+ System.out.println("Test Succeeded at attempt #1");
+ }
+ }
+
+ // Add a read edge from p5/c5's module (first_mod) to p2.c2's module (second_mod)
+ c5_obj.methodAddReadEdge(p2_c2_class.getModule());
+ // Second access check for p5.c5, should have same result as first
+ try {
+ c5_obj.method5(c2_obj); // should result in IAE
+ throw new RuntimeException("Test Failed, access should have been cached above");
+ } catch (IllegalAccessError e) {
+ String message = e.getMessage();
+ if (!(message.contains("cannot access") &&
+ message.contains("because module first_mod does not read module second_mod"))) {
+ throw new RuntimeException("Wrong message: " + message);
+ } else {
+ System.out.println("Test Succeeded at attempt #2");
+ }
+ }
+
+
+ // Test that if one invokedynamic instruction gets an IAE exception
+ // because of a module read issue, and then the read issue is fixed, that
+ // a subsequent invokedynamic instruction, that tries the same access,
+ // succeeds.
+ c7_obj.method7(c2_obj, second_mod); // Should not result in IAE
+ }
+
+ public static void main(String args[]) throws Throwable {
+ MethodAccessReadTwice test = new MethodAccessReadTwice();
+ test.createLayerOnBoot();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/modules/AccessCheck/p5/c5.java Mon Oct 23 14:57:05 2017 +0000
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2017, 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 p5;
+
+import java.lang.Module;
+import p2.c2;
+
+public class c5 {
+ public void method5(p2.c2 param) {
+ // The invokedynamic opcode that gets generated for the '+' string
+ // concatenation operator throws an IllegalAccessError when trying to
+ // access 'param'.
+ System.out.println("In c5's method5 with param = " + param);
+ }
+
+ public void methodAddReadEdge(Module m) {
+ // Add a read edge from p5/c5's module (first_mod) to second_mod
+ c5.class.getModule().addReads(m);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/modules/AccessCheck/p7/c7.java Mon Oct 23 14:57:05 2017 +0000
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2017, 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 p7;
+
+import java.lang.Module;
+import p2.c2;
+
+public class c7 {
+ public void method7(p2.c2 param, Module c2Mod) {
+ try {
+ // The invokedynamic opcode that gets generated for the '+' string
+ // concatenation operator throws an IllegalAccessError when trying
+ // to access 'param'.
+ System.out.println("In c7's method7 with param = " + param);
+ throw new java.lang.RuntimeException("c7 failed to throw expected IllegalAccessError");
+ } catch (IllegalAccessError e) {
+ }
+ methodAddReadEdge(c2Mod);
+
+ // This invokedynamic (for the string concat) should succeed because of
+ // the added read edge. The fact that the invokedynamic executed before
+ // the read edge was added threw an IllegalAccessError exception should
+ // not affect this one.
+ try {
+ System.out.println("In c7's method7 with param = " + param);
+ } catch (IllegalAccessError e) {
+ throw new java.lang.RuntimeException("Unexpected IllegalAccessError: " + e.getMessage());
+ }
+ }
+
+ public void methodAddReadEdge(Module m) {
+ // Add a read edge from p7/c7's module (third_mod) to module m.
+ c7.class.getModule().addReads(m);
+ }
+}
+