# HG changeset patch # User hseigel # Date 1508769746 14400 # Node ID c3c04b6e14f84c43aef24570f51d1b0bd501560d # Parent e4a89dfa1247a8a52f8fe5d66ad9f71ff406d2de 8174954: Parameter target type is allowed access after a module read edge or a package export has occurred after failed resolution Summary: Store resolution exception in resolution_error table and set flag in cpCache indicating resolution failed. Reviewed-by: acorn, jrose, dholmes, lfoltan diff -r e4a89dfa1247 -r c3c04b6e14f8 src/hotspot/share/classfile/resolutionErrors.hpp --- a/src/hotspot/share/classfile/resolutionErrors.hpp Mon Oct 23 10:45:07 2017 +0000 +++ b/src/hotspot/share/classfile/resolutionErrors.hpp Mon Oct 23 10:42:26 2017 -0400 @@ -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 { 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; + } }; diff -r e4a89dfa1247 -r c3c04b6e14f8 src/hotspot/share/interpreter/linkResolver.cpp --- a/src/hotspot/share/interpreter/linkResolver.cpp Mon Oct 23 10:45:07 2017 +0000 +++ b/src/hotspot/share/interpreter/linkResolver.cpp Mon Oct 23 10:42:26 2017 -0400 @@ -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, diff -r e4a89dfa1247 -r c3c04b6e14f8 src/hotspot/share/oops/constantPool.hpp --- a/src/hotspot/share/oops/constantPool.hpp Mon Oct 23 10:45:07 2017 +0000 +++ b/src/hotspot/share/oops/constantPool.hpp Mon Oct 23 10:42:26 2017 -0400 @@ -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) { diff -r e4a89dfa1247 -r c3c04b6e14f8 src/hotspot/share/oops/cpCache.cpp --- a/src/hotspot/share/oops/cpCache.cpp Mon Oct 23 10:45:07 2017 +0000 +++ b/src/hotspot/share/oops/cpCache.cpp Mon Oct 23 10:42:26 2017 -0400 @@ -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(); diff -r e4a89dfa1247 -r c3c04b6e14f8 src/hotspot/share/oops/cpCache.hpp --- a/src/hotspot/share/oops/cpCache.hpp Mon Oct 23 10:45:07 2017 +0000 +++ b/src/hotspot/share/oops/cpCache.hpp Mon Oct 23 10:42:26 2017 -0400 @@ -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() { diff -r e4a89dfa1247 -r c3c04b6e14f8 test/hotspot/jtreg/runtime/BootstrapMethod/BSMCalledTwice.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/BootstrapMethod/BSMCalledTwice.java Mon Oct 23 10:42:26 2017 -0400 @@ -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, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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(); + } +} diff -r e4a89dfa1247 -r c3c04b6e14f8 test/hotspot/jtreg/runtime/modules/AccessCheck/MethodAccessReadTwice.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/modules/AccessCheck/MethodAccessReadTwice.java Mon Oct 23 10:42:26 2017 -0400 @@ -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 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(); + } +} diff -r e4a89dfa1247 -r c3c04b6e14f8 test/hotspot/jtreg/runtime/modules/AccessCheck/p5/c5.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/modules/AccessCheck/p5/c5.java Mon Oct 23 10:42:26 2017 -0400 @@ -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); + } +} diff -r e4a89dfa1247 -r c3c04b6e14f8 test/hotspot/jtreg/runtime/modules/AccessCheck/p7/c7.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/modules/AccessCheck/p7/c7.java Mon Oct 23 10:42:26 2017 -0400 @@ -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); + } +} +