changeset 47565:1aecd400f2fa

Merge
author hseigel
date Mon, 23 Oct 2017 14:57:05 +0000
parents f433d49aceb4 c3c04b6e14f8
children 24ebaf9d7198
files
diffstat 9 files changed, 595 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- 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);
+    }
+}
+