# HG changeset patch # User mchung # Date 1586452112 25200 # Thu Apr 09 10:08:32 2020 -0700 # Node ID 8ed6134bb3bbc87bf33f22e5795be87e2f5a2ade # Parent 49baadd53e06c3fb3f87ebbb3bc3fec14a91cf44 8238358: Implementation of JEP 371: Hidden Classes Reviewed-by: alanb, cjplummer, coleenp, dholmes, dlong, forax, jlahoda, psandoz, plevart, vromero Contributed-by: mandy.chung@oracle.com, lois.foltan@oracle.com, david.holmes@oracle.com, harold.seigel@oracle.com, serguei.spitsyn@oracle.com, alex.buckley@oracle.com, jamsheed.c.m@oracle.com, jan.lahoda@oracle.com, amy.lu@oracle.com diff --git a/make/hotspot/symbols/symbols-unix b/make/hotspot/symbols/symbols-unix --- a/make/hotspot/symbols/symbols-unix +++ b/make/hotspot/symbols/symbols-unix @@ -142,6 +142,7 @@ JVM_InvokeMethod JVM_IsArrayClass JVM_IsConstructorIx +JVM_IsHiddenClass JVM_IsInterface JVM_IsPrimitiveClass JVM_IsRecord @@ -151,6 +152,7 @@ JVM_IsVMGeneratedMethodIx JVM_LatestUserDefinedLoader JVM_LoadLibrary +JVM_LookupDefineClass JVM_MaxMemory JVM_MaxObjectInspectionAge JVM_MonitorNotify diff --git a/src/hotspot/share/aot/aotCodeHeap.cpp b/src/hotspot/share/aot/aotCodeHeap.cpp --- a/src/hotspot/share/aot/aotCodeHeap.cpp +++ b/src/hotspot/share/aot/aotCodeHeap.cpp @@ -1049,7 +1049,7 @@ InstanceKlass* dyno = InstanceKlass::cast(dyno_klass); - if (!dyno->is_unsafe_anonymous()) { + if (!dyno->is_hidden() && !dyno->is_unsafe_anonymous()) { if (_klasses_got[dyno_data->_got_index] != dyno) { // compile-time class different from runtime class, fail and deoptimize sweep_dependent_methods(holder_data); diff --git a/src/hotspot/share/aot/aotLoader.cpp b/src/hotspot/share/aot/aotLoader.cpp --- a/src/hotspot/share/aot/aotLoader.cpp +++ b/src/hotspot/share/aot/aotLoader.cpp @@ -43,7 +43,7 @@ #define FOR_ALL_AOT_LIBRARIES(lib) for (GrowableArrayIterator lib = libraries()->begin(); lib != libraries()->end(); ++lib) void AOTLoader::load_for_klass(InstanceKlass* ik, Thread* thread) { - if (ik->is_unsafe_anonymous()) { + if (ik->is_hidden() || ik->is_unsafe_anonymous()) { // don't even bother return; } @@ -58,7 +58,7 @@ uint64_t AOTLoader::get_saved_fingerprint(InstanceKlass* ik) { assert(UseAOT, "called only when AOT is enabled"); - if (ik->is_unsafe_anonymous()) { + if (ik->is_hidden() || ik->is_unsafe_anonymous()) { // don't even bother return 0; } diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -223,9 +223,10 @@ holder->is_in_package("jdk/internal/foreign") || holder->is_in_package("jdk/incubator/foreign") || holder->is_in_package("java/lang")) return true; - // Trust VM unsafe anonymous classes. They are private API (jdk.internal.misc.Unsafe) - // and can't be serialized, so there is no hacking of finals going on with them. - if (holder->is_unsafe_anonymous()) + // Trust hidden classes and VM unsafe anonymous classes. They are created via + // Lookup.defineHiddenClass or the private jdk.internal.misc.Unsafe API and + // can't be serialized, so there is no hacking of finals going on with them. + if (holder->is_hidden() || holder->is_unsafe_anonymous()) return true; // Trust final fields in all boxed classes if (holder->is_box_klass()) diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -63,6 +63,7 @@ _has_nonstatic_fields = ik->has_nonstatic_fields(); _has_nonstatic_concrete_methods = ik->has_nonstatic_concrete_methods(); _is_unsafe_anonymous = ik->is_unsafe_anonymous(); + _is_hidden = ik->is_hidden(); _nonstatic_fields = NULL; // initialized lazily by compute_nonstatic_fields: _has_injected_fields = -1; _implementor = NULL; // we will fill these lazily @@ -73,13 +74,13 @@ // InstanceKlass are created for both weak and strong metadata. Ensuring this metadata // alive covers the cases where there are weak roots without performance cost. oop holder = ik->klass_holder(); - if (ik->is_unsafe_anonymous()) { + if (ik->class_loader_data()->has_class_mirror_holder()) { // Though ciInstanceKlass records class loader oop, it's not enough to keep - // VM unsafe anonymous classes alive (loader == NULL). Klass holder should + // non-strong hidden classes and VM unsafe anonymous classes alive (loader == NULL). Klass holder should // be used instead. It is enough to record a ciObject, since cached elements are never removed // during ciObjectFactory lifetime. ciObjectFactory itself is created for // every compilation and lives for the whole duration of the compilation. - assert(holder != NULL, "holder of unsafe anonymous class is the mirror which is never null"); + assert(holder != NULL, "holder of hidden or unsafe anonymous class is the mirror which is never null"); (void)CURRENT_ENV->get_object(holder); } @@ -123,6 +124,7 @@ _nonstatic_fields = NULL; _has_injected_fields = -1; _is_unsafe_anonymous = false; + _is_hidden = false; _loader = loader; _protection_domain = protection_domain; _is_shared = false; diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -56,6 +56,7 @@ bool _has_nonstatic_fields; bool _has_nonstatic_concrete_methods; bool _is_unsafe_anonymous; + bool _is_hidden; ciFlags _flags; jint _nonstatic_field_size; @@ -191,10 +192,14 @@ return _has_nonstatic_concrete_methods; } - bool is_unsafe_anonymous() { + bool is_unsafe_anonymous() const { return _is_unsafe_anonymous; } + bool is_hidden() const { + return _is_hidden; + } + ciInstanceKlass* get_canonical_holder(int offset); ciField* get_field_by_offset(int field_offset, bool is_static); ciField* get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static); diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -1092,7 +1092,7 @@ assert((int)_annotation_LIMIT <= (int)sizeof(_annotations_present) * BitsPerByte, ""); } // If this annotation name has an ID, report it (or _none). - ID annotation_index(const ClassLoaderData* loader_data, const Symbol* name); + ID annotation_index(const ClassLoaderData* loader_data, const Symbol* name, bool can_access_vm_annotations); // Set the annotation name: void set_annotation(ID id) { assert((int)id >= 0 && (int)id < (int)_annotation_LIMIT, "oob"); @@ -1225,6 +1225,7 @@ const u1* buffer, int limit, AnnotationCollector* coll, ClassLoaderData* loader_data, + const bool can_access_vm_annotations, TRAPS) { assert(cp != NULL, "invariant"); @@ -1270,7 +1271,7 @@ } // Here is where parsing particular annotations will take place. - AnnotationCollector::ID id = coll->annotation_index(loader_data, aname); + AnnotationCollector::ID id = coll->annotation_index(loader_data, aname, can_access_vm_annotations); if (AnnotationCollector::_unknown == id) continue; coll->set_annotation(id); @@ -1396,6 +1397,7 @@ runtime_visible_annotations_length, parsed_annotations, _loader_data, + _can_access_vm_annotations, CHECK); cfs->skip_u1_fast(runtime_visible_annotations_length); } else if (attribute_name == vmSymbols::tag_runtime_invisible_annotations()) { @@ -2059,12 +2061,13 @@ AnnotationCollector::ID AnnotationCollector::annotation_index(const ClassLoaderData* loader_data, - const Symbol* name) { + const Symbol* name, + const bool can_access_vm_annotations) { const vmSymbols::SID sid = vmSymbols::find_sid(name); // Privileged code can use all annotations. Other code silently drops some. - const bool privileged = loader_data->is_the_null_class_loader_data() || + const bool privileged = loader_data->is_boot_class_loader_data() || loader_data->is_platform_class_loader_data() || - loader_data->is_unsafe_anonymous(); + can_access_vm_annotations; switch (sid) { case vmSymbols::VM_SYMBOL_ENUM_NAME(reflect_CallerSensitive_signature): { if (_location != _in_method) break; // only allow for methods @@ -2671,6 +2674,7 @@ runtime_visible_annotations_length, &parsed_annotations, _loader_data, + _can_access_vm_annotations, CHECK_NULL); cfs->skip_u1_fast(runtime_visible_annotations_length); } else if (method_attribute_name == vmSymbols::tag_runtime_invisible_annotations()) { @@ -2865,6 +2869,10 @@ if (parsed_annotations.has_any_annotations()) parsed_annotations.apply_to(methodHandle(THREAD, m)); + if (is_hidden()) { // Mark methods in hidden classes as 'hidden'. + m->set_hidden(true); + } + // Copy annotations copy_method_annotations(m->constMethod(), runtime_visible_annotations, @@ -3596,6 +3604,7 @@ runtime_visible_annotations_length, parsed_annotations, _loader_data, + _can_access_vm_annotations, CHECK); cfs->skip_u1_fast(runtime_visible_annotations_length); } else if (tag == vmSymbols::tag_runtime_invisible_annotations()) { @@ -5592,7 +5601,9 @@ } } -InstanceKlass* ClassFileParser::create_instance_klass(bool changed_by_loadhook, TRAPS) { +InstanceKlass* ClassFileParser::create_instance_klass(bool changed_by_loadhook, + const ClassInstanceInfo& cl_inst_info, + TRAPS) { if (_klass != NULL) { return _klass; } @@ -5600,7 +5611,11 @@ InstanceKlass* const ik = InstanceKlass::allocate_instance_klass(*this, CHECK_NULL); - fill_instance_klass(ik, changed_by_loadhook, CHECK_NULL); + if (is_hidden()) { + mangle_hidden_class_name(ik); + } + + fill_instance_klass(ik, changed_by_loadhook, cl_inst_info, CHECK_NULL); assert(_klass == ik, "invariant"); @@ -5626,7 +5641,10 @@ return ik; } -void ClassFileParser::fill_instance_klass(InstanceKlass* ik, bool changed_by_loadhook, TRAPS) { +void ClassFileParser::fill_instance_klass(InstanceKlass* ik, + bool changed_by_loadhook, + const ClassInstanceInfo& cl_inst_info, + TRAPS) { assert(ik != NULL, "invariant"); // Set name and CLD before adding to CLD @@ -5662,6 +5680,11 @@ // the parser onto the InstanceKlass* apply_parsed_class_metadata(ik, _java_fields_count, CHECK); + // can only set dynamic nest-host after static nest information is set + if (cl_inst_info.dynamic_nest_host() != NULL) { + ik->set_nest_host(cl_inst_info.dynamic_nest_host(), THREAD); + } + // note that is not safe to use the fields in the parser from this point on assert(NULL == _cp, "invariant"); assert(NULL == _fields, "invariant"); @@ -5686,11 +5709,11 @@ ik->set_this_class_index(_this_class_index); - if (is_unsafe_anonymous()) { + if (_is_hidden || is_unsafe_anonymous()) { // _this_class_index is a CONSTANT_Class entry that refers to this - // anonymous class itself. If this class needs to refer to its own methods or - // fields, it would use a CONSTANT_MethodRef, etc, which would reference - // _this_class_index. However, because this class is anonymous (it's + // hidden or anonymous class itself. If this class needs to refer to its own + // methods or fields, it would use a CONSTANT_MethodRef, etc, which would reference + // _this_class_index. However, because this class is hidden or anonymous (it's // not stored in SystemDictionary), _this_class_index cannot be resolved // with ConstantPool::klass_at_impl, which does a SystemDictionary lookup. // Therefore, we must eagerly resolve _this_class_index now. @@ -5706,6 +5729,9 @@ assert (ik->is_unsafe_anonymous(), "should be the same"); ik->set_unsafe_anonymous_host(_unsafe_anonymous_host); } + if (_is_hidden) { + ik->set_is_hidden(); + } // Set PackageEntry for this_klass oop cl = ik->class_loader(); @@ -5785,6 +5811,7 @@ Handle(THREAD, _loader_data->class_loader()), module_handle, _protection_domain, + cl_inst_info.class_data(), CHECK); assert(_all_mirandas != NULL, "invariant"); @@ -5869,7 +5896,6 @@ _class_name->increment_refcount(); } - // For an unsafe anonymous class that is in the unnamed package, move it to its host class's // package by prepending its host class's package name to its class name and setting // its _class_name field. @@ -5922,8 +5948,8 @@ } static bool relax_format_check_for(ClassLoaderData* loader_data) { - bool trusted = (loader_data->is_the_null_class_loader_data() || - SystemDictionary::is_platform_class_loader(loader_data->class_loader())); + bool trusted = loader_data->is_boot_class_loader_data() || + loader_data->is_platform_class_loader_data(); bool need_verify = // verifyAll (BytecodeVerificationLocal && BytecodeVerificationRemote) || @@ -5935,17 +5961,16 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream, Symbol* name, ClassLoaderData* loader_data, - Handle protection_domain, - const InstanceKlass* unsafe_anonymous_host, - GrowableArray* cp_patches, + const ClassLoadInfo* cl_info, Publicity pub_level, TRAPS) : _stream(stream), - _requested_name(name), _class_name(NULL), _loader_data(loader_data), - _unsafe_anonymous_host(unsafe_anonymous_host), - _cp_patches(cp_patches), + _unsafe_anonymous_host(cl_info->unsafe_anonymous_host()), + _cp_patches(cl_info->cp_patches()), + _is_hidden(cl_info->is_hidden()), + _can_access_vm_annotations(cl_info->can_access_vm_annotations()), _num_patched_klasses(0), _max_num_patched_klasses(0), _orig_cp_size(0), @@ -5976,7 +6001,7 @@ _itable_size(0), _num_miranda_methods(0), _rt(REF_NONE), - _protection_domain(protection_domain), + _protection_domain(cl_info->protection_domain()), _access_flags(), _pub_level(pub_level), _bad_constant_seen(0), @@ -6179,10 +6204,15 @@ cp_size, CHECK); _orig_cp_size = cp_size; - if (int(cp_size) + _max_num_patched_klasses > 0xffff) { - THROW_MSG(vmSymbols::java_lang_InternalError(), "not enough space for patched classes"); - } - cp_size += _max_num_patched_klasses; + if (is_hidden()) { // Add a slot for hidden class name. + assert(_max_num_patched_klasses == 0, "Sanity check"); + cp_size++; + } else { + if (int(cp_size) + _max_num_patched_klasses > 0xffff) { + THROW_MSG(vmSymbols::java_lang_InternalError(), "not enough space for patched classes"); + } + cp_size += _max_num_patched_klasses; + } _cp = ConstantPool::allocate(_loader_data, cp_size, @@ -6233,36 +6263,67 @@ Symbol* const class_name_in_cp = cp->klass_name_at(_this_class_index); assert(class_name_in_cp != NULL, "class_name can't be null"); - // Update _class_name to reflect the name in the constant pool - update_class_name(class_name_in_cp); - // Don't need to check whether this class name is legal or not. // It has been checked when constant pool is parsed. // However, make sure it is not an array type. if (_need_verify) { - guarantee_property(_class_name->char_at(0) != JVM_SIGNATURE_ARRAY, + guarantee_property(class_name_in_cp->char_at(0) != JVM_SIGNATURE_ARRAY, "Bad class name in class file %s", CHECK); } - // Checks if name in class file matches requested name - if (_requested_name != NULL && _requested_name != _class_name) { - ResourceMark rm(THREAD); - Exceptions::fthrow( - THREAD_AND_LOCATION, - vmSymbols::java_lang_NoClassDefFoundError(), - "%s (wrong name: %s)", - _class_name->as_C_string(), - _requested_name != NULL ? _requested_name->as_C_string() : "NoName" - ); - return; - } - - // if this is an anonymous class fix up its name if it's in the unnamed +#ifdef ASSERT + // Basic sanity checks + assert(!(_is_hidden && (_unsafe_anonymous_host != NULL)), "mutually exclusive variants"); + + if (_unsafe_anonymous_host != NULL) { + assert(_class_name == vmSymbols::unknown_class_name(), "A named anonymous class???"); + } + if (_is_hidden) { + assert(_class_name != vmSymbols::unknown_class_name(), "hidden classes should have a special name"); + } +#endif + + // Update the _class_name as needed depending on whether this is a named, + // un-named, hidden or unsafe-anonymous class. + + if (_is_hidden) { + assert(_class_name != NULL, "Unexpected null _class_name"); +#ifdef ASSERT + if (_need_verify) { + verify_legal_class_name(_class_name, CHECK); + } +#endif + + // NOTE: !_is_hidden does not imply "findable" as it could be an old-style + // "hidden" unsafe-anonymous class + + // If this is an anonymous class fix up its name if it is in the unnamed // package. Otherwise, throw IAE if it is in a different package than // its host class. - if (_unsafe_anonymous_host != NULL) { + } else if (_unsafe_anonymous_host != NULL) { + update_class_name(class_name_in_cp); fix_unsafe_anonymous_class_name(CHECK); + + } else { + // Check if name in class file matches given name + if (_class_name != class_name_in_cp) { + if (_class_name != vmSymbols::unknown_class_name()) { + ResourceMark rm(THREAD); + Exceptions::fthrow(THREAD_AND_LOCATION, + vmSymbols::java_lang_NoClassDefFoundError(), + "%s (wrong name: %s)", + class_name_in_cp->as_C_string(), + _class_name->as_C_string() + ); + return; + } else { + // The class name was not known by the caller so we set it from + // the value in the CP. + update_class_name(class_name_in_cp); + } + // else nothing to do: the expected class name matches what is in the CP + } } // Verification prevents us from creating names with dots in them, this @@ -6287,9 +6348,10 @@ warning("DumpLoadedClassList and CDS are not supported in exploded build"); DumpLoadedClassList = NULL; } else if (SystemDictionaryShared::is_sharing_possible(_loader_data) && + !_is_hidden && _unsafe_anonymous_host == NULL) { // Only dump the classes that can be stored into CDS archive. - // Unsafe anonymous classes such as generated LambdaForm classes are also not included. + // Hidden and unsafe anonymous classes such as generated LambdaForm classes are also not included. oop class_loader = _loader_data->class_loader(); ResourceMark rm(THREAD); bool skip = false; @@ -6384,6 +6446,35 @@ // all bytes in stream read and parsed } +void ClassFileParser::mangle_hidden_class_name(InstanceKlass* const ik) { + ResourceMark rm; + // Construct hidden name from _class_name, "+", and &ik. Note that we can't + // use a '/' because that confuses finding the class's package. Also, can't + // use an illegal char such as ';' because that causes serialization issues + // and issues with hidden classes that create their own hidden classes. + char addr_buf[20]; + jio_snprintf(addr_buf, 20, INTPTR_FORMAT, p2i(ik)); + size_t new_name_len = _class_name->utf8_length() + 2 + strlen(addr_buf); + char* new_name = NEW_RESOURCE_ARRAY(char, new_name_len); + jio_snprintf(new_name, new_name_len, "%s+%s", + _class_name->as_C_string(), addr_buf); + update_class_name(SymbolTable::new_symbol(new_name)); + + // Add a Utf8 entry containing the hidden name. + assert(_class_name != NULL, "Unexpected null _class_name"); + int hidden_index = _orig_cp_size; // this is an extra slot we added + _cp->symbol_at_put(hidden_index, _class_name); + + // Update this_class_index's slot in the constant pool with the new Utf8 entry. + // We have to update the resolved_klass_index and the name_index together + // so extract the existing resolved_klass_index first. + CPKlassSlot cp_klass_slot = _cp->klass_slot_at(_this_class_index); + int resolved_klass_index = cp_klass_slot.resolved_klass_index(); + _cp->unresolved_klass_at_put(_this_class_index, hidden_index, resolved_klass_index); + assert(_cp->klass_slot_at(_this_class_index).name_index() == _orig_cp_size, + "Bad name_index"); +} + void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const stream, ConstantPool* cp, TRAPS) { diff --git a/src/hotspot/share/classfile/classFileParser.hpp b/src/hotspot/share/classfile/classFileParser.hpp --- a/src/hotspot/share/classfile/classFileParser.hpp +++ b/src/hotspot/share/classfile/classFileParser.hpp @@ -37,6 +37,8 @@ class Array; class ClassFileStream; class ClassLoaderData; +class ClassLoadInfo; +class ClassInstanceInfo; class CompressedLineNumberWriteStream; class ConstMethod; class FieldInfo; @@ -109,11 +111,12 @@ typedef void unsafe_u2; const ClassFileStream* _stream; // Actual input stream - const Symbol* _requested_name; Symbol* _class_name; mutable ClassLoaderData* _loader_data; const InstanceKlass* _unsafe_anonymous_host; GrowableArray* _cp_patches; // overrides for CP entries + const bool _is_hidden; + const bool _can_access_vm_annotations; int _num_patched_klasses; int _max_num_patched_klasses; int _orig_cp_size; @@ -201,6 +204,8 @@ void parse_stream(const ClassFileStream* const stream, TRAPS); + void mangle_hidden_class_name(InstanceKlass* const ik); + void post_process_parsed_stream(const ClassFileStream* const stream, ConstantPool* cp, TRAPS); @@ -208,7 +213,9 @@ void prepend_host_package_name(const InstanceKlass* unsafe_anonymous_host, TRAPS); void fix_unsafe_anonymous_class_name(TRAPS); - void fill_instance_klass(InstanceKlass* ik, bool cf_changed_in_CFLH, TRAPS); + void fill_instance_klass(InstanceKlass* ik, bool cf_changed_in_CFLH, + const ClassInstanceInfo& cl_inst_info, TRAPS); + void set_klass(InstanceKlass* instance); void set_class_bad_constant_seen(short bad_constant); @@ -527,21 +534,19 @@ FieldLayoutInfo* info, TRAPS); - void update_class_name(Symbol* new_name); + void update_class_name(Symbol* new_name); public: ClassFileParser(ClassFileStream* stream, Symbol* name, ClassLoaderData* loader_data, - Handle protection_domain, - const InstanceKlass* unsafe_anonymous_host, - GrowableArray* cp_patches, + const ClassLoadInfo* cl_info, Publicity pub_level, TRAPS); ~ClassFileParser(); - InstanceKlass* create_instance_klass(bool cf_changed_in_CFLH, TRAPS); + InstanceKlass* create_instance_klass(bool cf_changed_in_CFLH, const ClassInstanceInfo& cl_inst_info, TRAPS); const ClassFileStream* clone_stream() const; @@ -557,6 +562,7 @@ u2 this_class_index() const { return _this_class_index; } bool is_unsafe_anonymous() const { return _unsafe_anonymous_host != NULL; } + bool is_hidden() const { return _is_hidden; } bool is_interface() const { return _access_flags.is_interface(); } const InstanceKlass* unsafe_anonymous_host() const { return _unsafe_anonymous_host; } diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -1283,13 +1283,12 @@ ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data(); Handle protection_domain; + ClassLoadInfo cl_info(protection_domain); InstanceKlass* result = KlassFactory::create_from_stream(stream, name, loader_data, - protection_domain, - NULL, // unsafe_anonymous_host - NULL, // cp_patches + cl_info, THREAD); if (HAS_PENDING_EXCEPTION) { if (DumpSharedSpaces) { @@ -1331,8 +1330,8 @@ Arguments::assert_is_dumping_archive(); assert(stream != NULL, "sanity"); - if (ik->is_unsafe_anonymous()) { - // We do not archive unsafe anonymous classes. + if (ik->is_hidden() || ik->is_unsafe_anonymous()) { + // We do not archive hidden or unsafe anonymous classes. return; } diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp --- a/src/hotspot/share/classfile/classLoaderData.cpp +++ b/src/hotspot/share/classfile/classLoaderData.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2020, 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 @@ -126,16 +126,16 @@ _name_and_id = SymbolTable::new_symbol(cl_instance_name_and_id); } -ClassLoaderData::ClassLoaderData(Handle h_class_loader, bool is_unsafe_anonymous) : +ClassLoaderData::ClassLoaderData(Handle h_class_loader, bool has_class_mirror_holder) : _metaspace(NULL), _metaspace_lock(new Mutex(Mutex::leaf+1, "Metaspace allocation lock", true, Mutex::_safepoint_check_never)), - _unloading(false), _is_unsafe_anonymous(is_unsafe_anonymous), + _unloading(false), _has_class_mirror_holder(has_class_mirror_holder), _modified_oops(true), _accumulated_modified_oops(false), // An unsafe anonymous class loader data doesn't have anything to keep // it from being unloaded during parsing of the unsafe anonymous class. // The null-class-loader should always be kept alive. - _keep_alive((is_unsafe_anonymous || h_class_loader.is_null()) ? 1 : 0), + _keep_alive((has_class_mirror_holder || h_class_loader.is_null()) ? 1 : 0), _claim(0), _handles(), _klasses(NULL), _packages(NULL), _modules(NULL), _unnamed_module(NULL), _dictionary(NULL), @@ -150,13 +150,13 @@ initialize_name(h_class_loader); } - if (!is_unsafe_anonymous) { - // The holder is initialized later for unsafe anonymous classes, and before calling anything - // that call class_loader(). + if (!has_class_mirror_holder) { + // The holder is initialized later for non-strong hidden classes and unsafe anonymous classes, + // and before calling anything that call class_loader(). initialize_holder(h_class_loader); - // A ClassLoaderData created solely for an unsafe anonymous class should never have a - // ModuleEntryTable or PackageEntryTable created for it. The defining package + // A ClassLoaderData created solely for a non-strong hidden class or unsafe anonymous class should + // never have a ModuleEntryTable or PackageEntryTable created for it. The defining package // and module for an unsafe anonymous class will be found in its host class. _packages = new PackageEntryTable(PackageEntryTable::_packagetable_entry_size); if (h_class_loader.is_null()) { @@ -291,20 +291,20 @@ } } -// Unsafe anonymous classes have their own ClassLoaderData that is marked to keep alive +// Weak hidden and unsafe anonymous classes have their own ClassLoaderData that is marked to keep alive // while the class is being parsed, and if the class appears on the module fixup list. -// Due to the uniqueness that no other class shares the unsafe anonymous class' name or -// ClassLoaderData, no other non-GC thread has knowledge of the unsafe anonymous class while +// Due to the uniqueness that no other class shares the hidden or unsafe anonymous class' name or +// ClassLoaderData, no other non-GC thread has knowledge of the hidden or unsafe anonymous class while // it is being defined, therefore _keep_alive is not volatile or atomic. void ClassLoaderData::inc_keep_alive() { - if (is_unsafe_anonymous()) { + if (has_class_mirror_holder()) { assert(_keep_alive > 0, "Invalid keep alive increment count"); _keep_alive++; } } void ClassLoaderData::dec_keep_alive() { - if (is_unsafe_anonymous()) { + if (has_class_mirror_holder()) { assert(_keep_alive > 0, "Invalid keep alive decrement count"); _keep_alive--; } @@ -410,21 +410,21 @@ // Do not need to record dependency if the dependency is to a class whose // class loader data is never freed. (i.e. the dependency's class loader - // is one of the three builtin class loaders and the dependency is not - // unsafe anonymous.) + // is one of the three builtin class loaders and the dependency's class + // loader data has a ClassLoader holder, not a Class holder.) if (to_cld->is_permanent_class_loader_data()) { return; } oop to; - if (to_cld->is_unsafe_anonymous()) { - // Just return if an unsafe anonymous class is attempting to record a dependency - // to itself. (Note that every unsafe anonymous class has its own unique class + if (to_cld->has_class_mirror_holder()) { + // Just return if a non-strong hidden class or unsafe anonymous class is attempting to record a dependency + // to itself. (Note that every non-strong hidden class or unsafe anonymous class has its own unique class // loader data.) if (to_cld == from_cld) { return; } - // Unsafe anonymous class dependencies are through the mirror. + // Hidden and unsafe anonymous class dependencies are through the mirror. to = k->java_mirror(); } else { to = to_cld->class_loader(); @@ -572,7 +572,7 @@ const int _default_loader_dictionary_size = 107; Dictionary* ClassLoaderData::create_dictionary() { - assert(!is_unsafe_anonymous(), "unsafe anonymous class loader data do not have a dictionary"); + assert(!has_class_mirror_holder(), "class mirror holder cld does not have a dictionary"); int size; bool resizable = false; if (_the_null_class_loader_data == NULL) { @@ -618,7 +618,7 @@ // Unloading support bool ClassLoaderData::is_alive() const { - bool alive = keep_alive() // null class loader and incomplete unsafe anonymous klasses. + bool alive = keep_alive() // null class loader and incomplete non-strong hidden class or unsafe anonymous class. || (_holder.peek() != NULL); // and not cleaned by the GC weak handle processing. return alive; @@ -716,13 +716,13 @@ // Returns true if this class loader data is for the app class loader // or a user defined system class loader. (Note that the class loader -// data may be unsafe anonymous.) +// data may have a Class holder.) bool ClassLoaderData::is_system_class_loader_data() const { return SystemDictionary::is_system_class_loader(class_loader()); } // Returns true if this class loader data is for the platform class loader. -// (Note that the class loader data may be unsafe anonymous.) +// (Note that the class loader data may have a Class holder.) bool ClassLoaderData::is_platform_class_loader_data() const { return SystemDictionary::is_platform_class_loader(class_loader()); } @@ -730,8 +730,8 @@ // Returns true if the class loader for this class loader data is one of // the 3 builtin (boot application/system or platform) class loaders, // including a user-defined system class loader. Note that if the class -// loader data is for an unsafe anonymous class then it may get freed by a GC -// even if its class loader is one of these loaders. +// loader data is for a non-strong hidden class or unsafe anonymous class then it may +// get freed by a GC even if its class loader is one of these loaders. bool ClassLoaderData::is_builtin_class_loader_data() const { return (is_boot_class_loader_data() || SystemDictionary::is_system_class_loader(class_loader()) || @@ -740,9 +740,9 @@ // Returns true if this class loader data is a class loader data // that is not ever freed by a GC. It must be the CLD for one of the builtin -// class loaders and not the CLD for an unsafe anonymous class. +// class loaders and not the CLD for a non-strong hidden class or unsafe anonymous class. bool ClassLoaderData::is_permanent_class_loader_data() const { - return is_builtin_class_loader_data() && !is_unsafe_anonymous(); + return is_builtin_class_loader_data() && !has_class_mirror_holder(); } ClassLoaderMetaspace* ClassLoaderData::metaspace_non_null() { @@ -759,8 +759,8 @@ if (this == the_null_class_loader_data()) { assert (class_loader() == NULL, "Must be"); metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::BootMetaspaceType); - } else if (is_unsafe_anonymous()) { - metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::UnsafeAnonymousMetaspaceType); + } else if (has_class_mirror_holder()) { + metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::ClassMirrorHolderMetaspaceType); } else if (class_loader()->is_a(SystemDictionary::reflect_DelegatingClassLoader_klass())) { metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::ReflectionMetaspaceType); } else { @@ -877,8 +877,8 @@ } } -// These CLDs are to contain unsafe anonymous classes used for JSR292 -ClassLoaderData* ClassLoaderData::unsafe_anonymous_class_loader_data(Handle loader) { +// These CLDs are to contain non-strong hidden classes or unsafe anonymous classes used for JSR292 +ClassLoaderData* ClassLoaderData::has_class_mirror_holder_cld(Handle loader) { // Add a new class loader data to the graph. return ClassLoaderDataGraph::add(loader, true); } @@ -920,8 +920,8 @@ // loader data: 0xsomeaddr of 'bootstrap' out->print("loader data: " INTPTR_FORMAT " of %s", p2i(this), loader_name_and_id()); } - if (is_unsafe_anonymous()) { - out->print(" unsafe anonymous"); + if (_has_class_mirror_holder) { + out->print(" has a class holder"); } } @@ -931,7 +931,7 @@ void ClassLoaderData::print_on(outputStream* out) const { out->print("ClassLoaderData CLD: " PTR_FORMAT ", loader: " PTR_FORMAT ", loader_klass: %s {", p2i(this), p2i(_class_loader.ptr_raw()), loader_name_and_id()); - if (is_unsafe_anonymous()) out->print(" unsafe anonymous"); + if (has_class_mirror_holder()) out->print(" has a class holder"); if (claimed()) out->print(" claimed"); if (is_unloading()) out->print(" unloading"); out->print(" metaspace: " INTPTR_FORMAT, p2i(metaspace_or_null())); @@ -951,8 +951,8 @@ assert_locked_or_safepoint(_metaspace_lock); oop cl = class_loader(); - guarantee(this == class_loader_data(cl) || is_unsafe_anonymous(), "Must be the same"); - guarantee(cl != NULL || this == ClassLoaderData::the_null_class_loader_data() || is_unsafe_anonymous(), "must be"); + guarantee(this == class_loader_data(cl) || has_class_mirror_holder(), "Must be the same"); + guarantee(cl != NULL || this == ClassLoaderData::the_null_class_loader_data() || has_class_mirror_holder(), "must be"); // Verify the integrity of the allocated space. if (metaspace_or_null() != NULL) { diff --git a/src/hotspot/share/classfile/classLoaderData.hpp b/src/hotspot/share/classfile/classLoaderData.hpp --- a/src/hotspot/share/classfile/classLoaderData.hpp +++ b/src/hotspot/share/classfile/classLoaderData.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2020, 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 @@ -117,17 +117,20 @@ // classes in the class loader are allocated. Mutex* _metaspace_lock; // Locks the metaspace for allocations and setup. bool _unloading; // true if this class loader goes away - bool _is_unsafe_anonymous; // CLD is dedicated to one class and that class determines the CLDs lifecycle. - // For example, an unsafe anonymous class. + bool _has_class_mirror_holder; // If true, CLD is dedicated to one class and that class determines + // the CLDs lifecycle. For example, a non-strong hidden class or an + // unsafe anonymous class. Arrays of these classes are also assigned + // to these class loader datas. // Remembered sets support for the oops in the class loader data. bool _modified_oops; // Card Table Equivalent (YC/CMS support) bool _accumulated_modified_oops; // Mod Union Equivalent (CMS support) int _keep_alive; // if this CLD is kept alive. - // Used for unsafe anonymous classes and the boot class - // loader. _keep_alive does not need to be volatile or - // atomic since there is one unique CLD per unsafe anonymous class. + // Used for non-strong hidden classes, unsafe anonymous classes and the + // boot class loader. _keep_alive does not need to be volatile or + // atomic since there is one unique CLD per non-strong hidden class + // or unsafe anonymous class. volatile int _claim; // non-zero if claimed, for example during GC traces. // To avoid applying oop closure more than once. @@ -162,7 +165,7 @@ void set_next(ClassLoaderData* next) { _next = next; } ClassLoaderData* next() const { return Atomic::load(&_next); } - ClassLoaderData(Handle h_class_loader, bool is_unsafe_anonymous); + ClassLoaderData(Handle h_class_loader, bool has_class_mirror_holder); ~ClassLoaderData(); // The CLD are not placed in the Heap, so the Card Table or @@ -231,7 +234,7 @@ Mutex* metaspace_lock() const { return _metaspace_lock; } - bool is_unsafe_anonymous() const { return _is_unsafe_anonymous; } + bool has_class_mirror_holder() const { return _has_class_mirror_holder; } static void init_null_class_loader_data(); @@ -240,15 +243,15 @@ } // Returns true if this class loader data is for the system class loader. - // (Note that the class loader data may be unsafe anonymous.) + // (Note that the class loader data may be for a non-strong hidden class or unsafe anonymous class) bool is_system_class_loader_data() const; // Returns true if this class loader data is for the platform class loader. - // (Note that the class loader data may be unsafe anonymous.) + // (Note that the class loader data may be for a non-strong hidden class or unsafe anonymous class) bool is_platform_class_loader_data() const; // Returns true if this class loader data is for the boot class loader. - // (Note that the class loader data may be unsafe anonymous.) + // (Note that the class loader data may be for a non-strong hidden class or unsafe anonymous class) inline bool is_boot_class_loader_data() const; bool is_builtin_class_loader_data() const; @@ -269,7 +272,7 @@ return _unloading; } - // Used to refcount an unsafe anonymous class's CLD in order to + // Used to refcount a non-strong hidden class's or unsafe anonymous class's CLD in order to // indicate their aliveness. void inc_keep_alive(); void dec_keep_alive(); @@ -313,7 +316,7 @@ static ClassLoaderData* class_loader_data(oop loader); static ClassLoaderData* class_loader_data_or_null(oop loader); - static ClassLoaderData* unsafe_anonymous_class_loader_data(Handle loader); + static ClassLoaderData* has_class_mirror_holder_cld(Handle loader); // Returns Klass* of associated class loader, or NULL if associated loader is 'bootstrap'. // Also works if unloading. diff --git a/src/hotspot/share/classfile/classLoaderDataGraph.cpp b/src/hotspot/share/classfile/classLoaderDataGraph.cpp --- a/src/hotspot/share/classfile/classLoaderDataGraph.cpp +++ b/src/hotspot/share/classfile/classLoaderDataGraph.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -192,7 +192,7 @@ // Add a new class loader data node to the list. Assign the newly created // ClassLoaderData into the java/lang/ClassLoader object as a hidden field -ClassLoaderData* ClassLoaderDataGraph::add_to_graph(Handle loader, bool is_unsafe_anonymous) { +ClassLoaderData* ClassLoaderDataGraph::add_to_graph(Handle loader, bool has_class_mirror_holder) { assert_lock_strong(ClassLoaderDataGraph_lock); @@ -200,7 +200,7 @@ // First check if another thread beat us to creating the CLD and installing // it into the loader while we were waiting for the lock. - if (!is_unsafe_anonymous && loader.not_null()) { + if (!has_class_mirror_holder && loader.not_null()) { cld = java_lang_ClassLoader::loader_data_acquire(loader()); if (cld != NULL) { return cld; @@ -212,14 +212,14 @@ // loader oop in all collections, particularly young collections. NoSafepointVerifier no_safepoints; - cld = new ClassLoaderData(loader, is_unsafe_anonymous); + cld = new ClassLoaderData(loader, has_class_mirror_holder); // First install the new CLD to the Graph. cld->set_next(_head); Atomic::release_store(&_head, cld); // Next associate with the class_loader. - if (!is_unsafe_anonymous) { + if (!has_class_mirror_holder) { // Use OrderAccess, since readers need to get the loader_data only after // it's added to the Graph java_lang_ClassLoader::release_set_loader_data(loader(), cld); @@ -237,9 +237,9 @@ return cld; } -ClassLoaderData* ClassLoaderDataGraph::add(Handle loader, bool is_unsafe_anonymous) { +ClassLoaderData* ClassLoaderDataGraph::add(Handle loader, bool has_class_mirror_holder) { MutexLocker ml(ClassLoaderDataGraph_lock); - ClassLoaderData* loader_data = add_to_graph(loader, is_unsafe_anonymous); + ClassLoaderData* loader_data = add_to_graph(loader, has_class_mirror_holder); return loader_data; } diff --git a/src/hotspot/share/classfile/classLoaderDataGraph.hpp b/src/hotspot/share/classfile/classLoaderDataGraph.hpp --- a/src/hotspot/share/classfile/classLoaderDataGraph.hpp +++ b/src/hotspot/share/classfile/classLoaderDataGraph.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -56,8 +56,8 @@ static volatile size_t _num_instance_classes; static volatile size_t _num_array_classes; - static ClassLoaderData* add_to_graph(Handle class_loader, bool is_unsafe_anonymous); - static ClassLoaderData* add(Handle class_loader, bool is_unsafe_anonymous); + static ClassLoaderData* add_to_graph(Handle class_loader, bool has_class_mirror_holder); + static ClassLoaderData* add(Handle class_loader, bool has_class_mirror_holder); public: static ClassLoaderData* find_or_create(Handle class_loader); @@ -76,7 +76,7 @@ // Walking classes through the ClassLoaderDataGraph include array classes. It also includes // classes that are allocated but not loaded, classes that have errors, and scratch classes // for redefinition. These classes are removed during the next class unloading. - // Walking the ClassLoaderDataGraph also includes unsafe anonymous classes. + // Walking the ClassLoaderDataGraph also includes hidden and unsafe anonymous classes. static void classes_do(KlassClosure* klass_closure); static void classes_do(void f(Klass* const)); static void methods_do(void f(Method*)); diff --git a/src/hotspot/share/classfile/classLoaderExt.cpp b/src/hotspot/share/classfile/classLoaderExt.cpp --- a/src/hotspot/share/classfile/classLoaderExt.cpp +++ b/src/hotspot/share/classfile/classLoaderExt.cpp @@ -284,13 +284,12 @@ ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data(); Handle protection_domain; + ClassLoadInfo cl_info(protection_domain); InstanceKlass* result = KlassFactory::create_from_stream(stream, name, loader_data, - protection_domain, - NULL, // unsafe_anonymous_host - NULL, // cp_patches + cl_info, THREAD); if (HAS_PENDING_EXCEPTION) { diff --git a/src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp b/src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp --- a/src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp +++ b/src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -129,7 +129,7 @@ class LoaderTreeNode : public ResourceObj { - // We walk the CLDG and, for each CLD which is non-unsafe_anonymous, add + // We walk the CLDG and, for each CLD which is findable, add // a tree node. // To add a node we need its parent node; if the parent node does not yet // exist - because we have not yet encountered the CLD for the parent loader - @@ -149,6 +149,9 @@ LoadedClassInfo* _anon_classes; int _num_anon_classes; + LoadedClassInfo* _hidden_classes; + int _num_hidden_classes; + // In default view, similar tree nodes (same loader class, same name or no name) // are folded into each other to make the output more readable. // _num_folded contains the number of nodes which have been folded into this @@ -177,6 +180,7 @@ if (_cld->is_the_null_class_loader_data()) { st->print(" "); } else { + assert(!_cld->has_class_mirror_holder(), "_cld must be the primary cld"); if (loader_name != NULL) { st->print(" \"%s\",", loader_name->as_C_string()); } @@ -220,7 +224,7 @@ if (print_classes) { if (_classes != NULL) { for (LoadedClassInfo* lci = _classes; lci; lci = lci->_next) { - // Non-unsafe anonymous classes should live in the primary CLD of its loader + // non-strong hidden and unsafe anonymous classes should not live in the primary CLD of their loaders. assert(lci->_cld == _cld, "must be"); branchtracker.print(st); @@ -258,7 +262,8 @@ st->print("%*s ", indentation, ""); } st->print("%s", lci->_klass->external_name()); - // For unsafe anonymous classes, also print CLD if verbose. Should be a different one than the primary CLD. + // For unsafe anonymous classes, also print CLD if verbose. Should + // be a different one than the primary CLD. assert(lci->_cld != _cld, "must be"); if (verbose) { st->print(" (Loader Data: " PTR_FORMAT ")", p2i(lci->_cld)); @@ -267,7 +272,35 @@ } branchtracker.print(st); st->print("%*s ", indentation, ""); - st->print_cr("(%u unsafe anonymous class%s)", _num_anon_classes, (_num_anon_classes == 1) ? "" : "es"); + st->print_cr("(%u unsafe anonymous class%s)", _num_anon_classes, + (_num_anon_classes == 1) ? "" : "es"); + + // Empty line + branchtracker.print(st); + st->cr(); + } + + if (_hidden_classes != NULL) { + for (LoadedClassInfo* lci = _hidden_classes; lci; lci = lci->_next) { + branchtracker.print(st); + if (lci == _hidden_classes) { // first iteration + st->print("%*s ", indentation, "Hidden Classes:"); + } else { + st->print("%*s ", indentation, ""); + } + st->print("%s", lci->_klass->external_name()); + // For non-strong hidden classes, also print CLD if verbose. Should be a + // different one than the primary CLD. + assert(lci->_cld != _cld, "must be"); + if (verbose) { + st->print(" (Loader Data: " PTR_FORMAT ")", p2i(lci->_cld)); + } + st->cr(); + } + branchtracker.print(st); + st->print("%*s ", indentation, ""); + st->print_cr("(%u hidden class%s)", _num_hidden_classes, + (_num_hidden_classes == 1) ? "" : "es"); // Empty line branchtracker.print(st); @@ -301,6 +334,7 @@ LoaderTreeNode(const oop loader_oop) : _loader_oop(loader_oop), _cld(NULL), _child(NULL), _next(NULL), _classes(NULL), _num_classes(0), _anon_classes(NULL), _num_anon_classes(0), + _hidden_classes(NULL), _num_hidden_classes(0), _num_folded(0) {} @@ -319,15 +353,25 @@ _next = info; } - void add_classes(LoadedClassInfo* first_class, int num_classes, bool is_unsafe_anonymous) { - LoadedClassInfo** p_list_to_add_to = is_unsafe_anonymous ? &_anon_classes : &_classes; + void add_classes(LoadedClassInfo* first_class, int num_classes, bool has_class_mirror_holder) { + LoadedClassInfo** p_list_to_add_to; + bool is_hidden = first_class->_klass->is_hidden(); + if (has_class_mirror_holder) { + p_list_to_add_to = is_hidden ? &_hidden_classes : &_anon_classes; + } else { + p_list_to_add_to = &_classes; + } // Search tail. while ((*p_list_to_add_to) != NULL) { p_list_to_add_to = &(*p_list_to_add_to)->_next; } *p_list_to_add_to = first_class; - if (is_unsafe_anonymous) { - _num_anon_classes += num_classes; + if (has_class_mirror_holder) { + if (is_hidden) { + _num_hidden_classes += num_classes; + } else { + _num_anon_classes += num_classes; + } } else { _num_classes += num_classes; } @@ -421,7 +465,7 @@ LoadedClassCollectClosure lccc(cld); const_cast(cld)->classes_do(&lccc); if (lccc._num_classes > 0) { - info->add_classes(lccc._list, lccc._num_classes, cld->is_unsafe_anonymous()); + info->add_classes(lccc._list, lccc._num_classes, cld->has_class_mirror_holder()); } } @@ -481,7 +525,7 @@ assert(info != NULL, "must be"); // Update CLD in node, but only if this is the primary CLD for this loader. - if (cld->is_unsafe_anonymous() == false) { + if (cld->has_class_mirror_holder() == false) { assert(info->cld() == NULL, "there should be only one primary CLD per loader"); info->set_cld(cld); } diff --git a/src/hotspot/share/classfile/classLoaderStats.cpp b/src/hotspot/share/classfile/classLoaderStats.cpp --- a/src/hotspot/share/classfile/classLoaderStats.cpp +++ b/src/hotspot/share/classfile/classLoaderStats.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, 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 @@ -26,6 +26,7 @@ #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/classLoaderStats.hpp" +#include "oops/objArrayKlass.hpp" #include "oops/oop.inline.hpp" #include "utilities/globalDefinitions.hpp" @@ -59,7 +60,7 @@ cls = *cls_ptr; } - if (!cld->is_unsafe_anonymous()) { + if (!cld->has_class_mirror_holder()) { cls->_cld = cld; } @@ -71,8 +72,20 @@ ClassStatsClosure csc; cld->classes_do(&csc); - if(cld->is_unsafe_anonymous()) { - cls->_anon_classes_count += csc._num_classes; + bool is_hidden = false; + if(cld->has_class_mirror_holder()) { + // if cld has a class holder then it must be either hidden or unsafe anonymous. + Klass* k = cld->klasses(); + // if it's an array class then need to see if bottom class is hidden. + if (k->is_array_klass()) { + k = ObjArrayKlass::cast(k)->bottom_klass(); + } + is_hidden = k->is_hidden(); + if (is_hidden) { + cls->_hidden_classes_count += csc._num_classes; + } else { + cls->_anon_classes_count += csc._num_classes; + } } else { cls->_classes_count = csc._num_classes; } @@ -80,9 +93,14 @@ ClassLoaderMetaspace* ms = cld->metaspace_or_null(); if (ms != NULL) { - if(cld->is_unsafe_anonymous()) { - cls->_anon_chunk_sz += ms->allocated_chunks_bytes(); - cls->_anon_block_sz += ms->allocated_blocks_bytes(); + if(cld->has_class_mirror_holder()) { + if (is_hidden) { + cls->_hidden_chunk_sz += ms->allocated_chunks_bytes(); + cls->_hidden_block_sz += ms->allocated_blocks_bytes(); + } else { + cls->_anon_chunk_sz += ms->allocated_chunks_bytes(); + cls->_anon_block_sz += ms->allocated_blocks_bytes(); + } } else { cls->_chunk_sz = ms->allocated_chunks_bytes(); cls->_block_sz = ms->allocated_blocks_bytes(); @@ -121,6 +139,12 @@ cls->_anon_classes_count, cls->_anon_chunk_sz, cls->_anon_block_sz); } + if (cls->_hidden_classes_count > 0) { + _out->print_cr(SPACE SPACE SPACE " " UINTX_FORMAT_W(6) " " SIZE_FORMAT_W(8) " " SIZE_FORMAT_W(8) " + hidden classes", + "", "", "", + cls->_hidden_classes_count, + cls->_hidden_chunk_sz, cls->_hidden_block_sz); + } return true; } diff --git a/src/hotspot/share/classfile/classLoaderStats.hpp b/src/hotspot/share/classfile/classLoaderStats.hpp --- a/src/hotspot/share/classfile/classLoaderStats.hpp +++ b/src/hotspot/share/classfile/classLoaderStats.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, 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 @@ -81,6 +81,10 @@ size_t _anon_block_sz; uintx _anon_classes_count; + size_t _hidden_chunk_sz; + size_t _hidden_block_sz; + uintx _hidden_classes_count; + ClassLoaderStats() : _cld(0), _class_loader(0), @@ -90,7 +94,10 @@ _classes_count(0), _anon_chunk_sz(0), _anon_block_sz(0), - _anon_classes_count(0) { + _anon_classes_count(0), + _hidden_chunk_sz(0), + _hidden_block_sz(0), + _hidden_classes_count(0) { } }; diff --git a/src/hotspot/share/classfile/defaultMethods.cpp b/src/hotspot/share/classfile/defaultMethods.cpp --- a/src/hotspot/share/classfile/defaultMethods.cpp +++ b/src/hotspot/share/classfile/defaultMethods.cpp @@ -918,7 +918,7 @@ ConstantPool* cp = bpool->create_constant_pool(CHECK); if (cp != klass->constants()) { // Copy resolved anonymous class into new constant pool. - if (klass->is_unsafe_anonymous()) { + if (klass->is_unsafe_anonymous() || klass->is_hidden()) { cp->klass_at_put(klass->this_class_index(), klass); } klass->class_loader_data()->add_to_deallocate_list(klass->constants()); diff --git a/src/hotspot/share/classfile/dictionary.cpp b/src/hotspot/share/classfile/dictionary.cpp --- a/src/hotspot/share/classfile/dictionary.cpp +++ b/src/hotspot/share/classfile/dictionary.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, 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 @@ -354,6 +354,7 @@ // since been unreferenced, so this entry should be cleared. void Dictionary::clean_cached_protection_domains() { assert_locked_or_safepoint(SystemDictionary_lock); + assert(!loader_data()->has_class_mirror_holder(), "cld should have a ClassLoader holder not a Class holder"); if (loader_data()->is_the_null_class_loader_data()) { // Classes in the boot loader are not loaded with protection domains @@ -482,6 +483,7 @@ ResourceMark rm; assert(loader_data() != NULL, "loader data should not be null"); + assert(!loader_data()->has_class_mirror_holder(), "cld should have a ClassLoader holder not a Class holder"); st->print_cr("Java dictionary (table_size=%d, classes=%d, resizable=%s)", table_size(), number_of_entries(), BOOL_TO_STR(_resizable)); st->print_cr("^ indicates that initiating loader is different from defining loader"); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -852,12 +852,13 @@ k->clear_has_raw_archived_mirror(); } } - create_mirror(k, Handle(), Handle(), Handle(), CHECK); + create_mirror(k, Handle(), Handle(), Handle(), Handle(), CHECK); } void java_lang_Class::initialize_mirror_fields(Klass* k, Handle mirror, Handle protection_domain, + Handle classData, TRAPS) { // Allocate a simple java object for a lock. // This needs to be a java object because during class initialization @@ -870,6 +871,9 @@ // Initialize static fields InstanceKlass::cast(k)->do_local_static_fields(&initialize_static_field, mirror, CHECK); + + // Set classData + set_class_data(mirror(), classData()); } // Set the java.lang.Module module field in the java_lang_Class mirror @@ -923,7 +927,8 @@ } void java_lang_Class::create_mirror(Klass* k, Handle class_loader, - Handle module, Handle protection_domain, TRAPS) { + Handle module, Handle protection_domain, + Handle classData, TRAPS) { assert(k != NULL, "Use create_basic_type_mirror for primitive types"); assert(k->java_mirror() == NULL, "should only assign mirror once"); @@ -970,7 +975,7 @@ } else { assert(k->is_instance_klass(), "Must be"); - initialize_mirror_fields(k, mirror, protection_domain, THREAD); + initialize_mirror_fields(k, mirror, protection_domain, classData, THREAD); if (HAS_PENDING_EXCEPTION) { // If any of the fields throws an exception like OOM remove the klass field // from the mirror so GC doesn't follow it after the klass has been deallocated. @@ -1397,6 +1402,14 @@ java_class->obj_field_put(_signers_offset, (oop)signers); } +oop java_lang_Class::class_data(oop java_class) { + assert(_classData_offset != 0, "must be set"); + return java_class->obj_field(_classData_offset); +} +void java_lang_Class::set_class_data(oop java_class, oop class_data) { + assert(_classData_offset != 0, "must be set"); + java_class->obj_field_put(_classData_offset, class_data); +} void java_lang_Class::set_class_loader(oop java_class, oop loader) { assert(_class_loader_offset != 0, "offsets should have been initialized"); @@ -1600,6 +1613,7 @@ macro(_component_mirror_offset, k, "componentType", class_signature, false); \ macro(_module_offset, k, "module", module_signature, false); \ macro(_name_offset, k, "name", string_signature, false); \ + macro(_classData_offset, k, "classData", object_signature, false); void java_lang_Class::compute_offsets() { if (offsets_computed) { @@ -4268,6 +4282,7 @@ int java_lang_Class::_signers_offset; int java_lang_Class::_name_offset; int java_lang_Class::_source_file_offset; +int java_lang_Class::_classData_offset; GrowableArray* java_lang_Class::_fixup_mirror_list = NULL; GrowableArray* java_lang_Class::_fixup_module_field_list = NULL; int java_lang_Throwable::backtrace_offset; diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2020, 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 @@ -263,6 +263,7 @@ static int _component_mirror_offset; static int _name_offset; static int _source_file_offset; + static int _classData_offset; static bool offsets_computed; static int classRedefinedCount_offset; @@ -274,7 +275,8 @@ static void set_protection_domain(oop java_class, oop protection_domain); static void set_class_loader(oop java_class, oop class_loader); static void set_component_mirror(oop java_class, oop comp_mirror); - static void initialize_mirror_fields(Klass* k, Handle mirror, Handle protection_domain, TRAPS); + static void initialize_mirror_fields(Klass* k, Handle mirror, Handle protection_domain, + Handle classData, TRAPS); static void set_mirror_module_field(Klass* K, Handle mirror, Handle module, TRAPS); public: static void allocate_fixup_lists(); @@ -282,7 +284,7 @@ // Instance creation static void create_mirror(Klass* k, Handle class_loader, Handle module, - Handle protection_domain, TRAPS); + Handle protection_domain, Handle classData, TRAPS); static void fixup_mirror(Klass* k, TRAPS); static oop create_basic_type_mirror(const char* basic_type_name, BasicType type, TRAPS); static void update_archived_primitive_mirror_native_pointers(oop archived_mirror) NOT_CDS_JAVA_HEAP_RETURN; @@ -330,6 +332,8 @@ static oop component_mirror(oop java_class); static objArrayOop signers(oop java_class); static void set_signers(oop java_class, objArrayOop signers); + static oop class_data(oop java_class); + static void set_class_data(oop java_class, oop classData); static oop class_loader(oop java_class); static void set_module(oop java_class, oop module); @@ -1142,16 +1146,20 @@ // Relevant integer codes (keep these in synch. with MethodHandleNatives.Constants): enum { - MN_IS_METHOD = 0x00010000, // method (not constructor) - MN_IS_CONSTRUCTOR = 0x00020000, // constructor - MN_IS_FIELD = 0x00040000, // field - MN_IS_TYPE = 0x00080000, // nested type - MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected - MN_REFERENCE_KIND_SHIFT = 24, // refKind - MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT, + MN_IS_METHOD = 0x00010000, // method (not constructor) + MN_IS_CONSTRUCTOR = 0x00020000, // constructor + MN_IS_FIELD = 0x00040000, // field + MN_IS_TYPE = 0x00080000, // nested type + MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected + MN_REFERENCE_KIND_SHIFT = 24, // refKind + MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT, // The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers: - MN_SEARCH_SUPERCLASSES = 0x00100000, // walk super classes - MN_SEARCH_INTERFACES = 0x00200000 // walk implemented interfaces + MN_SEARCH_SUPERCLASSES = 0x00100000, // walk super classes + MN_SEARCH_INTERFACES = 0x00200000, // walk implemented interfaces + MN_NESTMATE_CLASS = 0x00000001, + MN_HIDDEN_CLASS = 0x00000002, + MN_STRONG_LOADER_LINK = 0x00000004, + MN_ACCESS_VM_ANNOTATIONS = 0x00000008 }; // Accessors for code generation: diff --git a/src/hotspot/share/classfile/klassFactory.cpp b/src/hotspot/share/classfile/klassFactory.cpp --- a/src/hotspot/share/classfile/klassFactory.cpp +++ b/src/hotspot/share/classfile/klassFactory.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2015, 2020, 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 @@ -79,16 +79,18 @@ end_ptr - ptr, cfs->source(), ClassFileStream::verify); + ClassLoadInfo cl_info(protection_domain); ClassFileParser parser(stream, class_name, loader_data, - protection_domain, - NULL, - NULL, + &cl_info, ClassFileParser::BROADCAST, // publicity level CHECK_NULL); - InstanceKlass* new_ik = parser.create_instance_klass(true /* changed_by_loadhook */, + const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr(); + InstanceKlass* new_ik = parser.create_instance_klass(true, // changed_by_loadhook + *cl_inst_info, // dynamic_nest_host and classData CHECK_NULL); + if (cached_class_file != NULL) { new_ik->set_cached_class_file(cached_class_file); } @@ -165,9 +167,7 @@ InstanceKlass* KlassFactory::create_from_stream(ClassFileStream* stream, Symbol* name, ClassLoaderData* loader_data, - Handle protection_domain, - const InstanceKlass* unsafe_anonymous_host, - GrowableArray* cp_patches, + const ClassLoadInfo& cl_info, TRAPS) { assert(stream != NULL, "invariant"); assert(loader_data != NULL, "invariant"); @@ -183,12 +183,15 @@ // increment counter THREAD->statistical_info().incr_define_class_count(); - // Skip this processing for VM anonymous classes - if (unsafe_anonymous_host == NULL) { + assert(!(cl_info.is_hidden() && (cl_info.unsafe_anonymous_host() != NULL)), + "hidden class has an anonymous host"); + + // Skip this processing for VM hidden or anonymous classes + if (!cl_info.is_hidden() && (cl_info.unsafe_anonymous_host() == NULL)) { stream = check_class_file_load_hook(stream, name, loader_data, - protection_domain, + cl_info.protection_domain(), &cached_class_file, CHECK_NULL); } @@ -196,14 +199,12 @@ ClassFileParser parser(stream, name, loader_data, - protection_domain, - unsafe_anonymous_host, - cp_patches, + &cl_info, ClassFileParser::BROADCAST, // publicity level CHECK_NULL); - InstanceKlass* result = parser.create_instance_klass(old_stream != stream, CHECK_NULL); - assert(result == parser.create_instance_klass(old_stream != stream, THREAD), "invariant"); + const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr(); + InstanceKlass* result = parser.create_instance_klass(old_stream != stream, *cl_inst_info, CHECK_NULL); if (result == NULL) { return NULL; diff --git a/src/hotspot/share/classfile/klassFactory.hpp b/src/hotspot/share/classfile/klassFactory.hpp --- a/src/hotspot/share/classfile/klassFactory.hpp +++ b/src/hotspot/share/classfile/klassFactory.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2015, 2020, 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 @@ -30,6 +30,7 @@ class ClassFileStream; class ClassLoaderData; +class ClassLoadInfo; template class GrowableArray; class Klass; @@ -71,9 +72,7 @@ static InstanceKlass* create_from_stream(ClassFileStream* stream, Symbol* name, ClassLoaderData* loader_data, - Handle protection_domain, - const InstanceKlass* unsafe_anonymous_host, - GrowableArray* cp_patches, + const ClassLoadInfo& cl_info, TRAPS); public: static InstanceKlass* check_shared_class_file_load_hook( diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2020, 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 @@ -75,6 +75,7 @@ const char* loc = location()->as_C_string(); ClassLoaderData* cld = loader_data(); + assert(!cld->has_class_mirror_holder(), "module's cld should have a ClassLoader holder not a Class holder"); if ((cld->is_the_null_class_loader_data() || cld->is_platform_class_loader_data()) && (strncmp(loc, "jrt:/java.", 10) == 0)) { return false; @@ -135,6 +136,7 @@ // injecting dependencies that require the default read edges for resolution. if (this->has_default_read_edges() && !m->is_named()) { ClassLoaderData* cld = m->loader_data(); + assert(!cld->has_class_mirror_holder(), "module's cld should have a ClassLoader holder not a Class holder"); if (cld->is_the_null_class_loader_data() || cld->is_system_class_loader_data()) { return true; // default read edge } diff --git a/src/hotspot/share/classfile/moduleEntry.hpp b/src/hotspot/share/classfile/moduleEntry.hpp --- a/src/hotspot/share/classfile/moduleEntry.hpp +++ b/src/hotspot/share/classfile/moduleEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2020, 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 @@ -110,7 +110,7 @@ ClassLoaderData* loader_data() const { return _loader_data; } void set_loader_data(ClassLoaderData* cld) { - assert(!cld->is_unsafe_anonymous(), "Unexpected unsafe anonymous class loader data"); + assert(!cld->has_class_mirror_holder(), "Unexpected has_class_mirror_holder cld"); _loader_data = cld; } diff --git a/src/hotspot/share/classfile/resolutionErrors.cpp b/src/hotspot/share/classfile/resolutionErrors.cpp --- a/src/hotspot/share/classfile/resolutionErrors.cpp +++ b/src/hotspot/share/classfile/resolutionErrors.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2020, 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 @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "classfile/resolutionErrors.hpp" +#include "memory/allocation.hpp" #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" #include "runtime/handles.inline.hpp" @@ -42,6 +43,18 @@ add_entry(index, entry); } +// add new entry to the table +void ResolutionErrorTable::add_entry(int index, unsigned int hash, + const constantPoolHandle& pool, int cp_index, + const char* message) +{ + assert_locked_or_safepoint(SystemDictionary_lock); + assert(!pool.is_null() && message != NULL, "adding NULL obj"); + + ResolutionErrorEntry* entry = new_entry(hash, pool(), cp_index, message); + add_entry(index, entry); +} + // find entry in the table ResolutionErrorEntry* ResolutionErrorTable::find_entry(int index, unsigned int hash, const constantPoolHandle& pool, int cp_index) @@ -59,9 +72,10 @@ } void ResolutionErrorEntry::set_error(Symbol* e) { - assert(e != NULL, "must set a value"); _error = e; - _error->increment_refcount(); + if (_error != NULL) { + _error->increment_refcount(); + } } void ResolutionErrorEntry::set_message(Symbol* c) { @@ -71,6 +85,10 @@ } } +void ResolutionErrorEntry::set_nest_host_error(const char* message) { + _nest_host_error = message; +} + // create new error entry ResolutionErrorEntry* ResolutionErrorTable::new_entry(int hash, ConstantPool* pool, int cp_index, Symbol* error, @@ -80,17 +98,35 @@ entry->set_cp_index(cp_index); entry->set_error(error); entry->set_message(message); + entry->set_nest_host_error(NULL); + + return entry; +} + +// create new nest host error entry +ResolutionErrorEntry* ResolutionErrorTable::new_entry(int hash, ConstantPool* pool, + int cp_index, const char* message) +{ + ResolutionErrorEntry* entry = (ResolutionErrorEntry*)Hashtable::new_entry(hash, pool); + entry->set_cp_index(cp_index); + entry->set_nest_host_error(message); + entry->set_error(NULL); + entry->set_message(NULL); return entry; } void ResolutionErrorTable::free_entry(ResolutionErrorEntry *entry) { // decrement error refcount - assert(entry->error() != NULL, "error should be set"); - entry->error()->decrement_refcount(); + if (entry->error() != NULL) { + entry->error()->decrement_refcount(); + } if (entry->message() != NULL) { entry->message()->decrement_refcount(); } + if (entry->nest_host_error() != NULL) { + FREE_C_HEAP_ARRAY(char, entry->nest_host_error()); + } Hashtable::free_entry(entry); } diff --git a/src/hotspot/share/classfile/resolutionErrors.hpp b/src/hotspot/share/classfile/resolutionErrors.hpp --- a/src/hotspot/share/classfile/resolutionErrors.hpp +++ b/src/hotspot/share/classfile/resolutionErrors.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2020, 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 @@ -46,6 +46,8 @@ ResolutionErrorEntry* new_entry(int hash, ConstantPool* pool, int cp_index, Symbol* error, Symbol* message); + ResolutionErrorEntry* new_entry(int hash, ConstantPool* pool, int cp_index, + const char* message); void free_entry(ResolutionErrorEntry *entry); ResolutionErrorEntry* bucket(int i) { @@ -64,6 +66,8 @@ void add_entry(int index, unsigned int hash, const constantPoolHandle& pool, int which, Symbol* error, Symbol* message); + void add_entry(int index, unsigned int hash, + const constantPoolHandle& pool, int which, const char* message); // find error given the constant pool and constant pool index ResolutionErrorEntry* find_entry(int index, unsigned int hash, @@ -95,6 +99,7 @@ int _cp_index; Symbol* _error; Symbol* _message; + const char* _nest_host_error; public: ConstantPool* pool() const { return literal(); } @@ -108,6 +113,9 @@ Symbol* message() const { return _message; } void set_message(Symbol* c); + const char* nest_host_error() const { return _nest_host_error; } + void set_nest_host_error(const char* message); + ResolutionErrorEntry* next() const { return (ResolutionErrorEntry*)HashtableEntry::next(); } diff --git a/src/hotspot/share/classfile/symbolTable.cpp b/src/hotspot/share/classfile/symbolTable.cpp --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.cpp @@ -459,6 +459,8 @@ void SymbolTable::new_symbols(ClassLoaderData* loader_data, const constantPoolHandle& cp, int names_count, const char** names, int* lengths, int* cp_indices, unsigned int* hashValues) { + // Note that c_heap will be true for non-strong hidden classes and unsafe anonymous classes + // even if their loader is the boot loader because they will have a different cld. bool c_heap = !loader_data->is_the_null_class_loader_data(); for (int i = 0; i < names_count; i++) { const char *name = names[i]; diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -111,6 +111,46 @@ const int defaultProtectionDomainCacheSize = 1009; +ClassLoadInfo::ClassLoadInfo() { + _protection_domain = Handle(); + _unsafe_anonymous_host = NULL; + _cp_patches = NULL; + _class_hidden_info._dynamic_nest_host = NULL; + _class_hidden_info._class_data = Handle(); + _is_hidden = false; + _is_strong_hidden = false; + _can_access_vm_annotations = false; +} + +ClassLoadInfo::ClassLoadInfo(Handle protection_domain) { + _protection_domain = protection_domain; + _unsafe_anonymous_host = NULL; + _cp_patches = NULL; + _class_hidden_info._dynamic_nest_host = NULL; + _class_hidden_info._class_data = Handle(); + _is_hidden = false; + _is_strong_hidden = false; + _can_access_vm_annotations = false; +} + +ClassLoadInfo::ClassLoadInfo(Handle protection_domain, + const InstanceKlass* unsafe_anonymous_host, + GrowableArray* cp_patches, + InstanceKlass* dynamic_nest_host, + Handle class_data, + bool is_hidden, + bool is_strong_hidden, + bool can_access_vm_annotations) { + _protection_domain = protection_domain; + _unsafe_anonymous_host = unsafe_anonymous_host; + _cp_patches = cp_patches; + _class_hidden_info._dynamic_nest_host = dynamic_nest_host; + _class_hidden_info._class_data = class_data; + _is_hidden = is_hidden; + _is_strong_hidden = is_strong_hidden; + _can_access_vm_annotations = can_access_vm_annotations; +} + // ---------------------------------------------------------------------------- // Java-level SystemLoader and PlatformLoader @@ -822,7 +862,7 @@ // class loaders holding the ObjectLock shouldn't find the class here InstanceKlass* check = find_class(d_hash, name, dictionary); if (check != NULL) { - // Klass is already loaded, so return it after checking/adding protection domain + // Klass is already loaded, so return it after checking/adding protection domain k = check; class_has_been_loaded = true; } @@ -982,24 +1022,36 @@ // Note: this method is much like resolve_from_stream, but // does not publish the classes via the SystemDictionary. -// Handles unsafe_DefineAnonymousClass and redefineclasses -// RedefinedClasses do not add to the class hierarchy +// Handles Lookup.defineClass hidden, unsafe_DefineAnonymousClass +// and redefineclasses. RedefinedClasses do not add to the class hierarchy. InstanceKlass* SystemDictionary::parse_stream(Symbol* class_name, Handle class_loader, - Handle protection_domain, ClassFileStream* st, - const InstanceKlass* unsafe_anonymous_host, - GrowableArray* cp_patches, + const ClassLoadInfo& cl_info, TRAPS) { EventClassLoad class_load_start_event; ClassLoaderData* loader_data; - if (unsafe_anonymous_host != NULL) { - // Create a new CLD for an unsafe anonymous class, that uses the same class loader - // as the unsafe_anonymous_host - guarantee(unsafe_anonymous_host->class_loader() == class_loader(), "should be the same"); - loader_data = ClassLoaderData::unsafe_anonymous_class_loader_data(class_loader); + + bool is_unsafe_anon_class = cl_info.unsafe_anonymous_host() != NULL; + + if (is_unsafe_anon_class) { + // - for unsafe anonymous class: create a new CLD whith a class holder that uses + // the same class loader as the unsafe_anonymous_host. + guarantee(cl_info.unsafe_anonymous_host()->class_loader() == class_loader(), + "should be the same"); + loader_data = ClassLoaderData::has_class_mirror_holder_cld(class_loader); + } else if (cl_info.is_hidden()) { + // - for hidden classes that are not strong: create a new CLD that has a class holder and + // whose loader is the Lookup class' loader. + // - for hidden class: add the class to the Lookup class' loader's CLD. + if (!cl_info.is_strong_hidden()) { + loader_data = ClassLoaderData::has_class_mirror_holder_cld(class_loader); + } else { + // This hidden class goes into the regular CLD pool for this loader. + loader_data = register_loader(class_loader); + } } else { loader_data = ClassLoaderData::class_loader_data(class_loader()); } @@ -1015,15 +1067,16 @@ InstanceKlass* k = KlassFactory::create_from_stream(st, class_name, loader_data, - protection_domain, - unsafe_anonymous_host, - cp_patches, + cl_info, CHECK_NULL); - if (unsafe_anonymous_host != NULL && k != NULL) { - // Unsafe anonymous classes must update ClassLoaderData holder (was unsafe_anonymous_host loader) - // so that they can be unloaded when the mirror is no longer referenced. - k->class_loader_data()->initialize_holder(Handle(THREAD, k->java_mirror())); + if ((cl_info.is_hidden() || is_unsafe_anon_class) && k != NULL) { + // Hidden classes that are not strong and unsafe anonymous classes must update + // ClassLoaderData holder so that they can be unloaded when the mirror is no + // longer referenced. + if (!cl_info.is_strong_hidden() || is_unsafe_anon_class) { + k->class_loader_data()->initialize_holder(Handle(THREAD, k->java_mirror())); + } { MutexLocker mu_r(THREAD, Compile_lock); @@ -1036,12 +1089,14 @@ // Rewrite and patch constant pool here. k->link_class(CHECK_NULL); - if (cp_patches != NULL) { - k->constants()->patch_resolved_references(cp_patches); + if (cl_info.cp_patches() != NULL) { + k->constants()->patch_resolved_references(cl_info.cp_patches()); } // If it's anonymous, initialize it now, since nobody else will. - k->eager_initialize(CHECK_NULL); + if (is_unsafe_anon_class) { + k->eager_initialize(CHECK_NULL); + } // notify jvmti if (JvmtiExport::should_post_class_load()) { @@ -1052,7 +1107,7 @@ post_class_load_event(&class_load_start_event, k, loader_data); } } - assert(unsafe_anonymous_host != NULL || NULL == cp_patches, + assert(is_unsafe_anon_class || NULL == cl_info.cp_patches(), "cp_patches only found with unsafe_anonymous_host"); return k; @@ -1107,13 +1162,8 @@ if (st->buffer() == NULL) { return NULL; } - k = KlassFactory::create_from_stream(st, - class_name, - loader_data, - protection_domain, - NULL, // unsafe_anonymous_host - NULL, // cp_patches - CHECK_NULL); + ClassLoadInfo cl_info(protection_domain); + k = KlassFactory::create_from_stream(st, class_name, loader_data, cl_info, CHECK_NULL); } assert(k != NULL, "no klass created"); @@ -2327,6 +2377,42 @@ } } +// Add an entry to resolution error table to record an error in resolving or +// validating a nest host. This is used to construct informative error +// messages when IllegalAccessError's occur. If an entry already exists it will +// be updated with the nest host error message. +void SystemDictionary::add_nest_host_error(const constantPoolHandle& pool, + int which, + const char* message) { + unsigned int hash = resolution_errors()->compute_hash(pool, which); + int index = resolution_errors()->hash_to_index(hash); + { + MutexLocker ml(Thread::current(), SystemDictionary_lock); + ResolutionErrorEntry* entry = resolution_errors()->find_entry(index, hash, pool, which); + if (entry != NULL) { + assert(entry->nest_host_error() == NULL, "Nest host error message already set!"); + entry->set_nest_host_error(message); + } else { + resolution_errors()->add_entry(index, hash, pool, which, message); + } + } +} + +// Lookup any nest host error +const char* SystemDictionary::find_nest_host_error(const constantPoolHandle& pool, int which) { + unsigned int hash = resolution_errors()->compute_hash(pool, which); + int index = resolution_errors()->hash_to_index(hash); + { + MutexLocker ml(Thread::current(), SystemDictionary_lock); + ResolutionErrorEntry* entry = resolution_errors()->find_entry(index, hash, pool, which); + if (entry != NULL) { + return entry->nest_host_error(); + } else { + return NULL; + } + } +} + // Signature constraints ensure that callers and callees agree about // the meaning of type names in their signatures. This routine is the diff --git a/src/hotspot/share/classfile/systemDictionary.hpp b/src/hotspot/share/classfile/systemDictionary.hpp --- a/src/hotspot/share/classfile/systemDictionary.hpp +++ b/src/hotspot/share/classfile/systemDictionary.hpp @@ -34,6 +34,53 @@ #include "runtime/signature.hpp" #include "utilities/hashtable.hpp" +class ClassInstanceInfo : public StackObj { + private: + InstanceKlass* _dynamic_nest_host; + Handle _class_data; + + public: + ClassInstanceInfo() { + _dynamic_nest_host = NULL; + _class_data = Handle(); + } + ClassInstanceInfo(InstanceKlass* dynamic_nest_host, Handle class_data) { + _dynamic_nest_host = dynamic_nest_host; + _class_data = class_data; + } + + InstanceKlass* dynamic_nest_host() const { return _dynamic_nest_host; } + Handle class_data() const { return _class_data; } + friend class ClassLoadInfo; +}; + +class ClassLoadInfo : public StackObj { + private: + Handle _protection_domain; + const InstanceKlass* _unsafe_anonymous_host; + GrowableArray* _cp_patches; + ClassInstanceInfo _class_hidden_info; + bool _is_hidden; + bool _is_strong_hidden; + bool _can_access_vm_annotations; + + public: + ClassLoadInfo(); + ClassLoadInfo(Handle protection_domain); + ClassLoadInfo(Handle protection_domain, const InstanceKlass* unsafe_anonymous_host, + GrowableArray* cp_patches, InstanceKlass* dynamic_nest_host, + Handle class_data, bool is_hidden, bool is_strong_hidden, + bool can_access_vm_annotations); + + Handle protection_domain() const { return _protection_domain; } + const InstanceKlass* unsafe_anonymous_host() const { return _unsafe_anonymous_host; } + GrowableArray* cp_patches() const { return _cp_patches; } + const ClassInstanceInfo* class_hidden_info_ptr() const { return &_class_hidden_info; } + bool is_hidden() const { return _is_hidden; } + bool is_strong_hidden() const { return _is_strong_hidden; } + bool can_access_vm_annotations() const { return _can_access_vm_annotations; } +}; + // The dictionary in each ClassLoaderData stores all loaded classes, either // initiatied by its class loader or defined by its class loader: // @@ -271,28 +318,13 @@ bool is_superclass, TRAPS); - // Parse new stream. This won't update the dictionary or - // class hierarchy, simply parse the stream. Used by JVMTI RedefineClasses. - // Also used by Unsafe_DefineAnonymousClass + // Parse new stream. This won't update the dictionary or class + // hierarchy, simply parse the stream. Used by JVMTI RedefineClasses + // and by Unsafe_DefineAnonymousClass and jvm_lookup_define_class. static InstanceKlass* parse_stream(Symbol* class_name, Handle class_loader, - Handle protection_domain, ClassFileStream* st, - TRAPS) { - return parse_stream(class_name, - class_loader, - protection_domain, - st, - NULL, // unsafe_anonymous_host - NULL, // cp_patches - THREAD); - } - static InstanceKlass* parse_stream(Symbol* class_name, - Handle class_loader, - Handle protection_domain, - ClassFileStream* st, - const InstanceKlass* unsafe_anonymous_host, - GrowableArray* cp_patches, + const ClassLoadInfo& cl_info, TRAPS); // Resolve from stream (called by jni_DefineClass and JVM_DefineClass) @@ -530,6 +562,11 @@ Symbol** message); + // Record a nest host resolution/validation error + static void add_nest_host_error(const constantPoolHandle& pool, int which, + const char* message); + static const char* find_nest_host_error(const constantPoolHandle& pool, int which); + static ProtectionDomainCacheEntry* cache_get(Handle protection_domain); protected: diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -914,7 +914,7 @@ if (!UseSharedSpaces) { return NULL; } - if (class_name == NULL) { // don't do this for anonymous classes + if (class_name == NULL) { // don't do this for hidden and unsafe anonymous classes return NULL; } if (class_loader.is_null() || @@ -1096,9 +1096,9 @@ } bool SystemDictionaryShared::should_be_excluded(InstanceKlass* k) { - if (k->class_loader_data()->is_unsafe_anonymous()) { - warn_excluded(k, "Unsafe anonymous class"); - return true; // unsafe anonymous classes are not archived, skip + if (k->is_hidden() || k->is_unsafe_anonymous()) { + warn_excluded(k, "Hidden or Unsafe anonymous class"); + return true; // hidden and unsafe anonymous classes are not archived, skip } if (k->is_in_error_state()) { warn_excluded(k, "In error state"); diff --git a/src/hotspot/share/classfile/verificationType.cpp b/src/hotspot/share/classfile/verificationType.cpp --- a/src/hotspot/share/classfile/verificationType.cpp +++ b/src/hotspot/share/classfile/verificationType.cpp @@ -48,11 +48,16 @@ bool VerificationType::resolve_and_check_assignability(InstanceKlass* klass, Symbol* name, Symbol* from_name, bool from_field_is_protected, bool from_is_array, bool from_is_object, TRAPS) { HandleMark hm(THREAD); - Klass* this_class = SystemDictionary::resolve_or_fail( + Klass* this_class; + if (klass->is_hidden() && klass->name() == name) { + this_class = klass; + } else { + this_class = SystemDictionary::resolve_or_fail( name, Handle(THREAD, klass->class_loader()), Handle(THREAD, klass->protection_domain()), true, CHECK_false); - if (log_is_enabled(Debug, class, resolve)) { - Verifier::trace_class_resolution(this_class, klass); + if (log_is_enabled(Debug, class, resolve)) { + Verifier::trace_class_resolution(this_class, klass); + } } if (this_class->is_interface() && (!from_field_is_protected || @@ -65,11 +70,16 @@ this_class == SystemDictionary::Cloneable_klass() || this_class == SystemDictionary::Serializable_klass(); } else if (from_is_object) { - Klass* from_class = SystemDictionary::resolve_or_fail( + Klass* from_class; + if (klass->is_hidden() && klass->name() == from_name) { + from_class = klass; + } else { + from_class = SystemDictionary::resolve_or_fail( from_name, Handle(THREAD, klass->class_loader()), Handle(THREAD, klass->protection_domain()), true, CHECK_false); - if (log_is_enabled(Debug, class, resolve)) { - Verifier::trace_class_resolution(from_class, klass); + if (log_is_enabled(Debug, class, resolve)) { + Verifier::trace_class_resolution(from_class, klass); + } } return from_class->is_subclass_of(this_class); } diff --git a/src/hotspot/share/classfile/verifier.cpp b/src/hotspot/share/classfile/verifier.cpp --- a/src/hotspot/share/classfile/verifier.cpp +++ b/src/hotspot/share/classfile/verifier.cpp @@ -2081,6 +2081,8 @@ oop loader = current_class()->class_loader(); oop protection_domain = current_class()->protection_domain(); + assert(name_in_supers(name, current_class()), "name should be a super class"); + Klass* kls = SystemDictionary::resolve_or_fail( name, Handle(THREAD, loader), Handle(THREAD, protection_domain), true, THREAD); diff --git a/src/hotspot/share/classfile/vmSymbols.cpp b/src/hotspot/share/classfile/vmSymbols.cpp --- a/src/hotspot/share/classfile/vmSymbols.cpp +++ b/src/hotspot/share/classfile/vmSymbols.cpp @@ -546,6 +546,7 @@ case vmIntrinsics::_isInterface: case vmIntrinsics::_isArray: case vmIntrinsics::_isPrimitive: + case vmIntrinsics::_isHidden: case vmIntrinsics::_getSuperclass: case vmIntrinsics::_Class_cast: case vmIntrinsics::_getLength: diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -884,6 +884,8 @@ do_name( isArray_name, "isArray") \ do_intrinsic(_isPrimitive, java_lang_Class, isPrimitive_name, void_boolean_signature, F_RN) \ do_name( isPrimitive_name, "isPrimitive") \ + do_intrinsic(_isHidden, java_lang_Class, isHidden_name, void_boolean_signature, F_RN) \ + do_name( isHidden_name, "isHidden") \ do_intrinsic(_getSuperclass, java_lang_Class, getSuperclass_name, void_class_signature, F_RN) \ do_name( getSuperclass_name, "getSuperclass") \ do_intrinsic(_Class_cast, java_lang_Class, Class_cast_name, object_object_signature, F_R) \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2020, 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 @@ -389,6 +389,21 @@ const char *source); /* + * Define a class with the specified lookup class. + * lookup: Lookup class + * name: the name of the class + * buf: class bytes + * len: length of class bytes + * pd: protection domain + * init: initialize the class + * flags: properties of the class + * classData: private static pre-initialized field; may be null + */ +JNIEXPORT jclass JNICALL +JVM_LookupDefineClass(JNIEnv *env, jclass lookup, const char *name, const jbyte *buf, + jsize len, jobject pd, jboolean init, int flags, jobject classData); + +/* * Module support funcions */ @@ -474,6 +489,9 @@ JNIEXPORT jboolean JNICALL JVM_IsPrimitiveClass(JNIEnv *env, jclass cls); +JNIEXPORT jboolean JNICALL +JVM_IsHiddenClass(JNIEnv *env, jclass cls); + JNIEXPORT jint JNICALL JVM_GetClassModifiers(JNIEnv *env, jclass cls); diff --git a/src/hotspot/share/interpreter/linkResolver.cpp b/src/hotspot/share/interpreter/linkResolver.cpp --- a/src/hotspot/share/interpreter/linkResolver.cpp +++ b/src/hotspot/share/interpreter/linkResolver.cpp @@ -540,6 +540,21 @@ return NULL; } +static void print_nest_host_error_on(stringStream* ss, Klass* ref_klass, Klass* sel_klass, TRAPS) { + assert(ref_klass->is_instance_klass(), "must be"); + assert(sel_klass->is_instance_klass(), "must be"); + InstanceKlass* ref_ik = InstanceKlass::cast(ref_klass); + InstanceKlass* sel_ik = InstanceKlass::cast(sel_klass); + const char* nest_host_error_1 = ref_ik->nest_host_error(THREAD); + const char* nest_host_error_2 = sel_ik->nest_host_error(THREAD); + if (nest_host_error_1 != NULL || nest_host_error_2 != NULL) { + ss->print(", (%s%s%s)", + (nest_host_error_1 != NULL) ? nest_host_error_1 : "", + (nest_host_error_1 != NULL && nest_host_error_2 != NULL) ? ", " : "", + (nest_host_error_2 != NULL) ? nest_host_error_2 : ""); + } +} + void LinkResolver::check_method_accessability(Klass* ref_klass, Klass* resolved_klass, Klass* sel_klass, @@ -572,24 +587,34 @@ sel_klass, flags, true, false, CHECK); - // Any existing exceptions that may have been thrown, for example LinkageErrors - // from nest-host resolution, have been allowed to propagate. + // Any existing exceptions that may have been thrown + // have been allowed to propagate. if (!can_access) { ResourceMark rm(THREAD); + stringStream ss; bool same_module = (sel_klass->module() == ref_klass->module()); - Exceptions::fthrow( - THREAD_AND_LOCATION, - vmSymbols::java_lang_IllegalAccessError(), - "class %s tried to access %s%s%smethod '%s' (%s%s%s)", - ref_klass->external_name(), - sel_method->is_abstract() ? "abstract " : "", - sel_method->is_protected() ? "protected " : "", - sel_method->is_private() ? "private " : "", - sel_method->external_name(), - (same_module) ? ref_klass->joint_in_module_of_loader(sel_klass) : ref_klass->class_in_module_of_loader(), - (same_module) ? "" : "; ", - (same_module) ? "" : sel_klass->class_in_module_of_loader() - ); + ss.print("class %s tried to access %s%s%smethod '%s' (%s%s%s)", + ref_klass->external_name(), + sel_method->is_abstract() ? "abstract " : "", + sel_method->is_protected() ? "protected " : "", + sel_method->is_private() ? "private " : "", + sel_method->external_name(), + (same_module) ? ref_klass->joint_in_module_of_loader(sel_klass) : ref_klass->class_in_module_of_loader(), + (same_module) ? "" : "; ", + (same_module) ? "" : sel_klass->class_in_module_of_loader() + ); + + // For private access see if there was a problem with nest host + // resolution, and if so report that as part of the message. + if (sel_method->is_private()) { + print_nest_host_error_on(&ss, ref_klass, sel_klass, THREAD); + } + + Exceptions::fthrow(THREAD_AND_LOCATION, + vmSymbols::java_lang_IllegalAccessError(), + "%s", + ss.as_string() + ); return; } } @@ -908,19 +933,27 @@ if (!can_access) { bool same_module = (sel_klass->module() == ref_klass->module()); ResourceMark rm(THREAD); - Exceptions::fthrow( - THREAD_AND_LOCATION, - vmSymbols::java_lang_IllegalAccessError(), - "class %s tried to access %s%sfield %s.%s (%s%s%s)", - ref_klass->external_name(), - fd.is_protected() ? "protected " : "", - fd.is_private() ? "private " : "", - sel_klass->external_name(), - fd.name()->as_C_string(), - (same_module) ? ref_klass->joint_in_module_of_loader(sel_klass) : ref_klass->class_in_module_of_loader(), - (same_module) ? "" : "; ", - (same_module) ? "" : sel_klass->class_in_module_of_loader() - ); + stringStream ss; + ss.print("class %s tried to access %s%sfield %s.%s (%s%s%s)", + ref_klass->external_name(), + fd.is_protected() ? "protected " : "", + fd.is_private() ? "private " : "", + sel_klass->external_name(), + fd.name()->as_C_string(), + (same_module) ? ref_klass->joint_in_module_of_loader(sel_klass) : ref_klass->class_in_module_of_loader(), + (same_module) ? "" : "; ", + (same_module) ? "" : sel_klass->class_in_module_of_loader() + ); + // For private access see if there was a problem with nest host + // resolution, and if so report that as part of the message. + if (fd.is_private()) { + print_nest_host_error_on(&ss, ref_klass, sel_klass, THREAD); + } + Exceptions::fthrow(THREAD_AND_LOCATION, + vmSymbols::java_lang_IllegalAccessError(), + "%s", + ss.as_string() + ); return; } } diff --git a/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp --- a/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp +++ b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2020, 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 @@ -1460,12 +1460,11 @@ Handle pd(THREAD, ik->protection_domain()); Symbol* const class_name = ik->name(); const char* const klass_name = class_name != NULL ? class_name->as_C_string() : ""; + ClassLoadInfo cl_info(pd); ClassFileParser new_parser(stream, class_name, cld, - pd, - NULL, // host klass - NULL, // cp_patches + &cl_info, ClassFileParser::INTERNAL, // internal visibility THREAD); if (HAS_PENDING_EXCEPTION) { @@ -1473,7 +1472,8 @@ CLEAR_PENDING_EXCEPTION; return NULL; } - InstanceKlass* const new_ik = new_parser.create_instance_klass(false, THREAD); + const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr(); + InstanceKlass* const new_ik = new_parser.create_instance_klass(false, *cl_inst_info, THREAD); if (HAS_PENDING_EXCEPTION) { log_pending_exception(PENDING_EXCEPTION); CLEAR_PENDING_EXCEPTION; diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.cpp --- a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.cpp +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020, 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 @@ -162,10 +162,9 @@ if (k->is_instance_klass()) { const InstanceKlass* ik = InstanceKlass::cast(k); - if (ik->is_unsafe_anonymous()) { + if (ik->is_unsafe_anonymous() || ik->is_hidden()) { return; } - assert(!ik->is_unsafe_anonymous(), "invariant"); const Symbol* name = ik->name(); if (name != NULL) { write_text("Class Name: "); diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -219,6 +219,7 @@ stackTrace="true"> + @@ -227,6 +228,7 @@ + @@ -729,6 +731,11 @@ description="Total size of all allocated metaspace chunks for unsafe anonymous classes (each chunk has several blocks)" /> + + + @@ -1135,6 +1142,7 @@ + diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2020, 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 @@ -483,6 +483,9 @@ event.set_unsafeAnonymousClassCount(cls->_anon_classes_count); event.set_unsafeAnonymousChunkSize(cls->_anon_chunk_sz); event.set_unsafeAnonymousBlockSize(cls->_anon_block_sz); + event.set_hiddenClassCount(cls->_hidden_classes_count); + event.set_hiddenChunkSize(cls->_hidden_chunk_sz); + event.set_hiddenBlockSize(cls->_hidden_block_sz); event.commit(); return true; } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp @@ -137,7 +137,6 @@ static traceid cld_id(CldPtr cld, bool leakp) { assert(cld != NULL, "invariant"); - assert(!cld->is_unsafe_anonymous(), "invariant"); if (leakp) { SET_LEAKP(cld); } else { @@ -163,6 +162,7 @@ if (klass->is_objArray_klass()) { klass = ObjArrayKlass::cast(klass)->bottom_klass(); } + if (klass->is_non_strong_hidden()) return NULL; return is_unsafe_anonymous(klass) ? InstanceKlass::cast(klass)->unsafe_anonymous_host()->class_loader_data() : klass->class_loader_data(); } @@ -188,10 +188,12 @@ assert(_artifacts != NULL, "invariant"); assert(klass != NULL, "invariant"); writer->write(artifact_id(klass)); - writer->write(cld_id(get_cld(klass), leakp)); + ClassLoaderData* cld = get_cld(klass); + writer->write(cld != NULL ? cld_id(cld, leakp) : 0); writer->write(mark_symbol(klass, leakp)); writer->write(package_id(klass, leakp)); writer->write(get_flags(klass)); + writer->write(klass->is_hidden()); return 1; } @@ -546,7 +548,6 @@ static int write_classloader(JfrCheckpointWriter* writer, CldPtr cld, bool leakp) { assert(cld != NULL, "invariant"); - assert(!cld->is_unsafe_anonymous(), "invariant"); // class loader type const Klass* class_loader_klass = cld->class_loader_klass(); if (class_loader_klass == NULL) { @@ -604,7 +605,7 @@ CLDCallback() {} void do_cld(ClassLoaderData* cld) { assert(cld != NULL, "invariant"); - if (cld->is_unsafe_anonymous()) { + if (cld->has_class_mirror_holder()) { return; } do_class_loader_data(cld); diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020, 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 @@ -189,52 +189,54 @@ * caller needs ResourceMark */ -uintptr_t JfrSymbolId::unsafe_anonymous_klass_name_hash(const InstanceKlass* ik) { +uintptr_t JfrSymbolId::hidden_or_anon_klass_name_hash(const InstanceKlass* ik) { assert(ik != NULL, "invariant"); - assert(ik->is_unsafe_anonymous(), "invariant"); + assert(ik->is_unsafe_anonymous() || ik->is_hidden(), "invariant"); const oop mirror = ik->java_mirror_no_keepalive(); assert(mirror != NULL, "invariant"); return (uintptr_t)mirror->identity_hash(); } -static const char* create_unsafe_anonymous_klass_symbol(const InstanceKlass* ik, uintptr_t hash) { +static const char* create_hidden_or_anon_klass_symbol(const InstanceKlass* ik, uintptr_t hash) { assert(ik != NULL, "invariant"); - assert(ik->is_unsafe_anonymous(), "invariant"); + assert(ik->is_unsafe_anonymous() || ik->is_hidden(), "invariant"); assert(hash != 0, "invariant"); - char* anonymous_symbol = NULL; + char* hidden_or_anon_symbol = NULL; const oop mirror = ik->java_mirror_no_keepalive(); assert(mirror != NULL, "invariant"); char hash_buf[40]; sprintf(hash_buf, "/" UINTX_FORMAT, hash); const size_t hash_len = strlen(hash_buf); const size_t result_len = ik->name()->utf8_length(); - anonymous_symbol = NEW_RESOURCE_ARRAY(char, result_len + hash_len + 1); - ik->name()->as_klass_external_name(anonymous_symbol, (int)result_len + 1); - assert(strlen(anonymous_symbol) == result_len, "invariant"); - strcpy(anonymous_symbol + result_len, hash_buf); - assert(strlen(anonymous_symbol) == result_len + hash_len, "invariant"); - return anonymous_symbol; + hidden_or_anon_symbol = NEW_RESOURCE_ARRAY(char, result_len + hash_len + 1); + ik->name()->as_klass_external_name(hidden_or_anon_symbol, (int)result_len + 1); + assert(strlen(hidden_or_anon_symbol) == result_len, "invariant"); + strcpy(hidden_or_anon_symbol + result_len, hash_buf); + assert(strlen(hidden_or_anon_symbol) == result_len + hash_len, "invariant"); + return hidden_or_anon_symbol; } -bool JfrSymbolId::is_unsafe_anonymous_klass(const Klass* k) { +bool JfrSymbolId::is_hidden_or_anon_klass(const Klass* k) { assert(k != NULL, "invariant"); - return k->is_instance_klass() && ((const InstanceKlass*)k)->is_unsafe_anonymous(); + return k->is_instance_klass() && + (((const InstanceKlass*)k)->is_unsafe_anonymous() || + ((const InstanceKlass*)k)->is_hidden()); } -traceid JfrSymbolId::mark_unsafe_anonymous_klass_name(const InstanceKlass* ik, bool leakp) { +traceid JfrSymbolId::mark_hidden_or_anon_klass_name(const InstanceKlass* ik, bool leakp) { assert(ik != NULL, "invariant"); - assert(ik->is_unsafe_anonymous(), "invariant"); - const uintptr_t hash = unsafe_anonymous_klass_name_hash(ik); - const char* const anonymous_klass_symbol = create_unsafe_anonymous_klass_symbol(ik, hash); - return mark(hash, anonymous_klass_symbol, leakp); + assert(ik->is_unsafe_anonymous() || ik->is_hidden(), "invariant"); + const uintptr_t hash = hidden_or_anon_klass_name_hash(ik); + const char* const hidden_or_anon_symbol = create_hidden_or_anon_klass_symbol(ik, hash); + return mark(hash, hidden_or_anon_symbol, leakp); } traceid JfrSymbolId::mark(const Klass* k, bool leakp) { assert(k != NULL, "invariant"); traceid symbol_id = 0; - if (is_unsafe_anonymous_klass(k)) { + if (is_hidden_or_anon_klass(k)) { assert(k->is_instance_klass(), "invariant"); - symbol_id = mark_unsafe_anonymous_klass_name((const InstanceKlass*)k, leakp); + symbol_id = mark_hidden_or_anon_klass_name((const InstanceKlass*)k, leakp); } if (0 == symbol_id) { Symbol* const sym = k->name(); @@ -276,9 +278,9 @@ return _symbol_id->bootstrap_name(leakp); } -traceid JfrArtifactSet::mark_unsafe_anonymous_klass_name(const Klass* klass, bool leakp) { +traceid JfrArtifactSet::mark_hidden_or_anon_klass_name(const Klass* klass, bool leakp) { assert(klass->is_instance_klass(), "invariant"); - return _symbol_id->mark_unsafe_anonymous_klass_name((const InstanceKlass*)klass, leakp); + return _symbol_id->mark_hidden_or_anon_klass_name((const InstanceKlass*)klass, leakp); } traceid JfrArtifactSet::mark(uintptr_t hash, const Symbol* sym, bool leakp) { diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020, 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 @@ -241,9 +241,9 @@ } } - traceid mark_unsafe_anonymous_klass_name(const InstanceKlass* k, bool leakp); - bool is_unsafe_anonymous_klass(const Klass* k); - uintptr_t unsafe_anonymous_klass_name_hash(const InstanceKlass* ik); + traceid mark_hidden_or_anon_klass_name(const InstanceKlass* k, bool leakp); + bool is_hidden_or_anon_klass(const Klass* k); + uintptr_t hidden_or_anon_klass_name_hash(const InstanceKlass* ik); public: JfrSymbolId(); @@ -304,7 +304,7 @@ traceid mark(const Klass* klass, bool leakp); traceid mark(const Symbol* symbol, bool leakp); traceid mark(uintptr_t hash, const char* const str, bool leakp); - traceid mark_unsafe_anonymous_klass_name(const Klass* klass, bool leakp); + traceid mark_hidden_or_anon_klass_name(const Klass* klass, bool leakp); traceid bootstrap_name(bool leakp); const JfrSymbolId::SymbolEntry* map_symbol(const Symbol* symbol) const; diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.cpp --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2020, 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 @@ -140,7 +140,7 @@ void JfrTraceId::assign(const ClassLoaderData* cld) { assert(cld != NULL, "invariant"); - if (cld->is_unsafe_anonymous()) { + if (cld->has_class_mirror_holder()) { cld->set_trace_id(0); return; } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp @@ -114,7 +114,7 @@ inline traceid JfrTraceId::use(const ClassLoaderData* cld) { assert(cld != NULL, "invariant"); - return cld->is_unsafe_anonymous() ? 0 : set_used_and_get(cld); + return cld->has_class_mirror_holder() ? 0 : set_used_and_get(cld); } inline void JfrTraceId::set_leakp(const Klass* klass, const Method* method) { diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp --- a/src/hotspot/share/memory/metaspace.cpp +++ b/src/hotspot/share/memory/metaspace.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2020, 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 @@ -64,7 +64,7 @@ switch (t) { case Metaspace::StandardMetaspaceType: s = "Standard"; break; case Metaspace::BootMetaspaceType: s = "Boot"; break; - case Metaspace::UnsafeAnonymousMetaspaceType: s = "UnsafeAnonymous"; break; + case Metaspace::ClassMirrorHolderMetaspaceType: s = "ClassMirrorHolder"; break; case Metaspace::ReflectionMetaspaceType: s = "Reflection"; break; default: ShouldNotReachHere(); } diff --git a/src/hotspot/share/memory/metaspace.hpp b/src/hotspot/share/memory/metaspace.hpp --- a/src/hotspot/share/memory/metaspace.hpp +++ b/src/hotspot/share/memory/metaspace.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2020, 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 @@ -104,8 +104,8 @@ ZeroMetaspaceType = 0, StandardMetaspaceType = ZeroMetaspaceType, BootMetaspaceType = StandardMetaspaceType + 1, - UnsafeAnonymousMetaspaceType = BootMetaspaceType + 1, - ReflectionMetaspaceType = UnsafeAnonymousMetaspaceType + 1, + ClassMirrorHolderMetaspaceType = BootMetaspaceType + 1, + ReflectionMetaspaceType = ClassMirrorHolderMetaspaceType + 1, MetaspaceTypeCount }; @@ -254,7 +254,7 @@ // Initialize the first chunk for a Metaspace. Used for // special cases such as the boot class loader, reflection - // class loader and anonymous class loader. + // class loader and hidden class loader. void initialize_first_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype); metaspace::Metachunk* get_initialization_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype); @@ -399,7 +399,7 @@ rf_show_loaders = (1 << 0), // Breaks report down by chunk type (small, medium, ...). rf_break_down_by_chunktype = (1 << 1), - // Breaks report down by space type (anonymous, reflection, ...). + // Breaks report down by space type (hidden, reflection, ...). rf_break_down_by_spacetype = (1 << 2), // Print details about the underlying virtual spaces. rf_show_vslist = (1 << 3), diff --git a/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.cpp b/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.cpp --- a/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.cpp +++ b/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -104,7 +104,7 @@ _out->print(UINTX_FORMAT_W(4) ": ", _num_loaders); // Print "CLD for [,] instance of " - // or "CLD for , loaded by [,] instance of " + // or "CLD for , loaded by [,] instance of " ResourceMark rm; const char* name = NULL; @@ -128,8 +128,8 @@ _out->print(" (unloading)"); } _out->print(":"); - if (cld->is_unsafe_anonymous()) { - _out->print(" , loaded by"); + if (cld->has_class_mirror_holder()) { + _out->print(" , loaded by"); } if (name != NULL) { _out->print(" \"%s\"", name); diff --git a/src/hotspot/share/memory/metaspace/spaceManager.cpp b/src/hotspot/share/memory/metaspace/spaceManager.cpp --- a/src/hotspot/share/memory/metaspace/spaceManager.cpp +++ b/src/hotspot/share/memory/metaspace/spaceManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -75,14 +75,14 @@ if (is_class()) { switch (type) { case Metaspace::BootMetaspaceType: requested = Metaspace::first_class_chunk_word_size(); break; - case Metaspace::UnsafeAnonymousMetaspaceType: requested = ClassSpecializedChunk; break; + case Metaspace::ClassMirrorHolderMetaspaceType: requested = ClassSpecializedChunk; break; case Metaspace::ReflectionMetaspaceType: requested = ClassSpecializedChunk; break; default: requested = ClassSmallChunk; break; } } else { switch (type) { case Metaspace::BootMetaspaceType: requested = Metaspace::first_chunk_word_size(); break; - case Metaspace::UnsafeAnonymousMetaspaceType: requested = SpecializedChunk; break; + case Metaspace::ClassMirrorHolderMetaspaceType: requested = SpecializedChunk; break; case Metaspace::ReflectionMetaspaceType: requested = SpecializedChunk; break; default: requested = SmallChunk; break; } @@ -114,15 +114,15 @@ // After that a medium chunk is preferred. size_t chunk_word_size; - // Special case for unsafe anonymous metadata space. - // UnsafeAnonymous metadata space is usually small since it is used for - // class loader data's whose life cycle is governed by one class such as an - // unsafe anonymous class. The majority within 1K - 2K range and + // Special case for hidden metadata space. + // ClassMirrorHolder metadata space is usually small since it is used for + // class loader data's whose life cycle is governed by one class such as a + // non-strong hidden class or unsafe anonymous class. The majority within 1K - 2K range and // rarely about 4K (64-bits JVM). // Instead of jumping to SmallChunk after initial chunk exhausted, keeping allocation // from SpecializeChunk up to _anon_or_delegating_metadata_specialize_chunk_limit (4) // reduces space waste from 60+% to around 30%. - if ((_space_type == Metaspace::UnsafeAnonymousMetaspaceType || _space_type == Metaspace::ReflectionMetaspaceType) && + if ((_space_type == Metaspace::ClassMirrorHolderMetaspaceType || _space_type == Metaspace::ReflectionMetaspaceType) && _mdtype == Metaspace::NonClassType && num_chunks_by_type(SpecializedIndex) < anon_and_delegating_metadata_specialize_chunk_limit && word_size + Metachunk::overhead() <= SpecializedChunk) { diff --git a/src/hotspot/share/memory/metaspaceTracer.cpp b/src/hotspot/share/memory/metaspaceTracer.cpp --- a/src/hotspot/share/memory/metaspaceTracer.cpp +++ b/src/hotspot/share/memory/metaspaceTracer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, 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 @@ -62,10 +62,15 @@ E event; if (event.should_commit()) { event.set_classLoader(cld); - if (cld->is_unsafe_anonymous()) { - event.set_unsafeAnonymousClassLoader(true); - } else { - event.set_unsafeAnonymousClassLoader(false); + event.set_unsafeAnonymousClassLoader(false); // initialize these + event.set_hiddenClassLoader(false); + if (cld->has_class_mirror_holder()) { + assert(cld->klasses() != NULL, "unexpected NULL for cld->klasses()"); + if (cld->klasses()->is_non_strong_hidden()) { + event.set_hiddenClassLoader(true); + } else { + event.set_unsafeAnonymousClassLoader(true); + } } event.set_size(word_size * BytesPerWord); event.set_metadataType((u1) mdtype); diff --git a/src/hotspot/share/oops/arrayKlass.cpp b/src/hotspot/share/oops/arrayKlass.cpp --- a/src/hotspot/share/oops/arrayKlass.cpp +++ b/src/hotspot/share/oops/arrayKlass.cpp @@ -110,7 +110,7 @@ assert((module_entry != NULL) || ((module_entry == NULL) && !ModuleEntryTable::javabase_defined()), "module entry not available post " JAVA_BASE_NAME " definition"); oop module = (module_entry != NULL) ? module_entry->module() : (oop)NULL; - java_lang_Class::create_mirror(k, Handle(THREAD, k->class_loader()), Handle(THREAD, module), Handle(), CHECK); + java_lang_Class::create_mirror(k, Handle(THREAD, k->class_loader()), Handle(THREAD, module), Handle(), Handle(), CHECK); } GrowableArray* ArrayKlass::compute_secondary_supers(int num_extra_slots, diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -31,6 +31,7 @@ #include "classfile/classLoaderData.inline.hpp" #include "classfile/javaClasses.hpp" #include "classfile/moduleEntry.hpp" +#include "classfile/resolutionErrors.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -134,6 +135,7 @@ #endif // ndef DTRACE_ENABLED + static inline bool is_class_loader(const Symbol* class_name, const ClassFileParser& parser) { assert(class_name != NULL, "invariant"); @@ -153,8 +155,11 @@ return false; } -// called to verify that k is a member of this nest +// private: called to verify that k is a static member of this nest. +// We know that k is an instance class in the same package and hence the +// same classloader. bool InstanceKlass::has_nest_member(InstanceKlass* k, TRAPS) const { + assert(!is_hidden(), "unexpected hidden class"); if (_nest_members == NULL || _nest_members == Universe::the_empty_short_array()) { if (log_is_enabled(Trace, class, nestmates)) { ResourceMark rm(THREAD); @@ -175,7 +180,9 @@ for (int i = 0; i < _nest_members->length(); i++) { int cp_index = _nest_members->at(i); if (_constants->tag_at(cp_index).is_klass()) { - Klass* k2 = _constants->klass_at(cp_index, CHECK_false); + Klass* k2 = _constants->klass_at(cp_index, THREAD); + assert(!HAS_PENDING_EXCEPTION || PENDING_EXCEPTION->is_a(SystemDictionary::VirtualMachineError_klass()), + "Exceptions should not be possible here"); if (k2 == k) { log_trace(class, nestmates)("- class is listed at nest_members[%d] => cp[%d]", i, cp_index); return true; @@ -186,15 +193,14 @@ if (name == k->name()) { log_trace(class, nestmates)("- Found it at nest_members[%d] => cp[%d]", i, cp_index); - // Names match so check actual klass - this may trigger class loading if - // it doesn't match (though that should be impossible). But to be safe we - // have to check for a compiler thread executing here. - if (!THREAD->can_call_java() && !_constants->tag_at(cp_index).is_klass()) { - log_trace(class, nestmates)("- validation required resolution in an unsuitable thread"); - return false; - } - - Klass* k2 = _constants->klass_at(cp_index, CHECK_false); + // Names match so check actual klass. This may trigger class loading if + // it doesn't match though that should be impossible as it means one classloader + // has defined two different classes with the same name! A compiler thread won't be + // able to perform that loading but we can't exclude the compiler threads from + // executing this logic. But it should actually be impossible to trigger loading here. + Klass* k2 = _constants->klass_at(cp_index, THREAD); + assert(!HAS_PENDING_EXCEPTION || PENDING_EXCEPTION->is_a(SystemDictionary::VirtualMachineError_klass()), + "Exceptions should not be possible here"); if (k2 == k) { log_trace(class, nestmates)("- class is listed as a nest member"); return true; @@ -213,167 +219,210 @@ } // Return nest-host class, resolving, validating and saving it if needed. -// In cases where this is called from a thread that can not do classloading +// In cases where this is called from a thread that cannot do classloading // (such as a native JIT thread) then we simply return NULL, which in turn // causes the access check to return false. Such code will retry the access -// from a more suitable environment later. -InstanceKlass* InstanceKlass::nest_host(Symbol* validationException, TRAPS) { +// from a more suitable environment later. Otherwise the _nest_host is always +// set once this method returns. +// Any errors from nest-host resolution must be preserved so they can be queried +// from higher-level access checking code, and reported as part of access checking +// exceptions. +// VirtualMachineErrors are propagated with a NULL return. +// Under any conditions where the _nest_host can be set to non-NULL the resulting +// value of it and, if applicable, the nest host resolution/validation error, +// are idempotent. +InstanceKlass* InstanceKlass::nest_host(TRAPS) { InstanceKlass* nest_host_k = _nest_host; - if (nest_host_k == NULL) { - // need to resolve and save our nest-host class. This could be attempted - // concurrently but as the result is idempotent and we don't use the class - // then we do not need any synchronization beyond what is implicitly used - // during class loading. - if (_nest_host_index != 0) { // we have a real nest_host - // Before trying to resolve check if we're in a suitable context - if (!THREAD->can_call_java() && !_constants->tag_at(_nest_host_index).is_klass()) { - if (log_is_enabled(Trace, class, nestmates)) { - ResourceMark rm(THREAD); - log_trace(class, nestmates)("Rejected resolution of nest-host of %s in unsuitable thread", - this->external_name()); - } - return NULL; - } - - if (log_is_enabled(Trace, class, nestmates)) { - ResourceMark rm(THREAD); - log_trace(class, nestmates)("Resolving nest-host of %s using cp entry for %s", - this->external_name(), - _constants->klass_name_at(_nest_host_index)->as_C_string()); + if (nest_host_k != NULL) { + return nest_host_k; + } + + ResourceMark rm(THREAD); + + // need to resolve and save our nest-host class. + if (_nest_host_index != 0) { // we have a real nest_host + // Before trying to resolve check if we're in a suitable context + if (!THREAD->can_call_java() && !_constants->tag_at(_nest_host_index).is_klass()) { + log_trace(class, nestmates)("Rejected resolution of nest-host of %s in unsuitable thread", + this->external_name()); + return NULL; // sentinel to say "try again from a different context" + } + + log_trace(class, nestmates)("Resolving nest-host of %s using cp entry for %s", + this->external_name(), + _constants->klass_name_at(_nest_host_index)->as_C_string()); + + Klass* k = _constants->klass_at(_nest_host_index, THREAD); + if (HAS_PENDING_EXCEPTION) { + if (PENDING_EXCEPTION->is_a(SystemDictionary::VirtualMachineError_klass())) { + return NULL; // propagate VMEs } - - Klass* k = _constants->klass_at(_nest_host_index, THREAD); - if (HAS_PENDING_EXCEPTION) { - Handle exc_h = Handle(THREAD, PENDING_EXCEPTION); - if (exc_h->is_a(SystemDictionary::NoClassDefFoundError_klass())) { - // throw a new CDNFE with the original as its cause, and a clear msg - ResourceMark rm(THREAD); - char buf[200]; - CLEAR_PENDING_EXCEPTION; - jio_snprintf(buf, sizeof(buf), - "Unable to load nest-host class (%s) of %s", - _constants->klass_name_at(_nest_host_index)->as_C_string(), - this->external_name()); - log_trace(class, nestmates)("%s - NoClassDefFoundError", buf); - THROW_MSG_CAUSE_NULL(vmSymbols::java_lang_NoClassDefFoundError(), buf, exc_h); - } - // All other exceptions pass through (OOME, StackOverflowError, LinkageErrors etc). - return NULL; - } - + stringStream ss; + char* target_host_class = _constants->klass_name_at(_nest_host_index)->as_C_string(); + ss.print("Nest host resolution of %s with host %s failed: ", + this->external_name(), target_host_class); + java_lang_Throwable::print(PENDING_EXCEPTION, &ss); + const char* msg = ss.as_string(true /* on C-heap */); + constantPoolHandle cph(THREAD, constants()); + SystemDictionary::add_nest_host_error(cph, _nest_host_index, msg); + CLEAR_PENDING_EXCEPTION; + + log_trace(class, nestmates)("%s", msg); + } else { // A valid nest-host is an instance class in the current package that lists this - // class as a nest member. If any of these conditions are not met we post the - // requested exception type (if any) and return NULL - + // class as a nest member. If any of these conditions are not met the class is + // its own nest-host. const char* error = NULL; // JVMS 5.4.4 indicates package check comes first if (is_same_class_package(k)) { - // Now check actual membership. We can't be a member if our "host" is // not an instance class. if (k->is_instance_klass()) { nest_host_k = InstanceKlass::cast(k); - - bool is_member = nest_host_k->has_nest_member(this, CHECK_NULL); - if (is_member) { - // save resolved nest-host value - _nest_host = nest_host_k; - - if (log_is_enabled(Trace, class, nestmates)) { - ResourceMark rm(THREAD); + bool is_member = nest_host_k->has_nest_member(this, THREAD); + // exception is rare, perhaps impossible + if (!HAS_PENDING_EXCEPTION) { + if (is_member) { + _nest_host = nest_host_k; // save resolved nest-host value + log_trace(class, nestmates)("Resolved nest-host of %s to %s", this->external_name(), k->external_name()); + return nest_host_k; + } else { + error = "current type is not listed as a nest member"; } - return nest_host_k; + } else { + if (PENDING_EXCEPTION->is_a(SystemDictionary::VirtualMachineError_klass())) { + return NULL; // propagate VMEs + } + stringStream ss; + ss.print("exception on member check: "); + java_lang_Throwable::print(PENDING_EXCEPTION, &ss); + error = ss.as_string(); } + } else { + error = "host is not an instance class"; } - error = "current type is not listed as a nest member"; } else { error = "types are in different packages"; } - if (log_is_enabled(Trace, class, nestmates)) { - ResourceMark rm(THREAD); - log_trace(class, nestmates) - ("Type %s (loader: %s) is not a nest member of " - "resolved type %s (loader: %s): %s", - this->external_name(), - this->class_loader_data()->loader_name_and_id(), - k->external_name(), - k->class_loader_data()->loader_name_and_id(), - error); + // something went wrong, so record what and log it + { + stringStream ss; + ss.print("Type %s (loader: %s) is not a nest member of type %s (loader: %s): %s", + this->external_name(), + this->class_loader_data()->loader_name_and_id(), + k->external_name(), + k->class_loader_data()->loader_name_and_id(), + error); + const char* msg = ss.as_string(true /* on C-heap */); + constantPoolHandle cph(THREAD, constants()); + SystemDictionary::add_nest_host_error(cph, _nest_host_index, msg); + log_trace(class, nestmates)("%s", msg); } - - if (validationException != NULL && THREAD->can_call_java()) { - ResourceMark rm(THREAD); - Exceptions::fthrow(THREAD_AND_LOCATION, - validationException, - "Type %s (loader: %s) is not a nest member of %s (loader: %s): %s", - this->external_name(), - this->class_loader_data()->loader_name_and_id(), - k->external_name(), - k->class_loader_data()->loader_name_and_id(), - error - ); - } - return NULL; - } else { - if (log_is_enabled(Trace, class, nestmates)) { - ResourceMark rm(THREAD); - log_trace(class, nestmates)("Type %s is not part of a nest: setting nest-host to self", - this->external_name()); - } - // save resolved nest-host value - return (_nest_host = this); } + } else { + log_trace(class, nestmates)("Type %s is not part of a nest: setting nest-host to self", + this->external_name()); } - return nest_host_k; + + // Either not in an explicit nest, or else an error occurred, so + // the nest-host is set to `this`. Any thread that sees this assignment + // will also see any setting of nest_host_error(), if applicable. + return (_nest_host = this); +} + +// Dynamic nest member support: set this class's nest host to the given class. +// This occurs as part of the class definition, as soon as the instanceKlass +// has been created and doesn't require further resolution. The code: +// lookup().defineHiddenClass(bytes_for_X, NESTMATE); +// results in: +// class_of_X.set_nest_host(lookup().lookupClass().getNestHost()) +// If it has an explicit _nest_host_index or _nest_members, these will be ignored. +// We also know the "host" is a valid nest-host in the same package so we can +// assert some of those facts. +void InstanceKlass::set_nest_host(InstanceKlass* host, TRAPS) { + assert(is_hidden(), "must be a hidden class"); + assert(host != NULL, "NULL nest host specified"); + assert(_nest_host == NULL, "current class has resolved nest-host"); + assert(nest_host_error(THREAD) == NULL, "unexpected nest host resolution error exists: %s", + nest_host_error(THREAD)); + assert((host->_nest_host == NULL && host->_nest_host_index == 0) || + (host->_nest_host == host), "proposed host is not a valid nest-host"); + // Can't assert this as package is not set yet: + // assert(is_same_class_package(host), "proposed host is in wrong package"); + + if (log_is_enabled(Trace, class, nestmates)) { + ResourceMark rm(THREAD); + const char* msg = ""; + // a hidden class does not expect a statically defined nest-host + if (_nest_host_index > 0) { + msg = "(the NestHost attribute in the current class is ignored)"; + } else if (_nest_members != NULL && _nest_members != Universe::the_empty_short_array()) { + msg = "(the NestMembers attribute in the current class is ignored)"; + } + log_trace(class, nestmates)("Injected type %s into the nest of %s %s", + this->external_name(), + host->external_name(), + msg); + } + // set dynamic nest host + _nest_host = host; + // Record dependency to keep nest host from being unloaded before this class. + ClassLoaderData* this_key = class_loader_data(); + this_key->record_dependency(host); } // check if 'this' and k are nestmates (same nest_host), or k is our nest_host, // or we are k's nest_host - all of which is covered by comparing the two -// resolved_nest_hosts +// resolved_nest_hosts. +// Any exceptions (i.e. VMEs) are propagated. bool InstanceKlass::has_nestmate_access_to(InstanceKlass* k, TRAPS) { assert(this != k, "this should be handled by higher-level code"); // Per JVMS 5.4.4 we first resolve and validate the current class, then - // the target class k. Resolution exceptions will be passed on by upper - // layers. IncompatibleClassChangeErrors from membership validation failures - // will also be passed through. - - Symbol* icce = vmSymbols::java_lang_IncompatibleClassChangeError(); - InstanceKlass* cur_host = nest_host(icce, CHECK_false); + // the target class k. + + InstanceKlass* cur_host = nest_host(CHECK_false); if (cur_host == NULL) { return false; } - Klass* k_nest_host = k->nest_host(icce, CHECK_false); + Klass* k_nest_host = k->nest_host(CHECK_false); if (k_nest_host == NULL) { return false; } bool access = (cur_host == k_nest_host); - if (log_is_enabled(Trace, class, nestmates)) { - ResourceMark rm(THREAD); - log_trace(class, nestmates)("Class %s does %shave nestmate access to %s", - this->external_name(), - access ? "" : "NOT ", - k->external_name()); + ResourceMark rm(THREAD); + log_trace(class, nestmates)("Class %s does %shave nestmate access to %s", + this->external_name(), + access ? "" : "NOT ", + k->external_name()); + return access; +} + +const char* InstanceKlass::nest_host_error(TRAPS) { + if (_nest_host_index == 0) { + return NULL; + } else { + constantPoolHandle cph(THREAD, constants()); + return SystemDictionary::find_nest_host_error(cph, (int)_nest_host_index); } - - return access; } InstanceKlass* InstanceKlass::allocate_instance_klass(const ClassFileParser& parser, TRAPS) { + bool is_hidden_or_anonymous = parser.is_hidden() || parser.is_unsafe_anonymous(); const int size = InstanceKlass::size(parser.vtable_size(), parser.itable_size(), nonstatic_oop_map_size(parser.total_oop_map_count()), parser.is_interface(), parser.is_unsafe_anonymous(), - should_store_fingerprint(parser.is_unsafe_anonymous())); + should_store_fingerprint(is_hidden_or_anonymous)); const Symbol* const class_name = parser.class_name(); assert(class_name != NULL, "invariant"); @@ -447,6 +496,7 @@ set_vtable_length(parser.vtable_size()); set_kind(kind); set_access_flags(parser.access_flags()); + if (parser.is_hidden()) set_is_hidden(); set_is_unsafe_anonymous(parser.is_unsafe_anonymous()); set_layout_helper(Klass::instance_layout_helper(parser.layout_size(), false)); @@ -2276,7 +2326,7 @@ return true; } -bool InstanceKlass::should_store_fingerprint(bool is_unsafe_anonymous) { +bool InstanceKlass::should_store_fingerprint(bool is_hidden_or_anonymous) { #if INCLUDE_AOT // We store the fingerprint into the InstanceKlass only in the following 2 cases: if (CalculateClassFingerprint) { @@ -2287,8 +2337,8 @@ // (2) We are running -Xshare:dump or -XX:ArchiveClassesAtExit to create a shared archive return true; } - if (UseAOT && is_unsafe_anonymous) { - // (3) We are using AOT code from a shared library and see an unsafe anonymous class + if (UseAOT && is_hidden_or_anonymous) { + // (3) We are using AOT code from a shared library and see a hidden or unsafe anonymous class return true; } #endif @@ -2581,6 +2631,7 @@ // Decrement symbol reference counts associated with the unloaded class. if (_name != NULL) _name->decrement_refcount(); + // unreference array name derived from this class name (arrays of an unloaded // class can't be referenced anymore). if (_array_name != NULL) _array_name->decrement_refcount(); @@ -2631,6 +2682,15 @@ dest[dest_index++] = src[src_index++]; } + if (is_hidden()) { // Replace the last '+' with a '.'. + for (int index = (int)src_length; index > 0; index--) { + if (dest[index] == '+') { + dest[index] = JVM_SIGNATURE_DOT; + break; + } + } + } + // If we have a hash, append it for (int hash_index = 0; hash_index < hash_len; ) { dest[dest_index++] = hash_buf[hash_index++]; @@ -2649,6 +2709,25 @@ return unsafe_anonymous_host()->module(); } + if (is_hidden() && + in_unnamed_package() && + class_loader_data()->has_class_mirror_holder()) { + // For a non-strong hidden class defined to an unnamed package, + // its (class held) CLD will not have an unnamed module created for it. + // Two choices to find the correct ModuleEntry: + // 1. If hidden class is within a nest, use nest host's module + // 2. Find the unnamed module off from the class loader + // For now option #2 is used since a nest host is not set until + // after the instance class is created in jvm_lookup_define_class(). + if (class_loader_data()->is_boot_class_loader_data()) { + return ClassLoaderData::the_null_class_loader_data()->unnamed_module(); + } else { + oop module = java_lang_ClassLoader::unnamedModule(class_loader_data()->class_loader()); + assert(java_lang_Module::is_instance(module), "Not an instance of java.lang.Module"); + return java_lang_Module::module_entry(module); + } + } + // Class is in a named package if (!in_unnamed_package()) { return _package_entry->module(); @@ -2859,7 +2938,7 @@ *inner_is_member = true; } if (NULL == outer_klass) { - // It may be unsafe anonymous; try for that. + // It may be a local or anonymous class; try for that. int encl_method_class_idx = enclosing_method_class_index(); if (encl_method_class_idx != 0) { Klass* ok = i_cp->klass_at(encl_method_class_idx, CHECK_NULL); diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -195,7 +195,10 @@ // that is the nest-host of this class. This data has not been validated. jushort _nest_host_index; - // Resolved nest-host klass: either true nest-host or self if we are not nested. + // Resolved nest-host klass: either true nest-host or self if we are not + // nested, or an error occurred resolving or validating the nominated + // nest-host. Can also be set directly by JDK API's that establish nest + // relationships. // By always being set it makes nest-member access checks simpler. InstanceKlass* _nest_host; @@ -469,6 +472,8 @@ // nest-host index jushort nest_host_index() const { return _nest_host_index; } void set_nest_host_index(u2 i) { _nest_host_index = i; } + // dynamic nest member support + void set_nest_host(InstanceKlass* host, TRAPS); // record components Array* record_components() const { return _record_components; } @@ -482,9 +487,13 @@ bool has_nest_member(InstanceKlass* k, TRAPS) const; public: - // Returns nest-host class, resolving and validating it if needed - // Returns NULL if an exception occurs during loading, or validation fails - InstanceKlass* nest_host(Symbol* validationException, TRAPS); + // Used to construct informative IllegalAccessError messages at a higher level, + // if there was an issue resolving or validating the nest host. + // Returns NULL if there was no error. + const char* nest_host_error(TRAPS); + // Returns nest-host class, resolving and validating it if needed. + // Returns NULL if resolution is not possible from the calling context. + InstanceKlass* nest_host(TRAPS); // Check if this klass is a nestmate of k - resolves this nest-host and k's bool has_nestmate_access_to(InstanceKlass* k, TRAPS); @@ -812,8 +821,8 @@ } bool supers_have_passed_fingerprint_checks(); - static bool should_store_fingerprint(bool is_unsafe_anonymous); - bool should_store_fingerprint() const { return should_store_fingerprint(is_unsafe_anonymous()); } + static bool should_store_fingerprint(bool is_hidden_or_anonymous); + bool should_store_fingerprint() const { return should_store_fingerprint(is_hidden() || is_unsafe_anonymous()); } bool has_stored_fingerprint() const; uint64_t get_stored_fingerprint() const; void store_fingerprint(uint64_t fingerprint); diff --git a/src/hotspot/share/oops/instanceMirrorKlass.inline.hpp b/src/hotspot/share/oops/instanceMirrorKlass.inline.hpp --- a/src/hotspot/share/oops/instanceMirrorKlass.inline.hpp +++ b/src/hotspot/share/oops/instanceMirrorKlass.inline.hpp @@ -1,4 +1,5 @@ -/* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. +/* + * Copyright (c) 2015, 2020, 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 @@ -51,10 +52,9 @@ Klass* klass = java_lang_Class::as_Klass_raw(obj); // We'll get NULL for primitive mirrors. if (klass != NULL) { - if (klass->is_instance_klass() && - InstanceKlass::cast(klass)->is_unsafe_anonymous()) { - // An unsafe anonymous class doesn't have its own class loader, so - // when handling the java mirror for the class we need to make sure its class + if (klass->is_instance_klass() && klass->class_loader_data()->has_class_mirror_holder()) { + // A non-strong hidden class or an unsafe anonymous class doesn't have its own class loader, + // so when handling the java mirror for the class we need to make sure its class // loader data is claimed, this is done by calling do_cld explicitly. // For non-anonymous classes the call to do_cld is made when the class // loader itself is handled. diff --git a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp --- a/src/hotspot/share/oops/klass.cpp +++ b/src/hotspot/share/oops/klass.cpp @@ -608,7 +608,7 @@ // gotten an OOM later but keep the mirror if it was created. if (java_mirror() == NULL) { log_trace(cds, mirror)("Recreate mirror for %s", external_name()); - java_lang_Class::create_mirror(this, loader, module_handle, protection_domain, CHECK); + java_lang_Class::create_mirror(this, loader, module_handle, protection_domain, Handle(), CHECK); } } @@ -672,6 +672,20 @@ } } +// Replace the last '+' char with '/'. +static char* convert_hidden_name_to_java(Symbol* name) { + size_t name_len = name->utf8_length(); + char* result = NEW_RESOURCE_ARRAY(char, name_len + 1); + name->as_klass_external_name(result, (int)name_len + 1); + for (int index = (int)name_len; index > 0; index--) { + if (result[index] == '+') { + result[index] = JVM_SIGNATURE_SLASH; + break; + } + } + return result; +} + // In product mode, this function doesn't have virtual function calls so // there might be some performance advantage to handling InstanceKlass here. const char* Klass::external_name() const { @@ -688,7 +702,14 @@ strcpy(result + name_len, addr_buf); assert(strlen(result) == name_len + addr_len, ""); return result; + + } else if (ik->is_hidden()) { + char* result = convert_hidden_name_to_java(name()); + return result; } + } else if (is_objArray_klass() && ObjArrayKlass::cast(this)->bottom_klass()->is_hidden()) { + char* result = convert_hidden_name_to_java(name()); + return result; } if (name() == NULL) return ""; return name()->as_klass_external_name(); @@ -696,6 +717,18 @@ const char* Klass::signature_name() const { if (name() == NULL) return ""; + if (is_objArray_klass() && ObjArrayKlass::cast(this)->bottom_klass()->is_hidden()) { + size_t name_len = name()->utf8_length(); + char* result = NEW_RESOURCE_ARRAY(char, name_len + 1); + name()->as_C_string(result, (int)name_len + 1); + for (int index = (int)name_len; index > 0; index--) { + if (result[index] == '+') { + result[index] = JVM_SIGNATURE_DOT; + break; + } + } + return result; + } return name()->as_C_string(); } diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -615,6 +615,10 @@ void set_has_miranda_methods() { _access_flags.set_has_miranda_methods(); } bool is_shared() const { return access_flags().is_shared_class(); } // shadows MetaspaceObj::is_shared)() void set_is_shared() { _access_flags.set_is_shared_class(); } + bool is_hidden() const { return access_flags().is_hidden_class(); } + void set_is_hidden() { _access_flags.set_is_hidden_class(); } + bool is_non_strong_hidden() const { return access_flags().is_hidden_class() && + class_loader_data()->has_class_mirror_holder(); } bool is_cloneable() const; void set_is_cloneable(); diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -892,9 +892,10 @@ _flags = x ? (_flags | _dont_inline) : (_flags & ~_dont_inline); } - bool is_hidden() { + bool is_hidden() const { return (_flags & _hidden) != 0; } + void set_hidden(bool x) { _flags = x ? (_flags | _hidden) : (_flags & ~_hidden); } diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -602,6 +602,7 @@ case vmIntrinsics::_isInterface: case vmIntrinsics::_isArray: case vmIntrinsics::_isPrimitive: + case vmIntrinsics::_isHidden: case vmIntrinsics::_getSuperclass: case vmIntrinsics::_getClassAccessFlags: case vmIntrinsics::_floatToRawIntBits: diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -186,6 +186,7 @@ int modifier_mask, int modifier_bits, RegionNode* region); Node* generate_interface_guard(Node* kls, RegionNode* region); + Node* generate_hidden_class_guard(Node* kls, RegionNode* region); Node* generate_array_guard(Node* kls, RegionNode* region) { return generate_array_guard_common(kls, region, false, false); } @@ -783,6 +784,7 @@ case vmIntrinsics::_isInterface: case vmIntrinsics::_isArray: case vmIntrinsics::_isPrimitive: + case vmIntrinsics::_isHidden: case vmIntrinsics::_getSuperclass: case vmIntrinsics::_getClassAccessFlags: return inline_native_Class_query(intrinsic_id()); @@ -3084,6 +3086,9 @@ Node* LibraryCallKit::generate_interface_guard(Node* kls, RegionNode* region) { return generate_access_flags_guard(kls, JVM_ACC_INTERFACE, 0, region); } +Node* LibraryCallKit::generate_hidden_class_guard(Node* kls, RegionNode* region) { + return generate_access_flags_guard(kls, JVM_ACC_IS_HIDDEN_CLASS, 0, region); +} //-------------------------inline_native_Class_query------------------- bool LibraryCallKit::inline_native_Class_query(vmIntrinsics::ID id) { @@ -3119,6 +3124,9 @@ prim_return_value = intcon(1); expect_prim = true; // obviously break; + case vmIntrinsics::_isHidden: + prim_return_value = intcon(0); + break; case vmIntrinsics::_getSuperclass: prim_return_value = null(); return_type = TypeInstPtr::MIRROR->cast_to_ptr_type(TypePtr::BotPTR); @@ -3211,6 +3219,16 @@ query_value = intcon(0); // "normal" path produces false break; + case vmIntrinsics::_isHidden: + // (To verify this code sequence, check the asserts in JVM_IsHiddenClass.) + if (generate_hidden_class_guard(kls, region) != NULL) + // A guard was added. If the guard is taken, it was an hidden class. + phi->add_req(intcon(1)); + // If we fall through, it's a plain class. + query_value = intcon(0); + break; + + case vmIntrinsics::_getSuperclass: // The rules here are somewhat unfortunate, but we can still do better // with random logic than with a JNI call. diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -989,6 +989,154 @@ return (jclass) JNIHandles::make_local(env, k->java_mirror()); } +enum { + NESTMATE = java_lang_invoke_MemberName::MN_NESTMATE_CLASS, + HIDDEN_CLASS = java_lang_invoke_MemberName::MN_HIDDEN_CLASS, + STRONG_LOADER_LINK = java_lang_invoke_MemberName::MN_STRONG_LOADER_LINK, + ACCESS_VM_ANNOTATIONS = java_lang_invoke_MemberName::MN_ACCESS_VM_ANNOTATIONS +}; + +/* + * Define a class with the specified flags that indicates if it's a nestmate, + * hidden, or strongly referenced from class loader. + */ +static jclass jvm_lookup_define_class(JNIEnv *env, jclass lookup, const char *name, + const jbyte *buf, jsize len, jobject pd, + jboolean init, int flags, jobject classData, TRAPS) { + assert(THREAD->is_Java_thread(), "must be a JavaThread"); + JavaThread* jt = (JavaThread*) THREAD; + ResourceMark rm(THREAD); + + Klass* lookup_k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(lookup)); + // Lookup class must be a non-null instance + if (lookup_k == NULL) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "Lookup class is null"); + } + assert(lookup_k->is_instance_klass(), "Lookup class must be an instance klass"); + + Handle class_loader (THREAD, lookup_k->class_loader()); + + bool is_nestmate = (flags & NESTMATE) == NESTMATE; + bool is_hidden = (flags & HIDDEN_CLASS) == HIDDEN_CLASS; + bool is_strong = (flags & STRONG_LOADER_LINK) == STRONG_LOADER_LINK; + bool vm_annotations = (flags & ACCESS_VM_ANNOTATIONS) == ACCESS_VM_ANNOTATIONS; + + InstanceKlass* host_class = NULL; + if (is_nestmate) { + host_class = InstanceKlass::cast(lookup_k)->nest_host(CHECK_NULL); + } + + log_info(class, nestmates)("LookupDefineClass: %s - %s%s, %s, %s, %s", + name, + is_nestmate ? "with dynamic nest-host " : "non-nestmate", + is_nestmate ? host_class->external_name() : "", + is_hidden ? "hidden" : "not hidden", + is_strong ? "strong" : "weak", + vm_annotations ? "with vm annotations" : "without vm annotation"); + + if (!is_hidden) { + // classData is only applicable for hidden classes + if (classData != NULL) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "classData is only applicable for hidden classes"); + } + if (is_nestmate) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "dynamic nestmate is only applicable for hidden classes"); + } + if (!is_strong) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "an ordinary class must be strongly referenced by its defining loader"); + } + if (vm_annotations) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "vm annotations only allowed for hidden classes"); + } + if (flags != STRONG_LOADER_LINK) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), + err_msg("invalid flag 0x%x", flags)); + } + } + + + // Since exceptions can be thrown, class initialization can take place + // if name is NULL no check for class name in .class stream has to be made. + TempNewSymbol class_name = NULL; + if (name != NULL) { + const int str_len = (int)strlen(name); + if (str_len > Symbol::max_length()) { + // It's impossible to create this class; the name cannot fit + // into the constant pool. + Exceptions::fthrow(THREAD_AND_LOCATION, + vmSymbols::java_lang_NoClassDefFoundError(), + "Class name exceeds maximum length of %d: %s", + Symbol::max_length(), + name); + return 0; + } + class_name = SymbolTable::new_symbol(name, str_len); + } + + Handle protection_domain (THREAD, JNIHandles::resolve(pd)); + const char* source = is_nestmate ? host_class->external_name() : "__JVM_LookupDefineClass__"; + ClassFileStream st((u1*)buf, len, source, ClassFileStream::verify); + + Klass* defined_k; + InstanceKlass* ik = NULL; + if (!is_hidden) { + defined_k = SystemDictionary::resolve_from_stream(class_name, + class_loader, + protection_domain, + &st, + CHECK_NULL); + + if (log_is_enabled(Debug, class, resolve) && defined_k != NULL) { + trace_class_resolution(defined_k); + } + ik = InstanceKlass::cast(defined_k); + } else { // hidden + Handle classData_h(THREAD, JNIHandles::resolve(classData)); + ClassLoadInfo cl_info(protection_domain, + NULL, // unsafe_anonymous_host + NULL, // cp_patches + host_class, + classData_h, + is_hidden, + is_strong, + vm_annotations); + defined_k = SystemDictionary::parse_stream(class_name, + class_loader, + &st, + cl_info, + CHECK_NULL); + if (defined_k == NULL) { + THROW_MSG_0(vmSymbols::java_lang_Error(), "Failure to define a hidden class"); + } + + ik = InstanceKlass::cast(defined_k); + + // The hidden class loader data has been artificially been kept alive to + // this point. The mirror and any instances of this class have to keep + // it alive afterwards. + ik->class_loader_data()->dec_keep_alive(); + + if (is_nestmate && log_is_enabled(Debug, class, nestmates)) { + ModuleEntry* module = ik->module(); + const char * module_name = module->is_named() ? module->name()->as_C_string() : UNNAMED_MODULE; + log_debug(class, nestmates)("Dynamic nestmate: %s/%s, nest_host %s, %s", + module_name, + ik->external_name(), + host_class->external_name(), + ik->is_hidden() ? "is hidden" : "is not hidden"); + } + } + assert(Reflection::is_same_class_package(lookup_k, defined_k), + "lookup class and defined class are in different packages"); + + if (init) { + ik->initialize(CHECK_NULL); + } else { + ik->link_class(CHECK_NULL); + } + + return (jclass) JNIHandles::make_local(env, defined_k->java_mirror()); +} JVM_ENTRY(jclass, JVM_DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd)) JVMWrapper("JVM_DefineClass"); @@ -996,6 +1144,29 @@ return jvm_define_class_common(env, name, loader, buf, len, pd, NULL, THREAD); JVM_END +/* + * Define a class with the specified lookup class. + * lookup: Lookup class + * name: the name of the class + * buf: class bytes + * len: length of class bytes + * pd: protection domain + * init: initialize the class + * flags: properties of the class + * classData: private static pre-initialized field + */ +JVM_ENTRY(jclass, JVM_LookupDefineClass(JNIEnv *env, jclass lookup, const char *name, const jbyte *buf, + jsize len, jobject pd, jboolean initialize, int flags, jobject classData)) + JVMWrapper("JVM_LookupDefineClass"); + + if (lookup == NULL) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "Lookup class is null"); + } + + assert(buf != NULL, "buf must not be NULL"); + + return jvm_lookup_define_class(env, lookup, name, buf, len, pd, initialize, flags, classData, THREAD); +JVM_END JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source)) JVMWrapper("JVM_DefineClassWithSource"); @@ -1158,6 +1329,15 @@ return result; JVM_END +JVM_ENTRY(jboolean, JVM_IsHiddenClass(JNIEnv *env, jclass cls)) + JVMWrapper("JVM_IsHiddenClass"); + oop mirror = JNIHandles::resolve_non_null(cls); + if (java_lang_Class::is_primitive(mirror)) { + return JNI_FALSE; + } + Klass* k = java_lang_Class::as_Klass(mirror); + return k->is_hidden(); +JVM_END JVM_ENTRY(jobjectArray, JVM_GetClassSigners(JNIEnv *env, jclass cls)) JVMWrapper("JVM_GetClassSigners"); @@ -1425,7 +1605,7 @@ = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(ofClass)) )->compute_enclosing_class(&inner_is_member, CHECK_NULL); if (outer_klass == NULL) return NULL; // already a top-level class - if (!inner_is_member) return NULL; // an anonymous class (inside a method) + if (!inner_is_member) return NULL; // a hidden or unsafe anonymous class (inside a method) return (jclass) JNIHandles::make_local(env, outer_klass->java_mirror()); } JVM_END @@ -1875,8 +2055,7 @@ Klass* c = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(current)); assert(c->is_instance_klass(), "must be"); InstanceKlass* ck = InstanceKlass::cast(c); - // Don't post exceptions if validation fails - InstanceKlass* host = ck->nest_host(NULL, THREAD); + InstanceKlass* host = ck->nest_host(THREAD); return (jclass) (host == NULL ? NULL : JNIHandles::make_local(THREAD, host->java_mirror())); } @@ -1886,62 +2065,77 @@ { // current is not a primitive or array class JVMWrapper("JVM_GetNestMembers"); + ResourceMark rm(THREAD); Klass* c = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(current)); assert(c->is_instance_klass(), "must be"); InstanceKlass* ck = InstanceKlass::cast(c); - // Get the nest host for this nest - throw ICCE if validation fails - Symbol* icce = vmSymbols::java_lang_IncompatibleClassChangeError(); - InstanceKlass* host = ck->nest_host(icce, CHECK_NULL); - + InstanceKlass* host = ck->nest_host(THREAD); + + log_trace(class, nestmates)("Calling GetNestMembers for type %s with nest-host %s", + ck->external_name(), host->external_name()); { JvmtiVMObjectAllocEventCollector oam; Array* members = host->nest_members(); int length = members == NULL ? 0 : members->length(); + + log_trace(class, nestmates)(" - host has %d listed nest members", length); + // nest host is first in the array so make it one bigger objArrayOop r = oopFactory::new_objArray(SystemDictionary::Class_klass(), length + 1, CHECK_NULL); - objArrayHandle result (THREAD, r); + objArrayHandle result(THREAD, r); result->obj_at_put(0, host->java_mirror()); if (length != 0) { - int i; - for (i = 0; i < length; i++) { - int cp_index = members->at(i); - Klass* k = host->constants()->klass_at(cp_index, CHECK_NULL); - if (k->is_instance_klass()) { - InstanceKlass* nest_host_k = - InstanceKlass::cast(k)->nest_host(icce, CHECK_NULL); - if (nest_host_k == host) { - result->obj_at_put(i+1, k->java_mirror()); - } - else { - // k's nest host is legal but it isn't our host so - // throw ICCE - ResourceMark rm(THREAD); - Exceptions::fthrow(THREAD_AND_LOCATION, - icce, - "Nest member %s in %s declares a different nest host of %s", - k->external_name(), - host->external_name(), - nest_host_k->external_name() - ); - return NULL; - } - } - else { - // we have a bad nest member entry - throw ICCE - ResourceMark rm(THREAD); - Exceptions::fthrow(THREAD_AND_LOCATION, - icce, - "Class %s can not be a nest member of %s", - k->external_name(), - host->external_name() - ); - return NULL; - } + int count = 0; + for (int i = 0; i < length; i++) { + int cp_index = members->at(i); + Klass* k = host->constants()->klass_at(cp_index, THREAD); + if (HAS_PENDING_EXCEPTION) { + if (PENDING_EXCEPTION->is_a(SystemDictionary::VirtualMachineError_klass())) { + return NULL; // propagate VMEs + } + if (log_is_enabled(Trace, class, nestmates)) { + stringStream ss; + char* target_member_class = host->constants()->klass_name_at(cp_index)->as_C_string(); + ss.print(" - resolution of nest member %s failed: ", target_member_class); + java_lang_Throwable::print(PENDING_EXCEPTION, &ss); + log_trace(class, nestmates)("%s", ss.as_string()); + } + CLEAR_PENDING_EXCEPTION; + continue; + } + if (k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + InstanceKlass* nest_host_k = ik->nest_host(CHECK_NULL); + if (nest_host_k == host) { + result->obj_at_put(count+1, k->java_mirror()); + count++; + log_trace(class, nestmates)(" - [%d] = %s", count, ik->external_name()); + } else { + log_trace(class, nestmates)(" - skipping member %s with different host %s", + ik->external_name(), nest_host_k->external_name()); + } + } else { + log_trace(class, nestmates)(" - skipping member %s that is not an instance class", + k->external_name()); + } + } + if (count < length) { + // we had invalid entries so we need to compact the array + log_trace(class, nestmates)(" - compacting array from length %d to %d", + length + 1, count + 1); + + objArrayOop r2 = oopFactory::new_objArray(SystemDictionary::Class_klass(), + count + 1, CHECK_NULL); + objArrayHandle result2(THREAD, r2); + for (int i = 0; i < count + 1; i++) { + result2->obj_at_put(i, result->obj_at(i)); + } + return (jobjectArray)JNIHandles::make_local(THREAD, result2()); } } else { - assert(host == ck, "must be singleton nest"); + assert(host == ck || ck->is_hidden(), "must be singleton nest or dynamic nestmate"); } return (jobjectArray)JNIHandles::make_local(THREAD, result()); } diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -150,8 +150,8 @@ } oop mirror = JNIHandles::resolve_non_null(_class_defs[i].klass); - // classes for primitives and arrays and vm unsafe anonymous classes cannot be redefined - // check here so following code can assume these classes are InstanceKlass + // classes for primitives, arrays, hidden and vm unsafe anonymous classes + // cannot be redefined. if (!is_modifiable_class(mirror)) { _res = JVMTI_ERROR_UNMODIFIABLE_CLASS; return false; @@ -293,8 +293,9 @@ return false; } - // Cannot redefine or retransform an unsafe anonymous class. - if (InstanceKlass::cast(k)->is_unsafe_anonymous()) { + // Cannot redefine or retransform a hidden or an unsafe anonymous class. + if (InstanceKlass::cast(k)->is_hidden() || + InstanceKlass::cast(k)->is_unsafe_anonymous()) { return false; } return true; @@ -1239,11 +1240,12 @@ // load hook event. state->set_class_being_redefined(the_class, _class_load_kind); + ClassLoadInfo cl_info(protection_domain); InstanceKlass* scratch_class = SystemDictionary::parse_stream( the_class_sym, the_class_loader, - protection_domain, &st, + cl_info, THREAD); // Clear class_being_redefined just to be sure. state->clear_class_being_redefined(); diff --git a/src/hotspot/share/prims/methodHandles.cpp b/src/hotspot/share/prims/methodHandles.cpp --- a/src/hotspot/share/prims/methodHandles.cpp +++ b/src/hotspot/share/prims/methodHandles.cpp @@ -1108,6 +1108,10 @@ template(java_lang_invoke_MemberName,MN_SEARCH_INTERFACES) \ template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_SHIFT) \ template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_MASK) \ + template(java_lang_invoke_MemberName,MN_NESTMATE_CLASS) \ + template(java_lang_invoke_MemberName,MN_HIDDEN_CLASS) \ + template(java_lang_invoke_MemberName,MN_STRONG_LOADER_LINK) \ + template(java_lang_invoke_MemberName,MN_ACCESS_VM_ANNOTATIONS) \ /*end*/ #define IGNORE_REQ(req_expr) /* req_expr */ @@ -1524,7 +1528,7 @@ {CC "clearCallSiteContext", CC "(" CTX ")V", FN_PTR(MHN_clearCallSiteContext)}, {CC "staticFieldOffset", CC "(" MEM ")J", FN_PTR(MHN_staticFieldOffset)}, {CC "staticFieldBase", CC "(" MEM ")" OBJ, FN_PTR(MHN_staticFieldBase)}, - {CC "getMemberVMInfo", CC "(" MEM ")" OBJ, FN_PTR(MHN_getMemberVMInfo)} + {CC "getMemberVMInfo", CC "(" MEM ")" OBJ, FN_PTR(MHN_getMemberVMInfo)} }; static JNINativeMethod MH_methods[] = { diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -850,12 +850,19 @@ ClassFileStream st(class_bytes, class_bytes_length, host_source, ClassFileStream::verify); Symbol* no_class_name = NULL; + ClassLoadInfo cl_info(host_domain, + InstanceKlass::cast(host_klass), + cp_patches, + NULL, // dynamic_nest_host + Handle(), // classData + false, // is_hidden + false, // is_strong_hidden + true); // can_access_vm_annotations + Klass* anonk = SystemDictionary::parse_stream(no_class_name, host_loader, - host_domain, &st, - InstanceKlass::cast(host_klass), - cp_patches, + cl_info, CHECK_NULL); if (anonk == NULL) { return NULL; diff --git a/src/hotspot/share/runtime/reflection.cpp b/src/hotspot/share/runtime/reflection.cpp --- a/src/hotspot/share/runtime/reflection.cpp +++ b/src/hotspot/share/runtime/reflection.cpp @@ -711,7 +711,7 @@ // Checks that the 'outer' klass has declared 'inner' as being an inner klass. If not, // throw an incompatible class change exception // If inner_is_member, require the inner to be a member of the outer. -// If !inner_is_member, require the inner to be unsafe anonymous (a non-member). +// If !inner_is_member, require the inner to be hidden or unsafe anonymous (non-members). // Caller is responsible for figuring out in advance which case must be true. void Reflection::check_for_inner_class(const InstanceKlass* outer, const InstanceKlass* inner, bool inner_is_member, TRAPS) { diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -517,6 +517,7 @@ nonstatic_field(ClassLoaderData, _class_loader, OopHandle) \ nonstatic_field(ClassLoaderData, _next, ClassLoaderData*) \ volatile_nonstatic_field(ClassLoaderData, _klasses, Klass*) \ + nonstatic_field(ClassLoaderData, _has_class_mirror_holder, bool) \ volatile_nonstatic_field(ClassLoaderData, _dictionary, Dictionary*) \ \ static_ptr_volatile_field(ClassLoaderDataGraph, _head, ClassLoaderData*) \ diff --git a/src/hotspot/share/utilities/accessFlags.hpp b/src/hotspot/share/utilities/accessFlags.hpp --- a/src/hotspot/share/utilities/accessFlags.hpp +++ b/src/hotspot/share/utilities/accessFlags.hpp @@ -66,6 +66,7 @@ JVM_ACC_IS_CLONEABLE_FAST = (int)0x80000000,// True if klass implements the Cloneable interface and can be optimized in generated code JVM_ACC_HAS_FINAL_METHOD = 0x01000000, // True if klass has final method JVM_ACC_IS_SHARED_CLASS = 0x02000000, // True if klass is shared + JVM_ACC_IS_HIDDEN_CLASS = 0x04000000, // True if klass is hidden // Klass* and Method* flags JVM_ACC_HAS_LOCAL_VARIABLE_TABLE= 0x00200000, @@ -149,6 +150,7 @@ bool has_final_method () const { return (_flags & JVM_ACC_HAS_FINAL_METHOD ) != 0; } bool is_cloneable_fast () const { return (_flags & JVM_ACC_IS_CLONEABLE_FAST ) != 0; } bool is_shared_class () const { return (_flags & JVM_ACC_IS_SHARED_CLASS ) != 0; } + bool is_hidden_class () const { return (_flags & JVM_ACC_IS_HIDDEN_CLASS ) != 0; } // Klass* and Method* flags bool has_localvariable_table () const { return (_flags & JVM_ACC_HAS_LOCAL_VARIABLE_TABLE) != 0; } @@ -221,6 +223,7 @@ void set_is_cloneable_fast() { atomic_set_bits(JVM_ACC_IS_CLONEABLE_FAST); } void set_has_miranda_methods() { atomic_set_bits(JVM_ACC_HAS_MIRANDA_METHODS); } void set_is_shared_class() { atomic_set_bits(JVM_ACC_IS_SHARED_CLASS); } + void set_is_hidden_class() { atomic_set_bits(JVM_ACC_IS_HIDDEN_CLASS); } public: // field flags diff --git a/src/hotspot/share/utilities/ostream.cpp b/src/hotspot/share/utilities/ostream.cpp --- a/src/hotspot/share/utilities/ostream.cpp +++ b/src/hotspot/share/utilities/ostream.cpp @@ -29,6 +29,7 @@ #include "oops/oop.inline.hpp" #include "runtime/arguments.hpp" #include "runtime/os.inline.hpp" +#include "runtime/orderAccess.hpp" #include "runtime/vm_version.hpp" #include "utilities/defaultStream.hpp" #include "utilities/macros.hpp" @@ -368,10 +369,16 @@ zero_terminate(); } -char* stringStream::as_string() const { - char* copy = NEW_RESOURCE_ARRAY(char, buffer_pos + 1); +char* stringStream::as_string(bool c_heap) const { + char* copy = c_heap ? + NEW_C_HEAP_ARRAY(char, buffer_pos + 1, mtInternal) : NEW_RESOURCE_ARRAY(char, buffer_pos + 1); strncpy(copy, buffer, buffer_pos); copy[buffer_pos] = 0; // terminating null + if (c_heap) { + // Need to ensure our content is written to memory before we return + // the pointer to it. + OrderAccess::storestore(); + } return copy; } diff --git a/src/hotspot/share/utilities/ostream.hpp b/src/hotspot/share/utilities/ostream.hpp --- a/src/hotspot/share/utilities/ostream.hpp +++ b/src/hotspot/share/utilities/ostream.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2020, 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 @@ -215,7 +215,8 @@ size_t size() const { return buffer_pos; } const char* base() const { return buffer; } void reset(); - char* as_string() const; + // copy to a resource, or C-heap, array as requested + char* as_string(bool c_heap = false) const; }; class fileStream : public outputStream { diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -28,6 +28,7 @@ import java.lang.annotation.Annotation; import java.lang.constant.ClassDesc; import java.lang.invoke.TypeDescriptor; +import java.lang.invoke.MethodHandles; import java.lang.module.ModuleReader; import java.lang.ref.SoftReference; import java.io.IOException; @@ -63,8 +64,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.StringJoiner; -import java.util.stream.Stream; import java.util.stream.Collectors; import jdk.internal.HotSpotIntrinsicCandidate; @@ -100,16 +99,42 @@ * keyword {@code void} are also represented as {@code Class} objects. * *

{@code Class} has no public constructor. Instead a {@code Class} - * object is constructed automatically by the Java Virtual Machine - * when a class loader invokes one of the - * {@link ClassLoader#defineClass(String,byte[], int,int) defineClass} methods - * and passes the bytes of a {@code class} file. + * object is constructed automatically by the Java Virtual Machine when + * a class is derived from the bytes of a {@code class} file through + * the invocation of one of the following methods: + *

    + *
  • {@link ClassLoader#defineClass(String, byte[], int, int) ClassLoader::defineClass} + *
  • {@link java.lang.invoke.MethodHandles.Lookup#defineClass(byte[]) + * java.lang.invoke.MethodHandles.Lookup::defineClass} + *
  • {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) + * java.lang.invoke.MethodHandles.Lookup::defineHiddenClass} + *
* *

The methods of class {@code Class} expose many characteristics of a * class or interface. Most characteristics are derived from the {@code class} - * file that the class loader passed to the Java Virtual Machine. A few - * characteristics are determined by the class loading environment at run time, - * such as the module returned by {@link #getModule() getModule()}. + * file that the class loader passed to the Java Virtual Machine or + * from the {@code class} file passed to {@code Lookup::defineClass} + * or {@code Lookup::defineHiddenClass}. + * A few characteristics are determined by the class loading environment + * at run time, such as the module returned by {@link #getModule() getModule()}. + * + *

The following example uses a {@code Class} object to print the + * class name of an object: + * + *

+ *     void printClassName(Object obj) {
+ *         System.out.println("The class of " + obj +
+ *                            " is " + obj.getClass().getName());
+ *     }
+ * 
+ * + * It is also possible to get the {@code Class} object for a named + * type (or for {@code void}) using a class literal. + * For example: + * + *
+ * {@code System.out.println("The name of class Foo is: "+Foo.class.getName());} + *
* *

Some methods of class {@code Class} expose whether the declaration of * a class or interface in Java source code was enclosed within @@ -128,24 +153,25 @@ * other members are the classes and interfaces whose declarations are * enclosed within the top-level class declaration. * - *

The following example uses a {@code Class} object to print the - * class name of an object: + *

A class or interface created by the invocation of + * {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) + * Lookup::defineHiddenClass} is a {@linkplain Class#isHidden() hidden} + * class or interface. + * All kinds of class, including enum types and record types, may be + * hidden classes; all kinds of interface, including annotation types, + * may be hidden interfaces. * - *

- *     void printClassName(Object obj) {
- *         System.out.println("The class of " + obj +
- *                            " is " + obj.getClass().getName());
- *     }
- * 
+ * The {@linkplain #getName() name of a hidden class or interface} is + * not a binary name, + * which means that a hidden class or interface cannot be + * referenced by the constant pools of other classes and interfaces, + * and cannot be discovered by {@link #forName Class::forName} or + * {@link ClassLoader#loadClass(String, boolean) ClassLoader::loadClass}. * - *

It is also possible to get the {@code Class} object for a named - * type (or for void) using a class literal. See Section {@jls - * 15.8.2} of The Java™ Language Specification. - * For example: - * - *

- * {@code System.out.println("The name of class Foo is: " + Foo.class.getName());} - *
+ * A hidden class or interface is never an array class, but may be + * the element type of an array. In all other respects, the fact that + * a class or interface is hidden has no bearing on the characteristics + * exposed by the methods of class {@code Class}. * * @param the type of the class modeled by this {@code Class} * object. For example, the type of {@code String.class} is {@code @@ -155,6 +181,7 @@ * @author unascribed * @see java.lang.ClassLoader#defineClass(byte[], int, int) * @since 1.0 + * @jls 15.8.2 Class Literals */ public final class Class implements java.io.Serializable, GenericDeclaration, @@ -186,9 +213,9 @@ /** * Converts the object to a string. The string representation is the * string "class" or "interface", followed by a space, and then by the - * fully qualified name of the class in the format returned by - * {@code getName}. If this {@code Class} object represents a - * primitive type, this method returns the name of the primitive type. If + * name of the class in the format returned by {@code getName}. + * If this {@code Class} object represents a primitive type, + * this method returns the name of the primitive type. If * this {@code Class} object represents void this method returns * "void". If this {@code Class} object represents an array type, * this method returns "class " followed by {@code getName}. @@ -745,11 +772,12 @@ } /** - * Returns {@code true} if this class is a synthetic class; - * returns {@code false} otherwise. - * @return {@code true} if and only if this class is a synthetic class as - * defined by The Java™ Language Specification. + * Returns {@code true} if and only if this class has the synthetic modifier + * bit set. + * + * @return {@code true} if and only if this class has the synthetic modifier bit set * @jls 13.1 The Form of a Binary + * @jvms 4.1 The {@code ClassFile} Structure * @since 1.5 */ public boolean isSynthetic() { @@ -758,22 +786,26 @@ /** * Returns the name of the entity (class, interface, array class, - * primitive type, or void) represented by this {@code Class} object, - * as a {@code String}. - * - *

If this {@code Class} object represents a reference type that is - * not an array type then the binary name of the class is - * returned, as specified by The Java™ Language - * Specification. - * - *

If this {@code Class} object represents a primitive type or void, then the - * name returned is a {@code String} equal to the Java language - * keyword corresponding to the primitive type or void. - * - *

If this {@code Class} object represents a class of arrays, then the internal - * form of the name consists of the name of the element type preceded by - * one or more '{@code [}' characters representing the depth of the array - * nesting. The encoding of element type names is as follows: + * primitive type, or void) represented by this {@code Class} object. + * + *

If this {@code Class} object represents a class or interface, + * not an array class, then: + *

    + *
  • If the class or interface is not {@linkplain #isHidden() hidden}, + * then the binary name + * of the class or interface is returned. + *
  • If the class or interface is hidden, then the result is a string + * of the form: {@code N + '/' + } + * where {@code N} is the binary name + * indicated by the {@code class} file passed to + * {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) + * Lookup::defineHiddenClass}, and {@code } is an unqualified name. + *
+ * + *

If this {@code Class} object represents an array class, then + * the result is a string consisting of one or more '{@code [}' characters + * representing the depth of the array nesting, followed by the element + * type as encoded using the following table: * *

* @@ -781,21 +813,22 @@ * - * *
Element types and encodings
Element Type Encoding * *
boolean Z - *
byte B - *
char C - *
class or interface - * Lclassname; - *
double D - *
float F - *
int I - *
long J - *
short S + *
{@code boolean} {@code Z} + *
{@code byte} {@code B} + *
{@code char} {@code C} + *
class or interface with binary name N + * {@code L}N{@code ;} + *
{@code double} {@code D} + *
{@code float} {@code F} + *
{@code int} {@code I} + *
{@code long} {@code J} + *
{@code short} {@code S} *
* - *

The class or interface name classname is the binary name of - * the class specified above. + *

If this {@code Class} object represents a primitive type or {@code void}, + * then the result is a string with the same spelling as the Java language + * keyword which corresponds to the primitive type or {@code void}. * *

Examples: *

@@ -809,8 +842,9 @@
      *     returns "[[[[[[[I"
      * 
* - * @return the name of the class or interface + * @return the name of the class, interface, or other entity * represented by this {@code Class} object. + * @jls 13.1 The Form of a Binary */ public String getName() { String name = this.name; @@ -888,6 +922,14 @@ // will throw NoSuchFieldException private final ClassLoader classLoader; + // Set by VM + private transient Object classData; + + // package-private + Object getClassData() { + return classData; + } + /** * Returns an array of {@code TypeVariable} objects that represent the * type variables declared by the generic declaration represented by this @@ -900,7 +942,7 @@ * @throws java.lang.reflect.GenericSignatureFormatError if the generic * signature of this generic declaration does not conform to * the format specified in section {@jvms 4.7.9} of - * The Java™ Virtual Machine Specification, + * The Java™ Virtual Machine Specification * @since 1.5 */ @SuppressWarnings("unchecked") @@ -1614,11 +1656,17 @@ } /** - * Returns the canonical name of the underlying class as defined - * by The Java™ Language Specification, section - * {@jls 6.7}. Returns null if the underlying class does not have - * a canonical name (i.e., if it is a local or anonymous class or - * an array whose component type does not have a canonical name). + * Returns the canonical name of the underlying class as + * defined by The Java™ Language Specification. + * Returns {@code null} if the underlying class does not have a canonical + * name. Classes without canonical names include: + *
    + *
  • a {@linkplain #isLocalClass() local class} + *
  • a {@linkplain #isAnonymousClass() anonymous class} + *
  • a {@linkplain #isHidden() hidden class} + *
  • an array whose component type does not have a canonical name
  • + *
+ * * @return the canonical name of the underlying class if it exists, and * {@code null} otherwise. * @since 1.5 @@ -1640,7 +1688,7 @@ else return ReflectionData.NULL_SENTINEL; } - if (isLocalOrAnonymousClass()) + if (isHidden() || isLocalOrAnonymousClass()) return ReflectionData.NULL_SENTINEL; Class enclosingClass = getEnclosingClass(); if (enclosingClass == null) { // top level class @@ -1657,6 +1705,9 @@ * Returns {@code true} if and only if the underlying class * is an anonymous class. * + * @apiNote + * An anonymous class is not a {@linkplain #isHidden() hidden class}. + * * @return {@code true} if and only if this class is an anonymous class. * @since 1.5 */ @@ -2882,6 +2933,11 @@ if (sm != null) { sm.checkPermission(SecurityConstants.GET_PD_PERMISSION); } + return protectionDomain(); + } + + // package-private + java.security.ProtectionDomain protectionDomain() { java.security.ProtectionDomain pd = getProtectionDomain0(); if (pd == null) { if (allPermDomain == null) { @@ -2896,7 +2952,6 @@ return pd; } - /** * Returns the ProtectionDomain of this class. */ @@ -4015,30 +4070,23 @@ /** * Returns the nest host of the nest to which the class * or interface represented by this {@code Class} object belongs. - * Every class and interface is a member of exactly one nest. - * A class or interface that is not recorded as belonging to a nest - * belongs to the nest consisting only of itself, and is the nest - * host. - * - *

Each of the {@code Class} objects representing array types, - * primitive types, and {@code void} returns {@code this} to indicate - * that the represented entity belongs to the nest consisting only of + * Every class and interface belongs to exactly one nest. + * + * If the nest host of this class or interface has previously + * been determined, then this method returns the nest host. + * If the nest host of this class or interface has + * not previously been determined, then this method determines the nest + * host using the algorithm of JVMS 5.4.4, and returns it. + * + * Often, a class or interface belongs to a nest consisting only of itself, + * in which case this method returns {@code this} to indicate that the class + * or interface is the nest host. + * + *

If this {@code Class} object represents a primitive type, an array type, + * or {@code void}, then this method returns {@code this}, + * indicating that the represented entity belongs to the nest consisting only of * itself, and is the nest host. * - *

If there is a {@linkplain LinkageError linkage error} accessing - * the nest host, or if this class or interface is not enumerated as - * a member of the nest by the nest host, then it is considered to belong - * to its own nest and {@code this} is returned as the host. - * - * @apiNote A {@code class} file of version 55.0 or greater may record the - * host of the nest to which it belongs by using the {@code NestHost} - * attribute (JVMS {@jvms 4.7.28}). Alternatively, a {@code class} file of - * version 55.0 or greater may act as a nest host by enumerating the nest's - * other members with the - * {@code NestMembers} attribute (JVMS {@jvms 4.7.29}). - * A {@code class} file of version 54.0 or lower does not use these - * attributes. - * * @return the nest host of this class or interface * * @throws SecurityException @@ -4058,17 +4106,9 @@ if (isPrimitive() || isArray()) { return this; } - Class host; - try { - host = getNestHost0(); - } catch (LinkageError e) { - // if we couldn't load our nest-host then we - // act as-if we have no nest-host attribute - return this; - } - // if null then nest membership validation failed, so we - // act as-if we have no nest-host attribute - if (host == null || host == this) { + + Class host = getNestHost0(); + if (host == this) { return this; } // returning a different class requires a security check @@ -4100,11 +4140,8 @@ c.isPrimitive() || c.isArray()) { return false; } - try { - return getNestHost0() == c.getNestHost0(); - } catch (LinkageError e) { - return false; - } + + return getNestHost() == c.getNestHost(); } private native Class[] getNestMembers0(); @@ -4113,39 +4150,47 @@ * Returns an array containing {@code Class} objects representing all the * classes and interfaces that are members of the nest to which the class * or interface represented by this {@code Class} object belongs. - * The {@linkplain #getNestHost() nest host} of that nest is the zeroth - * element of the array. Subsequent elements represent any classes or - * interfaces that are recorded by the nest host as being members of - * the nest; the order of such elements is unspecified. Duplicates are - * permitted. - * If the nest host of that nest does not enumerate any members, then the - * array has a single element containing {@code this}. - * - *

Each of the {@code Class} objects representing array types, - * primitive types, and {@code void} returns an array containing only + * + * First, this method obtains the {@linkplain #getNestHost() nest host}, + * {@code H}, of the nest to which the class or interface represented by + * this {@code Class} object belongs. The zeroth element of the returned + * array is {@code H}. + * + * Then, for each class or interface {@code C} which is recorded by {@code H} + * as being a member of its nest, this method attempts to obtain the {@code Class} + * object for {@code C} (using {@linkplain #getClassLoader() the defining class + * loader} of the current {@code Class} object), and then obtains the + * {@linkplain #getNestHost() nest host} of the nest to which {@code C} belongs. + * The classes and interfaces which are recorded by {@code H} as being members + * of its nest, and for which {@code H} can be determined as their nest host, + * are indicated by subsequent elements of the returned array. The order of + * such elements is unspecified. Duplicates are permitted. + * + *

If this {@code Class} object represents a primitive type, an array type, + * or {@code void}, then this method returns a single-element array containing * {@code this}. * - *

This method validates that, for each class or interface which is - * recorded as a member of the nest by the nest host, that class or - * interface records itself as a member of that same nest. Any exceptions - * that occur during this validation are rethrown by this method. + * @apiNote + * The returned array includes only the nest members recorded in the {@code NestMembers} + * attribute, and not any hidden classes that were added to the nest via + * {@link MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) + * Lookup::defineHiddenClass}. * * @return an array of all classes and interfaces in the same nest as - * this class - * - * @throws LinkageError - * If there is any problem loading or validating a nest member or - * its nest host + * this class or interface + * * @throws SecurityException - * If any returned class is not the current class, and - * if a security manager, s, is present and the caller's - * class loader is not the same as or an ancestor of the class - * loader for that returned class and invocation of {@link - * SecurityManager#checkPackageAccess s.checkPackageAccess()} - * denies access to the package of that returned class + * If any returned class is not the current class, and + * if a security manager, s, is present and the caller's + * class loader is not the same as or an ancestor of the class + * loader for that returned class and invocation of {@link + * SecurityManager#checkPackageAccess s.checkPackageAccess()} + * denies access to the package of that returned class * * @since 11 * @see #getNestHost() + * @jvms 4.7.28 The {@code NestHost} Attribute + * @jvms 4.7.29 The {@code NestMembers} Attribute */ @CallerSensitive public Class[] getNestMembers() { @@ -4230,5 +4275,17 @@ @Override public Optional describeConstable() { return Optional.of(ClassDesc.ofDescriptor(descriptorString())); - } + } + + /** + * Returns {@code true} if and only if the underlying class is a hidden class. + * + * @return {@code true} if and only if this class is a hidden class. + * + * @since 15 + * @see MethodHandles.Lookup#defineHiddenClass + */ + @HotSpotIntrinsicCandidate + public native boolean isHidden(); + } diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -1115,6 +1115,29 @@ int off, int len, ProtectionDomain pd, String source); + /** + * Defines a class of the given flags via Lookup.defineClass. + * + * @param loader the defining loader + * @param lookup nest host of the Class to be defined + * @param name the binary name or {@code null} if not findable + * @param b class bytes + * @param off the start offset in {@code b} of the class bytes + * @param len the length of the class bytes + * @param pd protection domain + * @param initialize initialize the class + * @param flags flags + * @param classData class data + */ + static native Class defineClass0(ClassLoader loader, + Class lookup, + String name, + byte[] b, int off, int len, + ProtectionDomain pd, + boolean initialize, + int flags, + Object classData); + // true if the name is null or has the potential to be a valid binary name private boolean checkName(String name) { if ((name == null) || (name.isEmpty())) diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -28,6 +28,10 @@ import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + /** * Helper for string concatenation. These methods are mostly looked up with private lookups * from {@link java.lang.invoke.StringConcatFactory}, and used in {@link java.lang.invoke.MethodHandle} @@ -466,4 +470,13 @@ return String.COMPACT_STRINGS ? LATIN1 : UTF16; } + static MethodHandle lookupStatic(String name, MethodType methodType) { + try { + return MethodHandles.lookup().findStatic(StringConcatHelper.class, name, methodType); + } catch (NoSuchMethodException|IllegalAccessException e) { + throw new AssertionError(e); + } + } + + } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -35,6 +35,8 @@ import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; import java.lang.module.ModuleDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; @@ -2175,6 +2177,10 @@ public Class defineClass(ClassLoader loader, String name, byte[] b, ProtectionDomain pd, String source) { return ClassLoader.defineClass1(loader, name, b, 0, b.length, pd, source); } + public Class defineClass(ClassLoader loader, Class lookup, String name, byte[] b, ProtectionDomain pd, + boolean initialize, int flags, Object classData) { + return ClassLoader.defineClass0(loader, lookup, name, b, 0, b.length, pd, initialize, flags, classData); + } public Class findBootstrapClassOrNull(ClassLoader cl, String name) { return cl.findBootstrapClassOrNull(name); } @@ -2257,6 +2263,18 @@ public void setCause(Throwable t, Throwable cause) { t.setCause(cause); } + + public ProtectionDomain protectionDomain(Class c) { + return c.protectionDomain(); + } + + public MethodHandle stringConcatHelper(String name, MethodType methodType) { + return StringConcatHelper.lookupStatic(name, methodType); + } + + public Object classData(Class c) { + return c.getClassData(); + } }); } } diff --git a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -51,6 +51,7 @@ * System.out.printf(">>> %s\n", iii.foo(44)); * }} */ + final MethodHandles.Lookup caller; // The caller's lookup context final Class targetClass; // The class calling the meta-factory via invokedynamic "class X" final MethodType invokedType; // The type of the invoked method "(CC)II" final Class samBase; // The type of the returned instance "interface JJ" @@ -120,6 +121,7 @@ "Invalid caller: %s", caller.lookupClass().getName())); } + this.caller = caller; this.targetClass = caller.lookupClass(); this.invokedType = invokedType; @@ -143,8 +145,20 @@ case REF_invokeSpecial: // JDK-8172817: should use referenced class here, but we don't know what it was this.implClass = implInfo.getDeclaringClass(); - this.implKind = REF_invokeSpecial; this.implIsInstanceMethod = true; + + // Classes compiled prior to dynamic nestmate support invokes a private instance + // method with REF_invokeSpecial. + // + // invokespecial should only be used to invoke private nestmate constructors. + // The lambda proxy class will be defined as a nestmate of targetClass. + // If the method to be invoked is an instance method of targetClass, then + // convert to use invokevirtual or invokeinterface. + if (targetClass == implClass && !implInfo.getName().equals("")) { + this.implKind = implClass.isInterface() ? REF_invokeInterface : REF_invokeVirtual; + } else { + this.implKind = REF_invokeSpecial; + } break; case REF_invokeStatic: case REF_newInvokeSpecial: diff --git a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java --- a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java +++ b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java @@ -196,24 +196,20 @@ private static byte[] generateCodeBytesForLFs(String className, String[] names, LambdaForm[] forms) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V1_8, Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, InvokerBytecodeGenerator.INVOKER_SUPER_NAME, null); cw.visitSource(className.substring(className.lastIndexOf('/') + 1), null); for (int i = 0; i < forms.length; i++) { - addMethod(className, names[i], forms[i], - forms[i].methodType(), cw); + InvokerBytecodeGenerator g + = new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()); + g.setClassWriter(cw); + g.addMethod(); } - return cw.toByteArray(); - } - private static void addMethod(String className, String methodName, LambdaForm form, - MethodType type, ClassWriter cw) { - InvokerBytecodeGenerator g - = new InvokerBytecodeGenerator(className, methodName, form, type); - g.setClassWriter(cw); - g.addMethod(); + return cw.toByteArray(); } private static LambdaForm makeReinvokerFor(MethodType type) { diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -27,13 +27,14 @@ import jdk.internal.org.objectweb.asm.*; import sun.invoke.util.BytecodeDescriptor; -import jdk.internal.misc.Unsafe; import sun.security.action.GetPropertyAction; import sun.security.action.GetBooleanAction; import java.io.FilePermission; import java.io.Serializable; +import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.LinkedHashSet; @@ -41,6 +42,8 @@ import java.util.PropertyPermission; import java.util.Set; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG; import static jdk.internal.org.objectweb.asm.Opcodes.*; /** @@ -50,13 +53,10 @@ * @see LambdaMetafactory */ /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - private static final int CLASSFILE_VERSION = 52; private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); private static final String JAVA_LANG_OBJECT = "java/lang/Object"; private static final String NAME_CTOR = ""; - private static final String NAME_FACTORY = "get$Lambda"; //Serialization support private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda"; @@ -64,13 +64,17 @@ private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V"; private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V"; + private static final String DESCR_SET_IMPL_METHOD = "(Ljava/lang/invoke/MethodHandle;)V"; + private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; private static final String NAME_METHOD_READ_OBJECT = "readObject"; private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; + private static final String NAME_FIELD_IMPL_METHOD = "protectedImplMethod"; private static final String DESCR_CLASS = "Ljava/lang/Class;"; private static final String DESCR_STRING = "Ljava/lang/String;"; private static final String DESCR_OBJECT = "Ljava/lang/Object;"; + private static final String DESCR_METHOD_HANDLE = "Ljava/lang/invoke/MethodHandle;"; private static final String DESCR_CTOR_SERIALIZED_LAMBDA = "(" + DESCR_CLASS + DESCR_STRING + DESCR_STRING + DESCR_STRING + "I" + DESCR_STRING + DESCR_STRING + DESCR_STRING + DESCR_STRING + "[" + DESCR_OBJECT + ")V"; @@ -78,8 +82,6 @@ private static final String DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION = "(Ljava/lang/String;)V"; private static final String[] SER_HOSTILE_EXCEPTIONS = new String[] {NAME_NOT_SERIALIZABLE_EXCEPTION}; - private static final String DESCR_HIDDEN = "Ljdk/internal/vm/annotation/Hidden;"; - private static final String[] EMPTY_STRING_ARRAY = new String[0]; // Used to ensure that each spun class name is unique @@ -108,6 +110,7 @@ private final String[] argNames; // Generated names for the constructor arguments private final String[] argDescs; // Type descriptors for the constructor arguments private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" + private final boolean useImplMethodHandle; // use MethodHandle invocation instead of symbolic bytecode invocation /** * General meta-factory constructor, supporting both standard cases and @@ -163,7 +166,9 @@ implMethodName = implInfo.getName(); implMethodDesc = implInfo.getMethodType().toMethodDescriptorString(); constructorType = invokedType.changeReturnType(Void.TYPE); - lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet(); + lambdaClassName = lambdaClassName(targetClass); + useImplMethodHandle = !implClass.getPackageName().equals(implInfo.getDeclaringClass().getPackageName()) + && !Modifier.isPublic(implInfo.getModifiers()); cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); int parameterCount = invokedType.parameterCount(); if (parameterCount > 0) { @@ -178,6 +183,15 @@ } } + private static String lambdaClassName(Class targetClass) { + String name = targetClass.getName(); + if (targetClass.isHidden()) { + // use the original class name + name = name.replace('/', '_'); + } + return name.replace('.', '/') + "$$Lambda$" + counter.incrementAndGet(); + } + /** * Build the CallSite. Generate a class file which implements the functional * interface, define the class, if there are no parameters create an instance @@ -217,20 +231,14 @@ try { Object inst = ctrs[0].newInstance(); return new ConstantCallSite(MethodHandles.constant(samBase, inst)); - } - catch (ReflectiveOperationException e) { + } catch (ReflectiveOperationException e) { throw new LambdaConversionException("Exception instantiating lambda object", e); } } else { try { - if (!disableEagerInitialization) { - UNSAFE.ensureClassInitialized(innerClass); - } - return new ConstantCallSite( - MethodHandles.Lookup.IMPL_LOOKUP - .findStatic(innerClass, NAME_FACTORY, invokedType)); - } - catch (ReflectiveOperationException e) { + MethodHandle mh = caller.findConstructor(innerClass, invokedType.changeReturnType(void.class)); + return new ConstantCallSite(mh.asType(invokedType)); + } catch (ReflectiveOperationException e) { throw new LambdaConversionException("Exception finding constructor", e); } } @@ -283,14 +291,9 @@ generateConstructor(); - if (invokedType.parameterCount() != 0 || disableEagerInitialization) { - generateFactory(); - } - // Forward the SAM method MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, samMethodType.toMethodDescriptorString(), null, null); - mv.visitAnnotation(DESCR_HIDDEN, true); new ForwardingMethodGenerator(mv).generate(samMethodType); // Forward the bridges @@ -298,11 +301,18 @@ for (MethodType mt : additionalBridges) { mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName, mt.toMethodDescriptorString(), null, null); - mv.visitAnnotation(DESCR_HIDDEN, true); new ForwardingMethodGenerator(mv).generate(mt); } } + if (useImplMethodHandle) { + FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, + NAME_FIELD_IMPL_METHOD, + DESCR_METHOD_HANDLE, + null, null); + fv.visitEnd(); + } + if (isSerializable) generateSerializationFriendlyMethods(); else if (accidentallySerializable) @@ -313,7 +323,6 @@ // Define the generated class in this VM. final byte[] classBytes = cw.toByteArray(); - // If requested, dump out to a file for debugging purposes if (dumper != null) { AccessController.doPrivileged(new PrivilegedAction<>() { @@ -327,28 +336,26 @@ // createDirectories may need it new PropertyPermission("user.dir", "read")); } - - return UNSAFE.defineAnonymousClass(targetClass, classBytes, null); - } - - /** - * Generate the factory method for the class - */ - private void generateFactory() { - MethodVisitor m = cw.visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_FACTORY, invokedType.toMethodDescriptorString(), null, null); - m.visitCode(); - m.visitTypeInsn(NEW, lambdaClassName); - m.visitInsn(Opcodes.DUP); - int parameterCount = invokedType.parameterCount(); - for (int typeIndex = 0, varIndex = 0; typeIndex < parameterCount; typeIndex++) { - Class argType = invokedType.parameterType(typeIndex); - m.visitVarInsn(getLoadOpcode(argType), varIndex); - varIndex += getParameterSize(argType); + try { + // this class is linked at the indy callsite; so define a hidden nestmate + Lookup lookup = caller.defineHiddenClass(classBytes, !disableEagerInitialization, NESTMATE, STRONG); + if (useImplMethodHandle) { + // If the target class invokes a method reference this::m which is + // resolved to a protected method inherited from a superclass in a different + // package, the target class does not have a bridge and this method reference + // has been changed from public to protected after the target class was compiled. + // This lambda proxy class has no access to the resolved method. + // So this workaround by passing the live implMethod method handle + // to the proxy class to invoke directly. + MethodHandle mh = lookup.findStaticSetter(lookup.lookupClass(), NAME_FIELD_IMPL_METHOD, MethodHandle.class); + mh.invokeExact(implMethod); + } + return lookup.lookupClass(); + } catch (IllegalAccessException e) { + throw new LambdaConversionException("Exception defining lambda proxy class", e); + } catch (Throwable t) { + throw new InternalError(t); } - m.visitMethodInsn(INVOKESPECIAL, lambdaClassName, NAME_CTOR, constructorType.toMethodDescriptorString(), false); - m.visitInsn(ARETURN); - m.visitMaxs(-1, -1); - m.visitEnd(); } /** @@ -464,6 +471,10 @@ visitTypeInsn(NEW, implMethodClassName); visitInsn(DUP); } + if (useImplMethodHandle) { + visitVarInsn(ALOAD, 0); + visitFieldInsn(GETSTATIC, lambdaClassName, NAME_FIELD_IMPL_METHOD, DESCR_METHOD_HANDLE); + } for (int i = 0; i < argNames.length; i++) { visitVarInsn(ALOAD, 0); visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); @@ -471,11 +482,16 @@ convertArgumentTypes(methodType); - // Invoke the method we want to forward to - visitMethodInsn(invocationOpcode(), implMethodClassName, - implMethodName, implMethodDesc, - implClass.isInterface()); - + if (useImplMethodHandle) { + MethodType mtype = implInfo.getMethodType().insertParameterTypes(0, implClass); + visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", + "invokeExact", mtype.descriptorString(), false); + } else { + // Invoke the method we want to forward to + visitMethodInsn(invocationOpcode(), implMethodClassName, + implMethodName, implMethodDesc, + implClass.isInterface()); + } // Convert the return value (if any) and return it // Note: if adapting from non-void to void, the 'return' // instruction will pop the unneeded result diff --git a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java --- a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java +++ b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java @@ -26,6 +26,7 @@ package java.lang.invoke; import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.FieldVisitor; import jdk.internal.org.objectweb.asm.Label; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.stream.Stream; import static java.lang.invoke.LambdaForm.BasicType; @@ -49,6 +51,7 @@ import static java.lang.invoke.LambdaForm.*; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandles.Lookup.*; /** * Code generation backend for LambdaForm. @@ -67,6 +70,8 @@ private static final String LOOP_CLAUSES = MHI + "$LoopClauses"; private static final String MHARY2 = "[[L" + MH + ";"; + private static final String MH_SIG = "L" + MH + ";"; + private static final String LF_SIG = "L" + LF + ";"; private static final String LFN_SIG = "L" + LFN + ";"; @@ -92,6 +97,7 @@ /** ASM bytecode generation. */ private ClassWriter cw; private MethodVisitor mv; + private final List classData = new ArrayList<>(); /** Single element internal class name lookup cache. */ private Class lastClass; @@ -99,6 +105,15 @@ private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory(); private static final Class HOST_CLASS = LambdaForm.class; + private static final MethodHandles.Lookup LOOKUP = lookup(); + + private static MethodHandles.Lookup lookup() { + try { + return MethodHandles.privateLookupIn(HOST_CLASS, IMPL_LOOKUP); + } catch (IllegalAccessException e) { + throw newInternalError(e); + } + } /** Main constructor; other constructors delegate to this one. */ private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize, @@ -221,41 +236,52 @@ return className; } - class CpPatch { - final int index; + public static class ClassData { + final String name; + final String desc; final Object value; - CpPatch(int index, Object value) { - this.index = index; + + ClassData(String name, String desc, Object value) { + this.name = name; + this.desc = desc; this.value = value; } + + public String name() { return name; } public String toString() { - return "CpPatch/index="+index+",value="+value; + return name + ",value="+value; } } - private final ArrayList cpPatches = new ArrayList<>(); - - private int cph = 0; // for counting constant placeholders + String classData(Object arg) { + String desc; + if (arg instanceof Class) { + desc = "Ljava/lang/Class;"; + } else if (arg instanceof MethodHandle) { + desc = MH_SIG; + } else if (arg instanceof LambdaForm) { + desc = LF_SIG; + } else { + desc = "Ljava/lang/Object;"; + } - String constantPlaceholder(Object arg) { - String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++; - if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + debugString(arg) + ">>"; - // TODO check if arg is already in the constant pool - // insert placeholder in CP and remember the patch - int index = cw.newConst((Object) cpPlaceholder); - cpPatches.add(new CpPatch(index, arg)); - return cpPlaceholder; + Class c = arg.getClass(); + while (c.isArray()) { + c = c.getComponentType(); + } + // unique static variable name + String name = "_DATA_" + c.getSimpleName() + "_" + classData.size(); + ClassData cd = new ClassData(name, desc, arg); + classData.add(cd); + return cd.name(); } - Object[] cpPatches(byte[] classFile) { - int size = getConstantPoolSize(classFile); - Object[] res = new Object[size]; - for (CpPatch p : cpPatches) { - if (p.index >= size) - throw new InternalError("in cpool["+size+"]: "+p+"\n"+Arrays.toString(Arrays.copyOf(classFile, 20))); - res[p.index] = p.value; + List classDataValues() { + Object[] data = new Object[classData.size()]; + for (int i = 0; i < classData.size(); i++) { + data[i] = classData.get(i).value; } - return res; + return List.of(data); } private static String debugString(Object arg) { @@ -288,19 +314,11 @@ * Extract the MemberName of a newly-defined method. */ private MemberName loadMethod(byte[] classFile) { - Class invokerClass = loadAndInitializeInvokerClass(classFile, cpPatches(classFile)); + Class invokerClass = LOOKUP.makeHiddenClassDefiner(classFile) + .defineClass(true, classDataValues()); return resolveInvokerMember(invokerClass, invokerName, invokerType); } - /** - * Define a given class as anonymous class in the runtime system. - */ - private static Class loadAndInitializeInvokerClass(byte[] classBytes, Object[] patches) { - Class invokerClass = UNSAFE.defineAnonymousClass(HOST_CLASS, classBytes, patches); - UNSAFE.ensureClassInitialized(invokerClass); // Make sure the class is initialized; VM might complain. - return invokerClass; - } - private static MemberName resolveInvokerMember(Class invokerClass, String name, MethodType type) { MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic); try { @@ -316,7 +334,8 @@ */ private ClassWriter classFilePrologue() { final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC - cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + setClassWriter(cw); cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, CLASS_PREFIX + className, null, INVOKER_SUPER_NAME, null); cw.visitSource(SOURCE_PREFIX + className, null); @@ -336,6 +355,51 @@ mv.visitEnd(); } + private String className() { + return CLASS_PREFIX + className; + } + + private void clinit() { + clinit(cw, className(), classData); + } + + /* + * to initialize the static final fields with the live class data + * LambdaForms can't use condy due to bootstrapping issue. + */ + static void clinit(ClassWriter cw, String className, List classData) { + if (classData.isEmpty()) + return; + + for (ClassData p : classData) { + // add the static field + FieldVisitor fv = cw.visitField(Opcodes.ACC_STATIC|Opcodes.ACC_FINAL, p.name, p.desc, null, null); + fv.visitEnd(); + } + + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); + mv.visitCode(); + mv.visitLdcInsn(Type.getType("L" + className + ";")); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandleNatives", + "classData", "(Ljava/lang/Class;)Ljava/lang/Object;", false); + // we should optimize one single element case that does not need to create a List + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/util/List"); + mv.visitVarInsn(Opcodes.ASTORE, 0); + int index = 0; + for (ClassData p : classData) { + // initialize the static field + mv.visitVarInsn(Opcodes.ALOAD, 0); + emitIconstInsn(mv, index++); + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", + "get", "(I)Ljava/lang/Object;", true); + mv.visitTypeInsn(Opcodes.CHECKCAST, p.desc.substring(1, p.desc.length()-1)); + mv.visitFieldInsn(Opcodes.PUTSTATIC, className, p.name, p.desc); + } + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + } + /* * Low-level emit helpers. */ @@ -408,6 +472,10 @@ } private void emitIconstInsn(final int cst) { + emitIconstInsn(mv, cst); + } + + private static void emitIconstInsn(MethodVisitor mv, int cst) { if (cst >= -1 && cst <= 5) { mv.visitInsn(Opcodes.ICONST_0 + cst); } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { @@ -577,8 +645,7 @@ String sig = getInternalName(cls); mv.visitTypeInsn(Opcodes.CHECKCAST, sig); } else { - mv.visitLdcInsn(constantPlaceholder(cls)); - mv.visitTypeInsn(Opcodes.CHECKCAST, CLS); + mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(cls), "Ljava/lang/Class;"); mv.visitInsn(Opcodes.SWAP); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG, false); if (Object[].class.isAssignableFrom(cls)) @@ -737,6 +804,7 @@ private byte[] generateCustomizedCodeBytes() { classFilePrologue(); addMethod(); + clinit(); bogusMethod(lambdaForm); final byte[] classFile = toByteArray(); @@ -764,14 +832,14 @@ mv.visitAnnotation(DONTINLINE_SIG, true); } - constantPlaceholder(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled. + classData(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled. if (lambdaForm.customized != null) { // Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute // receiver MethodHandle (at slot #0) with an embedded constant and use it instead. // It enables more efficient code generation in some situations, since embedded constants // are compile-time constants for JIT compiler. - mv.visitLdcInsn(constantPlaceholder(lambdaForm.customized)); + mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(lambdaForm.customized), MH_SIG); mv.visitTypeInsn(Opcodes.CHECKCAST, MH); assert(checkActualReceiver()); // expects MethodHandle on top of the stack mv.visitVarInsn(Opcodes.ASTORE, localsMap[0]); @@ -901,7 +969,7 @@ // push receiver MethodHandle target = name.function.resolvedHandle(); assert(target != null) : name.exprString(); - mv.visitLdcInsn(constantPlaceholder(target)); + mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(target), MH_SIG); emitReferenceCast(MethodHandle.class, target); } else { // load receiver @@ -957,7 +1025,9 @@ return false; // inner class of some sort if (cls.getClassLoader() != MethodHandle.class.getClassLoader()) return false; // not on BCP - if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: switch to supported API once it is added + if (cls.isHidden()) + return false; + if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: Unsafe::defineAnonymousClass to be removed return false; if (!isStaticallyInvocableType(member.getMethodOrFieldType())) return false; @@ -981,14 +1051,16 @@ if (cls == Object.class) return true; if (MethodHandle.class.isAssignableFrom(cls)) { - assert(!ReflectUtil.isVMAnonymousClass(cls)); + assert(!cls.isHidden()); return true; } while (cls.isArray()) cls = cls.getComponentType(); if (cls.isPrimitive()) return true; // int[].class, for example - if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: switch to supported API once it is added + if (cls.isHidden()) + return false; + if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: Unsafe::defineAnonymousClass to be removed return false; // could use VerifyAccess.isClassAccessible but the following is a safe approximation if (cls.getClassLoader() != Object.class.getClassLoader()) @@ -1060,7 +1132,7 @@ } assert(java.lang.reflect.Array.getLength(emptyArray) == 0); assert(emptyArray.getClass() == rtype); // exact typing - mv.visitLdcInsn(constantPlaceholder(emptyArray)); + mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(emptyArray), "Ljava/lang/Object;"); emitReferenceCast(rtype, emptyArray); return; } @@ -1623,7 +1695,7 @@ if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) { emitConst(arg); } else { - mv.visitLdcInsn(constantPlaceholder(arg)); + mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(arg), "Ljava/lang/Object;"); emitImplicitConversion(L_TYPE, ptype, arg); } } @@ -1815,6 +1887,7 @@ emitReturnInsn(basicType(rtype)); methodEpilogue(); + clinit(); bogusMethod(invokerType); final byte[] classFile = cw.toByteArray(); @@ -1883,6 +1956,7 @@ emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value. methodEpilogue(); + clinit(); bogusMethod(dstType); final byte[] classFile = cw.toByteArray(); diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -27,7 +27,6 @@ import jdk.internal.access.JavaLangInvokeAccess; import jdk.internal.access.SharedSecrets; -import jdk.internal.org.objectweb.asm.AnnotationVisitor; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.reflect.CallerSensitive; @@ -40,6 +39,7 @@ import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; +import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Array; import java.nio.ByteOrder; import java.util.Arrays; @@ -1157,10 +1157,24 @@ return restoreToType(bccInvoker.bindTo(vamh), mh, hostClass); } - private static MethodHandle makeInjectedInvoker(Class hostClass) { + private static MethodHandle makeInjectedInvoker(Class targetClass) { try { - Class invokerClass = UNSAFE.defineAnonymousClass(hostClass, INJECTED_INVOKER_TEMPLATE, null); - assert checkInjectedInvoker(hostClass, invokerClass); + /* + * The invoker class defined to the same class loader as the lookup class + * but in an unnamed package so that the class bytes can be cached and + * reused for any @CSM. + * + * @CSM must be public and exported if called by any module. + */ + String name = targetClass.getName() + "$$InjectedInvoker"; + if (targetClass.isHidden()) { + // use the original class name + name = name.replace('/', '_'); + } + Class invokerClass = new Lookup(targetClass) + .makeHiddenClassDefiner(name, INJECTED_INVOKER_TEMPLATE) + .defineClass(true); + assert checkInjectedInvoker(targetClass, invokerClass); return IMPL_LOOKUP.findStatic(invokerClass, "invoke_V", INVOKER_MT); } catch (ReflectiveOperationException ex) { throw uncaughtException(ex); @@ -1256,10 +1270,6 @@ "(Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;", null, null); - // Suppress invoker method in stack traces. - AnnotationVisitor av0 = mv.visitAnnotation(InvokerBytecodeGenerator.HIDDEN_SIG, true); - av0.visitEnd(); - mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -25,6 +25,8 @@ package java.lang.invoke; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; import jdk.internal.ref.CleanerFactory; import sun.invoke.util.Wrapper; @@ -137,6 +139,15 @@ REF_newInvokeSpecial = 8, REF_invokeInterface = 9, REF_LIMIT = 10; + + /** + * Flags for Lookup.ClassOptions + */ + static final int + NESTMATE_CLASS = 0x00000001, + HIDDEN_CLASS = 0x00000002, + STRONG_LOADER_LINK = 0x00000004, + ACCESS_VM_ANNOTATIONS = 0x00000008; } static boolean refKindIsValid(int refKind) { @@ -659,4 +670,13 @@ return (definingClass.isAssignableFrom(symbolicRefClass) || // Msym overrides Mdef symbolicRefClass.isInterface()); // Mdef implements Msym } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + /* + * A convenient method for LambdaForms to get the class data of a given class. + * LambdaForms cannot use condy via MethodHandles.classData + */ + static Object classData(Class c) { + return JLA.classData(c); + } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -25,9 +25,12 @@ package java.lang.invoke; +import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.VM; import jdk.internal.module.IllegalAccessLogger; import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.Opcodes; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.vm.annotation.ForceInline; @@ -45,8 +48,6 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ReflectPermission; import java.nio.ByteOrder; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; @@ -219,6 +220,10 @@ * @see Cross-module lookups */ public static Lookup privateLookupIn(Class targetClass, Lookup caller) throws IllegalAccessException { + if (caller.allowedModes == Lookup.TRUSTED) { + return new Lookup(targetClass); + } + SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(ACCESS_PERMISSION); if (targetClass.isPrimitive()) @@ -263,6 +268,57 @@ } /** + * Returns the class data associated with the lookup class + * of the specified {@code Lookup} object, or {@code null}. + * + *

Classes can be created with class data by calling + * {@link Lookup#defineHiddenClassWithClassData(byte[], Object, Lookup.ClassOption...) + * Lookup::defineHiddenClassWithClassData}. + * A hidden class with a class data behaves as if the hidden class + * has a private static final unnamed field pre-initialized with + * the class data and this method is equivalent as if calling + * {@link ConstantBootstraps#getStaticFinal(Lookup, String, Class)} to + * obtain the value of such field corresponding to the class data. + * + *

The {@linkplain Lookup#lookupModes() lookup modes} for this lookup + * must have {@link Lookup#ORIGINAL ORIGINAL} access in order to retrieve + * the class data. + * + * @apiNote + * This method can be called as a bootstrap method for a dynamically computed + * constant. A framework can create a hidden class with class data, for + * example that can be {@code List.of(o1, o2, o3....)} containing more than + * one live object. The class data is accessible only to the lookup object + * created by the original caller but inaccessible to other members + * in the same nest. If a framework passes security sensitive live objects + * to a hidden class via class data, it is recommended to load the value + * of class data as a dynamically computed constant instead of storing + * the live objects in private fields which are accessible to other + * nestmates. + * + * @param the type to cast the class data object to + * @param caller the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name ignored + * @param type the type of the class data + * @return the value of the class data if present in the lookup class; + * otherwise {@code null} + * @throws IllegalAccessException if the lookup context does not have + * original caller access + * @throws ClassCastException if the class data cannot be converted to + * the specified {@code type} + * @see Lookup#defineHiddenClassWithClassData(byte[], Object, Lookup.ClassOption...) + * @since 15 + */ + static T classData(Lookup caller, String name, Class type) throws IllegalAccessException { + if (!caller.hasFullPrivilegeAccess()) { + throw new IllegalAccessException(caller + " does not have full privilege access"); + } + Object classData = MethodHandleNatives.classData(caller.lookupClass); + return type.cast(classData); + } + + /** * Performs an unchecked "crack" of a * direct method handle. * The result is as if the user had obtained a lookup object capable enough @@ -517,7 +573,7 @@ * that the receiver argument must match both the resolved method and * the current class. Again, this requirement is enforced by narrowing the * type of the leading parameter to the resulting method handle. - * (See the Java Virtual Machine Specification, section {@jmvs 4.10.1.9}.) + * (See the Java Virtual Machine Specification, section {@jvms 4.10.1.9}.) *

* The JVM represents constructors and static initializer blocks as internal methods * with special names ({@code ""} and {@code ""}). @@ -1400,8 +1456,6 @@ */ Lookup(Class lookupClass) { this(lookupClass, null, FULL_POWER_MODES); - // make sure we haven't accidentally picked up a privileged class: - checkUnprivilegedlookupClass(lookupClass); } private Lookup(Class lookupClass, Class prevLookupClass, int allowedModes) { @@ -1508,7 +1562,7 @@ } // Allow nestmate lookups to be created without special privilege: if ((newModes & PRIVATE) != 0 - && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) { + && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) { newModes &= ~(PRIVATE|PROTECTED); } if ((newModes & (PUBLIC|UNCONDITIONAL)) != 0 @@ -1577,9 +1631,12 @@ } /** - * Defines a class to the same class loader and in the same runtime package and + * Creates a class or interface from {@code bytes} + * with the same class loader and in the same runtime package and * {@linkplain java.security.ProtectionDomain protection domain} as this lookup's - * {@linkplain #lookupClass() lookup class}. + * {@linkplain #lookupClass() lookup class} as if calling + * {@link ClassLoader#defineClass(String,byte[],int,int,ProtectionDomain) + * ClassLoader::defineClass}. * *

The {@linkplain #lookupModes() lookup modes} for this lookup must include * {@link #PACKAGE PACKAGE} access as default (package) members will be @@ -1602,11 +1659,12 @@ * * @param bytes the class bytes * @return the {@code Class} object for the class + * @throws IllegalAccessException if this lookup does not have {@code PACKAGE} access + * @throws ClassFormatError if {@code bytes} is not a {@code ClassFile} structure * @throws IllegalArgumentException the bytes are for a class in a different package * to the lookup class - * @throws IllegalAccessException if this lookup does not have {@code PACKAGE} access - * @throws LinkageError if the class is malformed ({@code ClassFormatError}), cannot be - * verified ({@code VerifyError}), is already defined, or another linkage error occurs + * @throws VerifyError if the newly created class cannot be verified + * @throws LinkageError if the newly created class cannot be linked for any other reason * @throws SecurityException if a security manager is present and it * refuses access * @throws NullPointerException if {@code bytes} is {@code null} @@ -1617,66 +1675,530 @@ * @see ClassLoader#defineClass(String,byte[],int,int,ProtectionDomain) */ public Class defineClass(byte[] bytes) throws IllegalAccessException { + ensureDefineClassPermission(); + if ((lookupModes() & PACKAGE) == 0) + throw new IllegalAccessException("Lookup does not have PACKAGE access"); + return makeClassDefiner(bytes.clone()).defineClass(false); + } + + private void ensureDefineClassPermission() { + if (allowedModes == TRUSTED) return; + if (!hasFullPrivilegeAccess()) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(new RuntimePermission("defineClass")); } - if ((lookupModes() & PACKAGE) == 0) - throw new IllegalAccessException("Lookup does not have PACKAGE access"); - - // parse class bytes to get class name (in internal form) - bytes = bytes.clone(); - String name; + } + + /** + * The set of class options that specify whether a hidden class created by + * {@link Lookup#defineHiddenClass(byte[], boolean, ClassOption...) + * Lookup::defineHiddenClass} method is dynamically added as a new member + * to the nest of a lookup class and/or whether a hidden class has + * a strong relationship with the class loader marked as its defining loader. + * + * @since 15 + */ + public enum ClassOption { + /** + * Specifies that a hidden class be added to {@linkplain Class#getNestHost nest} + * of a lookup class as a nestmate. + * + *

A hidden nestmate class has access to the private members of all + * classes and interfaces in the same nest. + * + * @see Class#getNestHost() + */ + NESTMATE(NESTMATE_CLASS), + + /** + * Specifies that a hidden class has a strong + * relationship with the class loader marked as its defining loader, + * as a normal class or interface has with its own defining loader. + * This means that the hidden class may be unloaded if and only if + * its defining loader is not reachable and thus may be reclaimed + * by a garbage collector (JLS 12.7). + * + *

By default, a hidden class or interface may be unloaded + * even if the class loader that is marked as its defining loader is + * reachable. + + * + * @jls 12.7 Unloading of Classes and Interfaces + */ + STRONG(STRONG_LOADER_LINK); + + /* the flag value is used by VM at define class time */ + private final int flag; + ClassOption(int flag) { + this.flag = flag; + } + + static int optionsToFlag(Set options) { + int flags = 0; + for (ClassOption cp : options) { + flags |= cp.flag; + } + return flags; + } + } + + /** + * Creates a hidden class or interface from {@code bytes}, + * returning a {@code Lookup} on the newly created class or interface. + * + *

Ordinarily, a class or interface {@code C} is created by a class loader, + * which either defines {@code C} directly or delegates to another class loader. + * A class loader defines {@code C} directly by invoking + * {@link ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain) + * ClassLoader::defineClass}, which causes the Java Virtual Machine + * to derive {@code C} from a purported representation in {@code class} file format. + * In situations where use of a class loader is undesirable, a class or interface + * {@code C} can be created by this method instead. This method is capable of + * defining {@code C}, and thereby creating it, without invoking + * {@code ClassLoader::defineClass}. + * Instead, this method defines {@code C} as if by arranging for + * the Java Virtual Machine to derive a nonarray class or interface {@code C} + * from a purported representation in {@code class} file format + * using the following rules: + * + *

    + *
  1. The {@linkplain #lookupModes() lookup modes} for this {@code Lookup} + * must include {@linkplain #hasFullPrivilegeAccess() full privilege} access. + * This level of access is needed to create {@code C} in the module + * of the lookup class of this {@code Lookup}.
  2. + * + *
  3. The purported representation in {@code bytes} must be a {@code ClassFile} + * structure of a supported major and minor version. The major and minor version + * may differ from the {@code class} file version of the lookup class of this + * {@code Lookup}.
  4. + * + *
  5. The value of {@code this_class} must be a valid index in the + * {@code constant_pool} table, and the entry at that index must be a valid + * {@code CONSTANT_Class_info} structure. Let {@code N} be the binary name + * encoded in internal form that is specified by this structure. {@code N} must + * denote a class or interface in the same package as the lookup class.
  6. + * + *
  7. Let {@code CN} be the string {@code N + "." + }, + * where {@code } is an unqualified name. + * + *

    Let {@code newBytes} be the {@code ClassFile} structure given by + * {@code bytes} with an additional entry in the {@code constant_pool} table, + * indicating a {@code CONSTANT_Utf8_info} structure for {@code CN}, and + * where the {@code CONSTANT_Class_info} structure indicated by {@code this_class} + * refers to the new {@code CONSTANT_Utf8_info} structure. + * + *

    Let {@code L} be the defining class loader of the lookup class of this {@code Lookup}. + * + *

    {@code C} is derived with name {@code CN}, class loader {@code L}, and + * purported representation {@code newBytes} as if by the rules of JVMS {@jvms 5.3.5}, + * with the following adjustments: + *

      + *
    • The constant indicated by {@code this_class} is permitted to specify a name + * that includes a single {@code "."} character, even though this is not a valid + * binary class or interface name in internal form.
    • + * + *
    • The Java Virtual Machine marks {@code L} as the defining class loader of {@code C}, + * but no class loader is recorded as an initiating class loader of {@code C}.
    • + * + *
    • {@code C} is considered to have the same runtime + * {@linkplain Class#getPackage() package}, {@linkplain Class#getModule() module} + * and {@linkplain java.security.ProtectionDomain protection domain} + * as the lookup class of this {@code Lookup}. + *
    • Let {@code GN} be the binary name obtained by taking {@code N} + * (a binary name encoded in internal form) and replacing ASCII forward slashes with + * ASCII periods. For the instance of {@link java.lang.Class} representing {@code C}, + * {@link Class#getName()} returns the string {@code GN + "/" + }, even though + * this is not a valid binary class or interface name.
    • + *
    + *
  8. + *
+ * + *

After {@code C} is derived, it is linked by the Java Virtual Machine. + * Linkage occurs as specified in JVMS {@jvms 5.4.3}, with the following adjustments: + *

    + *
  • During verification, whenever it is necessary to load the class named + * {@code CN}, the attempt succeeds, producing class {@code C}. No request is + * made of any class loader.
  • + * + *
  • On any attempt to resolve the entry in the run-time constant pool indicated + * by {@code this_class}, the symbolic reference is considered to be resolved to + * {@code C} and resolution always succeeds immediately.
  • + *
+ * + *

If the {@code initialize} parameter is {@code true}, + * then {@code C} is initialized by the Java Virtual Machine. + * + *

The newly created class or interface {@code C} serves as the + * {@linkplain #lookupClass() lookup class} of the {@code Lookup} object + * returned by this method. {@code C} is hidden in the sense that + * no other class or interface can refer to {@code C} via a constant pool entry. + * That is, a hidden class or interface cannot be named as a supertype, a field type, + * a method parameter type, or a method return type by any other class. + * This is because a hidden class or interface does not have a binary name, so + * there is no internal form available to record in any class's constant pool. + * A hidden class or interface is not discoverable by {@link Class#forName(String, boolean, ClassLoader)}, + * {@link ClassLoader#loadClass(String, boolean)}, or {@link #findClass(String)}, and + * is not {@linkplain java.lang.instrument.Instrumentation#isModifiableClass(Class) + * modifiable} by Java agents or tool agents using the + * JVM Tool Interface. + * + *

A class or interface created by + * {@linkplain ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain) + * a class loader} has a strong relationship with that class loader. + * That is, every {@code Class} object contains a reference to the {@code ClassLoader} + * that {@linkplain Class#getClassLoader() defined it}. + * This means that a class created by a class loader may be unloaded if and + * only if its defining loader is not reachable and thus may be reclaimed + * by a garbage collector (JLS 12.7). + * + * By default, however, a hidden class or interface may be unloaded even if + * the class loader that is marked as its defining loader is + * reachable. + * This behavior is useful when a hidden class or interface serves multiple + * classes defined by arbitrary class loaders. In other cases, a hidden + * class or interface may be linked to a single class (or a small number of classes) + * with the same defining loader as the hidden class or interface. + * In such cases, where the hidden class or interface must be coterminous + * with a normal class or interface, the {@link ClassOption#STRONG STRONG} + * option may be passed in {@code options}. + * This arranges for a hidden class to have the same strong relationship + * with the class loader marked as its defining loader, + * as a normal class or interface has with its own defining loader. + * + * If {@code STRONG} is not used, then the invoker of {@code defineHiddenClass} + * may still prevent a hidden class or interface from being + * unloaded by ensuring that the {@code Class} object is reachable. + * + *

The unloading characteristics are set for each hidden class when it is + * defined, and cannot be changed later. An advantage of allowing hidden classes + * to be unloaded independently of the class loader marked as their defining loader + * is that a very large number of hidden classes may be created by an application. + * In contrast, if {@code STRONG} is used, then the JVM may run out of memory, + * just as if normal classes were created by class loaders. + * + *

Classes and interfaces in a nest are allowed to have mutual access to + * their private members. The nest relationship is determined by + * the {@code NestHost} attribute (JVMS {@jvms 4.7.28}) and + * the {@code NestMembers} attribute (JVMS {@jvms 4.7.29}) in a {@code class} file. + * By default, a hidden class belongs to a nest consisting only of itself + * because a hidden class has no binary name. + * The {@link ClassOption#NESTMATE NESTMATE} option can be passed in {@code options} + * to create a hidden class or interface {@code C} as a member of a nest. + * The nest to which {@code C} belongs is not based on any {@code NestHost} attribute + * in the {@code ClassFile} structure from which {@code C} was derived. + * Instead, the following rules determine the nest host of {@code C}: + *

    + *
  • If the nest host of the lookup class of this {@code Lookup} has previously + * been determined, then let {@code H} be the nest host of the lookup class. + * Otherwise, the nest host of the lookup class is determined using the + * algorithm in JVMS {@jvms 5.4.4}, yielding {@code H}.
  • + *
  • The nest host of {@code C} is determined to be {@code H}, + * the nest host of the lookup class.
  • + *
+ * + *

A hidden class or interface may be serializable, but this requires a custom + * serialization mechanism in order to ensure that instances are properly serialized + * and deserialized. The default serialization mechanism supports only classes and + * interfaces that are discoverable by their class name. + * + * @param bytes the bytes that make up the class data, + * in the format of a valid {@code class} file as defined by + * The Java Virtual Machine Specification. + * @param initialize if {@code true} the class will be initialized. + * @param options {@linkplain ClassOption class options} + * @return the {@code Lookup} object on the hidden class + * + * @throws IllegalAccessException if this {@code Lookup} does not have + * {@linkplain #hasFullPrivilegeAccess() full privilege} access + * @throws SecurityException if a security manager is present and it + * refuses access + * @throws ClassFormatError if {@code bytes} is not a {@code ClassFile} structure + * @throws UnsupportedClassVersionError if {@code bytes} is not of a supported major or minor version + * @throws IllegalArgumentException if {@code bytes} is not a class or interface or + * {@bytes} denotes a class in a different package than the lookup class + * @throws IncompatibleClassChangeError if the class or interface named as + * the direct superclass of {@code C} is in fact an interface, or if any of the classes + * or interfaces named as direct superinterfaces of {@code C} are not in fact interfaces + * @throws ClassCircularityError if any of the superclasses or superinterfaces of + * {@code C} is {@code C} itself + * @throws VerifyError if the newly created class cannot be verified + * @throws LinkageError if the newly created class cannot be linked for any other reason + * @throws NullPointerException if any parameter is {@code null} + * + * @since 15 + * @see Class#isHidden() + * @jvms 4.2.1 Binary Class and Interface Names + * @jvms 4.2.2 Unqualified Names + * @jvms 4.7.28 The {@code NestHost} Attribute + * @jvms 4.7.29 The {@code NestMembers} Attribute + * @jvms 5.4.3.1 Class and Interface Resolution + * @jvms 5.4.4 Access Control + * @jvms 5.3.5 Deriving a {@code Class} from a {@code class} File Representation + * @jvms 5.4 Linking + * @jvms 5.5 Initialization + * @jls 12.7 Unloading of Classes and Interfaces + */ + public Lookup defineHiddenClass(byte[] bytes, boolean initialize, ClassOption... options) + throws IllegalAccessException + { + Objects.requireNonNull(bytes); + Objects.requireNonNull(options); + + ensureDefineClassPermission(); + if (!hasFullPrivilegeAccess()) { + throw new IllegalAccessException(this + " does not have full privilege access"); + } + + return makeHiddenClassDefiner(bytes.clone(), Set.of(options), false).defineClassAsLookup(initialize); + } + + /** + * Creates a hidden class or interface from {@code bytes} with associated + * {@linkplain MethodHandles#classData(Lookup, String, Class) class data}, + * returning a {@code Lookup} on the newly created class or interface. + * + *

This method is equivalent to calling + * {@link #defineHiddenClass(byte[], boolean, ClassOption...) defineHiddenClass(bytes, true, options)} + * as if the hidden class has a private static final unnamed field whose value + * is initialized to {@code classData} right before the class initializer is + * executed. The newly created class is linked and initialized by the Java + * Virtual Machine. + * + *

The {@link MethodHandles#classData(Lookup, String, Class) MethodHandles::classData} + * method can be used to retrieve the {@code classData}. + * + * @param bytes the class bytes + * @param classData pre-initialized class data + * @param options {@linkplain ClassOption class options} + * @return the {@code Lookup} object on the hidden class + * + * @throws IllegalAccessException if this {@code Lookup} does not have + * {@linkplain #hasFullPrivilegeAccess() full privilege} access + * @throws SecurityException if a security manager is present and it + * refuses access + * @throws ClassFormatError if {@code bytes} is not a {@code ClassFile} structure + * @throws UnsupportedClassVersionError if {@code bytes} is not of a supported major or minor version + * @throws IllegalArgumentException if {@code bytes} is not a class or interface or + * {@bytes} denotes a class in a different package than the lookup class + * @throws IncompatibleClassChangeError if the class or interface named as + * the direct superclass of {@code C} is in fact an interface, or if any of the classes + * or interfaces named as direct superinterfaces of {@code C} are not in fact interfaces + * @throws ClassCircularityError if any of the superclasses or superinterfaces of + * {@code C} is {@code C} itself + * @throws VerifyError if the newly created class cannot be verified + * @throws LinkageError if the newly created class cannot be linked for any other reason + * @throws NullPointerException if any parameter is {@code null} + * + * @since 15 + * @see Lookup#defineHiddenClass(byte[], boolean, ClassOption...) + * @see Class#isHidden() + */ + /* package-private */ Lookup defineHiddenClassWithClassData(byte[] bytes, Object classData, ClassOption... options) + throws IllegalAccessException + { + Objects.requireNonNull(bytes); + Objects.requireNonNull(classData); + Objects.requireNonNull(options); + + ensureDefineClassPermission(); + if (!hasFullPrivilegeAccess()) { + throw new IllegalAccessException(this + " does not have full privilege access"); + } + + return makeHiddenClassDefiner(bytes.clone(), Set.of(options), false) + .defineClassAsLookup(true, classData); + } + + /* + * Validates the given bytes to be a class or interface and the class name + * is in the same package as the lookup class. + * + * This method returns the class name. + */ + private String validateAndGetClassName(byte[] bytes) { try { ClassReader reader = new ClassReader(bytes); - name = reader.getClassName(); + if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) { + throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set"); + } + String name = reader.getClassName().replace('/', '.'); + int index = name.lastIndexOf('.'); + String pn = (index == -1) ? "" : name.substring(0, index); + if (!pn.equals(lookupClass.getPackageName())) { + throw newIllegalArgumentException(name + " not in same package as lookup class: " + + lookupClass.getName()); + } + return name; + } catch (IllegalArgumentException e) { + throw e; } catch (RuntimeException e) { // ASM exceptions are poorly specified ClassFormatError cfe = new ClassFormatError(); cfe.initCause(e); throw cfe; } - - // get package and class name in binary form - String cn, pn; - int index = name.lastIndexOf('/'); - if (index == -1) { - cn = name; - pn = ""; - } else { - cn = name.replace('/', '.'); - pn = cn.substring(0, index); + } + + + /* + * Returns a ClassDefiner that creates a {@code Class} object of a normal class + * from the given bytes. + * + * Caller should make a defensive copy of the arguments if needed + * before calling this factory method. + * + * @throws IllegalArgumentException if {@code bytes} is not a class or interface or + * {@bytes} denotes a class in a different package than the lookup class + */ + private ClassDefiner makeClassDefiner(byte[] bytes) { + return new ClassDefiner(this, validateAndGetClassName(bytes), bytes, STRONG_LOADER_LINK); + } + + /** + * Returns a ClassDefiner that creates a {@code Class} object of a hidden class + * from the given bytes. The name must be in the same package as the lookup class. + * + * Caller should make a defensive copy of the arguments if needed + * before calling this factory method. + * + * @param bytes class bytes + * @return ClassDefiner that defines a hidden class of the given bytes. + * + * @throws IllegalArgumentException if {@code bytes} is not a class or interface or + * {@bytes} denotes a class in a different package than the lookup class + */ + ClassDefiner makeHiddenClassDefiner(byte[] bytes) { + return makeHiddenClassDefiner(validateAndGetClassName(bytes), bytes, Set.of(), false); + } + + /** + * Returns a ClassDefiner that creates a {@code Class} object of a hidden class + * from the given bytes and options. + * The name must be in the same package as the lookup class. + * + * Caller should make a defensive copy of the arguments if needed + * before calling this factory method. + * + * @param bytes class bytes + * @param options class options + * @param accessVmAnnotations true to give the hidden class access to VM annotations + * @return ClassDefiner that defines a hidden class of the given bytes and options + * + * @throws IllegalArgumentException if {@code bytes} is not a class or interface or + * {@bytes} denotes a class in a different package than the lookup class + */ + ClassDefiner makeHiddenClassDefiner(byte[] bytes, + Set options, + boolean accessVmAnnotations) { + return makeHiddenClassDefiner(validateAndGetClassName(bytes), bytes, options, accessVmAnnotations); + } + + /** + * Returns a ClassDefiner that creates a {@code Class} object of a hidden class + * from the given bytes. No package name check on the given name. + * + * @param name fully-qualified name that specifies the prefix of the hidden class + * @param bytes class bytes + * @return ClassDefiner that defines a hidden class of the given bytes. + */ + ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes) { + return makeHiddenClassDefiner(name, bytes, Set.of(), false); + } + + /** + * Returns a ClassDefiner that creates a {@code Class} object of a hidden class + * from the given bytes and options. No package name check on the given name. + * + * @param name the name of the class and the name in the class bytes is ignored. + * @param bytes class bytes + * @param options class options + * @param accessVmAnnotations true to give the hidden class access to VM annotations + */ + ClassDefiner makeHiddenClassDefiner(String name, + byte[] bytes, + Set options, + boolean accessVmAnnotations) { + int flags = HIDDEN_CLASS | ClassOption.optionsToFlag(options); + if (accessVmAnnotations | VM.isSystemDomainLoader(lookupClass.getClassLoader())) { + // jdk.internal.vm.annotations are permitted for classes + // defined to boot loader and platform loader + flags |= ACCESS_VM_ANNOTATIONS; } - if (!pn.equals(lookupClass.getPackageName())) { - throw new IllegalArgumentException("Class not in same package as lookup class"); + + return new ClassDefiner(this, name, bytes, flags); + } + + static class ClassDefiner { + private final Lookup lookup; + private final String name; + private final byte[] bytes; + private final int classFlags; + + private ClassDefiner(Lookup lookup, String name, byte[] bytes, int flags) { + assert ((flags & HIDDEN_CLASS) != 0 || (flags & STRONG_LOADER_LINK) == STRONG_LOADER_LINK); + this.lookup = lookup; + this.bytes = bytes; + this.classFlags = flags; + this.name = name; + } + + String className() { + return name; + } + + Class defineClass(boolean initialize) { + return defineClass(initialize, null); + } + + Lookup defineClassAsLookup(boolean initialize) { + Class c = defineClass(initialize, null); + return new Lookup(c, null, FULL_POWER_MODES); } - // invoke the class loader's defineClass method - ClassLoader loader = lookupClass.getClassLoader(); - ProtectionDomain pd = (loader != null) ? lookupClassProtectionDomain() : null; - String source = "__Lookup_defineClass__"; - Class clazz = SharedSecrets.getJavaLangAccess().defineClass(loader, cn, bytes, pd, source); - return clazz; + /** + * Defines the class of the given bytes and the given classData. + * If {@code initialize} parameter is true, then the class will be initialized. + * + * @param initialize true if the class to be initialized + * @param classData classData or null + * @return the class + * + * @throws LinkageError linkage error + */ + Class defineClass(boolean initialize, Object classData) { + Class lookupClass = lookup.lookupClass(); + ClassLoader loader = lookupClass.getClassLoader(); + ProtectionDomain pd = (loader != null) ? lookup.lookupClassProtectionDomain() : null; + Class c = JLA.defineClass(loader, lookupClass, name, bytes, pd, initialize, classFlags, classData); + assert !isNestmate() || c.getNestHost() == lookupClass.getNestHost(); + return c; + } + + Lookup defineClassAsLookup(boolean initialize, Object classData) { + // initialize must be true if classData is non-null + assert classData == null || initialize == true; + Class c = defineClass(initialize, classData); + return new Lookup(c, null, FULL_POWER_MODES); + } + + private boolean isNestmate() { + return (classFlags & NESTMATE_CLASS) != 0; + } } private ProtectionDomain lookupClassProtectionDomain() { ProtectionDomain pd = cachedProtectionDomain; if (pd == null) { - cachedProtectionDomain = pd = protectionDomain(lookupClass); + cachedProtectionDomain = pd = JLA.protectionDomain(lookupClass); } return pd; } - private ProtectionDomain protectionDomain(Class clazz) { - PrivilegedAction pa = clazz::getProtectionDomain; - return AccessController.doPrivileged(pa); - } - // cached protection domain private volatile ProtectionDomain cachedProtectionDomain; - // Make sure outer class is initialized first. static { IMPL_NAMES.getClass(); } @@ -1689,6 +2211,8 @@ */ static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, null, UNCONDITIONAL); + static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + private static void checkUnprivilegedlookupClass(Class lookupClass) { String name = lookupClass.getName(); if (name.startsWith("java.lang.invoke.")) @@ -1749,7 +2273,7 @@ return cname + "/package"; case FULL_POWER_MODES & (~PROTECTED): case FULL_POWER_MODES & ~(PROTECTED|MODULE): - return cname + "/private"; + return cname + "/private"; case FULL_POWER_MODES: case FULL_POWER_MODES & (~MODULE): return cname; @@ -2639,8 +3163,13 @@ private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException { MemberName field = new MemberName(f, isSetter); - if (isSetter && field.isStatic() && field.isFinal()) - throw field.makeAccessException("static final field has no write access", this); + if (isSetter && field.isFinal()) { + if (field.isStatic()) { + throw field.makeAccessException("static final field has no write access", this); + } else if (field.getDeclaringClass().isHidden()){ + throw field.makeAccessException("final field in a hidden class has no write access", this); + } + } assert(isSetter ? MethodHandleNatives.refKindIsSetter(field.getReferenceKind()) : MethodHandleNatives.refKindIsGetter(field.getReferenceKind())); @@ -3201,7 +3730,8 @@ } refc = lookupClass(); } - return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(), this.allowedModes == TRUSTED); + return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(), + this.allowedModes == TRUSTED && !getField.getDeclaringClass().isHidden()); } /** Check access and get the requested constructor. */ private MethodHandle getDirectConstructor(Class refc, MemberName ctor) throws IllegalAccessException { diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java --- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -25,7 +25,8 @@ package java.lang.invoke; -import jdk.internal.misc.Unsafe; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; import jdk.internal.misc.VM; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Label; @@ -42,6 +43,9 @@ import java.util.concurrent.ConcurrentMap; import java.util.function.Function; +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodType.methodType; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; import static jdk.internal.org.objectweb.asm.Opcodes.*; /** @@ -133,6 +137,8 @@ */ private static final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT; + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + private enum Strategy { /** * Bytecode generator, calling into {@link java.lang.StringBuilder}. @@ -189,8 +195,6 @@ */ private static final ProxyClassesDumper DUMPER; - private static final Class STRING_HELPER; - static { // In case we need to double-back onto the StringConcatFactory during this // static initialization, make sure we have the reasonable defaults to complete @@ -202,12 +206,6 @@ // DEBUG = false; // implied // DUMPER = null; // implied - try { - STRING_HELPER = Class.forName("java.lang.StringConcatHelper"); - } catch (Throwable e) { - throw new AssertionError(e); - } - final String strategy = VM.getSavedProperty("java.lang.invoke.stringConcat"); CACHE_ENABLE = Boolean.parseBoolean( @@ -718,25 +716,10 @@ private static String getClassName(Class hostClass) throws StringConcatException { /* - When cache is enabled, we want to cache as much as we can. - - However, there are two peculiarities: - - a) The generated class should stay within the same package as the - host class, to allow Unsafe.defineAnonymousClass access controls - to work properly. JDK may choose to fail with IllegalAccessException - when accessing a VM anonymous class with non-privileged callers, - see JDK-8058575. + The generated class is in the same package as the host class as + it's the implementation of the string concatenation for the host class. - b) If we mark the stub with some prefix, say, derived from the package - name because of (a), we can technically use that stub in other packages. - But the call stack traces would be extremely puzzling to unsuspecting users - and profiling tools: whatever stub wins the race, would be linked in all - similar callsites. - - Therefore, we set the class prefix to match the host class package, and use - the prefix as the cache key too. This only affects BC_* strategies, and only when - cache is enabled. + When cache is enabled, we want to cache as much as we can. */ switch (STRATEGY) { @@ -745,9 +728,11 @@ case BC_SB_SIZED_EXACT: { if (CACHE_ENABLE) { String pkgName = hostClass.getPackageName(); - return (pkgName != null && !pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat"; + return (!pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat"; } else { - return hostClass.getName().replace('.', '/') + "$$StringConcat"; + String name = hostClass.isHidden() ? hostClass.getName().replace('/', '_') + : hostClass.getName(); + return name.replace('.', '/') + "$$StringConcat"; } } case MH_SB_SIZED: @@ -819,7 +804,7 @@ * chain javac would otherwise emit. This strategy uses only the public API, * and comes as the baseline for the current JDK behavior. On other words, * this strategy moves the javac generated bytecode to runtime. The - * generated bytecode is loaded via Unsafe.defineAnonymousClass, but with + * generated bytecode is loaded via Lookup::defineClass, but with * the caller class coming from the BSM -- in other words, the protection * guarantees are inherited from the method where invokedynamic was * originally called. This means, among other things, that the bytecode is @@ -848,7 +833,6 @@ * private String API. */ private static final class BytecodeStringBuilderStrategy { - static final Unsafe UNSAFE = Unsafe.getUnsafe(); static final int CLASSFILE_VERSION = 52; static final String METHOD_NAME = "concat"; @@ -861,7 +845,7 @@ cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC, - className, // Unsafe.defineAnonymousClass would append an unique ID + className, null, "java/lang/Object", null @@ -874,6 +858,7 @@ null, null); + // use of @ForceInline no longer has any effect mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true); mv.visitCode(); @@ -1143,11 +1128,9 @@ byte[] classBytes = cw.toByteArray(); try { - Class hostClass = lookup.lookupClass(); - Class innerClass = UNSAFE.defineAnonymousClass(hostClass, classBytes, null); - UNSAFE.ensureClassInitialized(innerClass); - dumpIfEnabled(innerClass.getName(), classBytes); - return Lookup.IMPL_LOOKUP.findStatic(innerClass, METHOD_NAME, args); + Class innerClass = lookup.defineHiddenClass(classBytes, true, STRONG).lookupClass(); + dumpIfEnabled(className, classBytes); + return lookup.findStatic(innerClass, METHOD_NAME, args); } catch (Exception e) { dumpIfEnabled(className + "$$FAILED", classBytes); throw new StringConcatException("Exception while spinning the class", e); @@ -1270,8 +1253,8 @@ * computation on MethodHandle combinators. The computation is built with * public MethodHandle APIs, resolved from a public Lookup sequence, and * ends up calling the public StringBuilder API. Therefore, this strategy - * does not use any private API at all, even the Unsafe.defineAnonymousClass, - * since everything is handled under cover by java.lang.invoke APIs. + * does not use any private API at all since everything is handled under + * cover by java.lang.invoke APIs. * *

{@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder, * sized exactly". @@ -1283,7 +1266,6 @@ * private String API. */ private static final class MethodHandleStringBuilderStrategy { - private MethodHandleStringBuilderStrategy() { // no instantiation } @@ -1461,6 +1443,8 @@ return sum; } + private static final Lookup MHSBS_LOOKUP = lookup(); + private static final ConcurrentMap SUMMERS; // This one is deliberately non-lambdified to optimize startup time: @@ -1474,9 +1458,9 @@ // unroll some initial sizes. Class[] cls = new Class[cnt]; Arrays.fill(cls, int.class); - return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls); + return lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls); } else { - return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class) + return lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class) .asCollector(int[].class, cnt - 1); } } @@ -1491,8 +1475,8 @@ STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class); BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class); if (DEBUG) { - BUILDER_TO_STRING_CHECKED = lookupStatic(MethodHandles.Lookup.IMPL_LOOKUP, - MethodHandleStringBuilderStrategy.class, "toStringChecked", String.class, StringBuilder.class); + BUILDER_TO_STRING_CHECKED = lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class, + "toStringChecked", String.class, StringBuilder.class); } else { BUILDER_TO_STRING_CHECKED = null; } @@ -1516,8 +1500,6 @@ * that requires porting if there are private JDK changes occur. */ private static final class MethodHandleInlineCopyStrategy { - static final Unsafe UNSAFE = Unsafe.getUnsafe(); - private MethodHandleInlineCopyStrategy() { // no instantiation } @@ -1736,8 +1718,9 @@ private static final Function, MethodHandle> PREPEND = new Function<>() { @Override public MethodHandle apply(Class c) { - return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class, - String.class, Wrapper.asPrimitiveType(c), String.class); + return JLA.stringConcatHelper("prepend", + methodType(long.class, long.class, byte[].class, + String.class, Wrapper.asPrimitiveType(c), String.class)); } }; @@ -1745,8 +1728,7 @@ private static final Function, MethodHandle> MIX = new Function<>() { @Override public MethodHandle apply(Class c) { - return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class, - Wrapper.asPrimitiveType(c)); + return JLA.stringConcatHelper("mix", methodType(long.class, long.class, Wrapper.asPrimitiveType(c))); } }; @@ -1759,7 +1741,7 @@ static { try { - MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", long.class); + MethodHandle initCoder = JLA.stringConcatHelper("initialCoder", methodType(long.class)); INITIAL_CODER = (long) initCoder.invoke(); } catch (Throwable e) { throw new AssertionError(e); @@ -1768,9 +1750,9 @@ PREPENDERS = new ConcurrentHashMap<>(); MIXERS = new ConcurrentHashMap<>(); - SIMPLE = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "simpleConcat", String.class, Object.class, Object.class); - NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, long.class); - NEW_ARRAY = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newArray", byte[].class, long.class); + SIMPLE = JLA.stringConcatHelper("simpleConcat", methodType(String.class, Object.class, Object.class)); + NEW_STRING = JLA.stringConcatHelper("newString", methodType(String.class, byte[].class, long.class)); + NEW_ARRAY = JLA.stringConcatHelper( "newArray", methodType(byte[].class, long.class)); } } @@ -1784,7 +1766,7 @@ } private static final MethodHandle OBJECT_INSTANCE = - lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "stringOf", String.class, Object.class); + JLA.stringConcatHelper("stringOf", methodType(String.class, Object.class)); private static class FloatStringifiers { private static final MethodHandle FLOAT_INSTANCE = diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -176,6 +176,12 @@ * to the caller and the package containing the declaring class is not open * to the caller's module.

* + *

This method cannot be used to enable {@linkplain Field#set write} + * access to a final field declared in a {@linkplain Class#isHidden() hidden class}, + * since such fields are not modifiable. The {@code accessible} flag when + * {@code true} suppresses Java language access control checks to only + * enable {@linkplain Field#get read} access to such fields. + * *

If there is a security manager, its * {@code checkPermission} method is first called with a * {@code ReflectPermission("suppressAccessChecks")} permission. diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -721,10 +721,19 @@ * the underlying field is inaccessible, the method throws an * {@code IllegalAccessException}. * - *

If the underlying field is final, the method throws an - * {@code IllegalAccessException} unless {@code setAccessible(true)} - * has succeeded for this {@code Field} object - * and the field is non-static. Setting a final field in this way + *

If the underlying field is final, this {@code Field} object has + * write access if and only if the following conditions are met: + *

    + *
  • {@link #setAccessible(boolean) setAccessible(true)} has succeeded for + * this {@code Field} object;
  • + *
  • the field is non-static; and
  • + *
  • the field's declaring class is not a {@linkplain Class#isHidden() + * hidden class}.
  • + *
+ * If any of the above checks is not met, this method throws an + * {@code IllegalAccessException}. + * + *

Setting a final field in this way * is meaningful only during deserialization or reconstruction of * instances of classes with blank final fields, before they are * made available for access by other parts of a program. Use in @@ -756,7 +765,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -791,7 +801,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -827,7 +838,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -863,7 +875,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -899,7 +912,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -935,7 +949,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -971,7 +986,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -1007,7 +1023,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), @@ -1043,7 +1060,8 @@ * * @throws IllegalAccessException if this {@code Field} object * is enforcing Java language access control and the underlying - * field is either inaccessible or final. + * field is either inaccessible or final; + * or if this {@code Field} object has no write access. * @throws IllegalArgumentException if the specified object is not an * instance of the class or interface declaring the underlying * field (or a subclass or implementor thereof), diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -26,6 +26,9 @@ package jdk.internal.access; import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.module.ModuleDescriptor; import java.lang.reflect.Executable; import java.lang.reflect.Method; @@ -148,6 +151,14 @@ Class defineClass(ClassLoader cl, String name, byte[] b, ProtectionDomain pd, String source); /** + * Defines a class with the given name to a class loader with + * the given flags and class data. + * + * @see java.lang.invoke.MethodHandles.Lookup#defineClass + */ + Class defineClass(ClassLoader cl, Class lookup, String name, byte[] b, ProtectionDomain pd, boolean initialize, int flags, Object classData); + + /** * Returns a class loaded by the bootstrap class loader. */ Class findBootstrapClassOrNull(ClassLoader cl, String name); @@ -311,4 +322,21 @@ * @param cause set t's cause to new value */ void setCause(Throwable t, Throwable cause); + + /** + * Get protection domain of the given Class + */ + ProtectionDomain protectionDomain(Class c); + + /** + * Get a method handle of string concat helper method + */ + MethodHandle stringConcatHelper(String name, MethodType methodType); + + /* + * Get the class data associated with the given class. + * @param c the class + * @see java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) + */ + Object classData(Class c); } diff --git a/src/java.base/share/classes/jdk/internal/reflect/NativeConstructorAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/NativeConstructorAccessorImpl.java --- a/src/java.base/share/classes/jdk/internal/reflect/NativeConstructorAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/NativeConstructorAccessorImpl.java @@ -25,8 +25,9 @@ package jdk.internal.reflect; +import sun.reflect.misc.ReflectUtil; + import java.lang.reflect.*; -import sun.reflect.misc.ReflectUtil; /** Used only for the first few invocations of a Constructor; afterward, switches to bytecode-based implementation */ @@ -49,6 +50,7 @@ // because that kind of class can't be referred to by name, hence can't // be found from the generated bytecode. if (++numInvocations > ReflectionFactory.inflationThreshold() + && !c.getDeclaringClass().isHidden() && !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) { ConstructorAccessorImpl acc = (ConstructorAccessorImpl) new MethodAccessorGenerator(). diff --git a/src/java.base/share/classes/jdk/internal/reflect/NativeMethodAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/NativeMethodAccessorImpl.java --- a/src/java.base/share/classes/jdk/internal/reflect/NativeMethodAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/NativeMethodAccessorImpl.java @@ -25,8 +25,9 @@ package jdk.internal.reflect; +import sun.reflect.misc.ReflectUtil; + import java.lang.reflect.*; -import sun.reflect.misc.ReflectUtil; /** Used only for the first few invocations of a Method; afterward, switches to bytecode-based implementation */ @@ -47,6 +48,7 @@ // that kind of class can't be referred to by name, hence can't be // found from the generated bytecode. if (++numInvocations > ReflectionFactory.inflationThreshold() + && !method.getDeclaringClass().isHidden() && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) { MethodAccessorImpl acc = (MethodAccessorImpl) new MethodAccessorGenerator(). diff --git a/src/java.base/share/classes/jdk/internal/reflect/Reflection.java b/src/java.base/share/classes/jdk/internal/reflect/Reflection.java --- a/src/java.base/share/classes/jdk/internal/reflect/Reflection.java +++ b/src/java.base/share/classes/jdk/internal/reflect/Reflection.java @@ -51,7 +51,7 @@ fieldFilterMap = Map.of( Reflection.class, ALL_MEMBERS, AccessibleObject.class, ALL_MEMBERS, - Class.class, Set.of("classLoader"), + Class.class, Set.of("classLoader", "classData"), ClassLoader.class, ALL_MEMBERS, Constructor.class, ALL_MEMBERS, Field.class, ALL_MEMBERS, diff --git a/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java b/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java --- a/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java +++ b/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java @@ -200,7 +200,8 @@ method = root; } - if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) { + if (noInflation && !method.getDeclaringClass().isHidden() + && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) { return new MethodAccessorGenerator(). generateMethod(method.getDeclaringClass(), method.getName(), @@ -244,7 +245,8 @@ return new BootstrapConstructorAccessorImpl(c); } - if (noInflation && !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) { + if (noInflation && !c.getDeclaringClass().isHidden() + && !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) { return new MethodAccessorGenerator(). generateConstructor(c.getDeclaringClass(), c.getParameterTypes(), diff --git a/src/java.base/share/classes/jdk/internal/reflect/UnsafeFieldAccessorFactory.java b/src/java.base/share/classes/jdk/internal/reflect/UnsafeFieldAccessorFactory.java --- a/src/java.base/share/classes/jdk/internal/reflect/UnsafeFieldAccessorFactory.java +++ b/src/java.base/share/classes/jdk/internal/reflect/UnsafeFieldAccessorFactory.java @@ -35,7 +35,7 @@ boolean isFinal = Modifier.isFinal(field.getModifiers()); boolean isVolatile = Modifier.isVolatile(field.getModifiers()); boolean isQualified = isFinal || isVolatile; - boolean isReadOnly = isFinal && (isStatic || !override); + boolean isReadOnly = isFinal && (isStatic || !override || field.getDeclaringClass().isHidden()); if (isStatic) { // This code path does not guarantee that the field's // declaring class has been initialized, but it must be diff --git a/src/java.base/share/native/libjava/Class.c b/src/java.base/share/native/libjava/Class.c --- a/src/java.base/share/native/libjava/Class.c +++ b/src/java.base/share/native/libjava/Class.c @@ -61,6 +61,7 @@ {"getSigners", "()[" OBJ, (void *)&JVM_GetClassSigners}, {"setSigners", "([" OBJ ")V", (void *)&JVM_SetClassSigners}, {"isArray", "()Z", (void *)&JVM_IsArrayClass}, + {"isHidden", "()Z", (void *)&JVM_IsHiddenClass}, {"isPrimitive", "()Z", (void *)&JVM_IsPrimitiveClass}, {"getModifiers", "()I", (void *)&JVM_GetClassModifiers}, {"getDeclaredFields0","(Z)[" FLD, (void *)&JVM_GetClassDeclaredFields}, diff --git a/src/java.base/share/native/libjava/ClassLoader.c b/src/java.base/share/native/libjava/ClassLoader.c --- a/src/java.base/share/native/libjava/ClassLoader.c +++ b/src/java.base/share/native/libjava/ClassLoader.c @@ -207,6 +207,66 @@ return result; } +JNIEXPORT jclass JNICALL +Java_java_lang_ClassLoader_defineClass0(JNIEnv *env, + jclass cls, + jobject loader, + jclass lookup, + jstring name, + jbyteArray data, + jint offset, + jint length, + jobject pd, + jboolean initialize, + jint flags, + jobject classData) +{ + jbyte *body; + char *utfName; + jclass result = 0; + char buf[128]; + + if (data == NULL) { + JNU_ThrowNullPointerException(env, 0); + return 0; + } + + /* Work around 4153825. malloc crashes on Solaris when passed a + * negative size. + */ + if (length < 0) { + JNU_ThrowArrayIndexOutOfBoundsException(env, 0); + return 0; + } + + body = (jbyte *)malloc(length); + if (body == 0) { + JNU_ThrowOutOfMemoryError(env, 0); + return 0; + } + + (*env)->GetByteArrayRegion(env, data, offset, length, body); + + if ((*env)->ExceptionOccurred(env)) + goto free_body; + + if (name != NULL) { + utfName = getUTF(env, name, buf, sizeof(buf)); + if (utfName == NULL) { + goto free_body; + } + fixClassname(utfName); + } else { + utfName = NULL; + } + + return JVM_LookupDefineClass(env, lookup, utfName, body, length, pd, initialize, flags, classData); + + free_body: + free(body); + return result; +} + /* * Returns NULL if class not found. */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskPool.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskPool.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskPool.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskPool.java @@ -68,12 +68,14 @@ import com.sun.tools.javac.main.Arguments; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.model.JavacElements; +import com.sun.tools.javac.platform.PlatformDescription; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.LetExpr; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; /** * A pool of reusable JavacTasks. When a task is no valid anymore, it is returned to the pool, @@ -252,6 +254,7 @@ drop(JavacTask.class); drop(JavacTrees.class); drop(JavacElements.class); + drop(PlatformDescription.class); if (ht.get(Log.logKey) instanceof ReusableLog) { //log already inited - not first round @@ -266,6 +269,7 @@ Annotate.instance(this).newRound(); CompileStates.instance(this).clear(); MultiTaskListener.instance(this).clear(); + Options.instance(this).clear(); //find if any of the roots have redefined java.* classes Symtab syms = Symtab.instance(this); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -1231,10 +1231,14 @@ public static class RootPackageSymbol extends PackageSymbol { public final MissingInfoHandler missingInfoHandler; + public final boolean allowPrivateInvokeVirtual; - public RootPackageSymbol(Name name, Symbol owner, MissingInfoHandler missingInfoHandler) { + public RootPackageSymbol(Name name, Symbol owner, + MissingInfoHandler missingInfoHandler, + boolean allowPrivateInvokeVirtual) { super(name, owner); this.missingInfoHandler = missingInfoHandler; + this.allowPrivateInvokeVirtual = allowPrivateInvokeVirtual; } } @@ -2311,7 +2315,7 @@ } else { if (refSym.isStatic()) { return ClassFile.REF_invokeStatic; - } else if ((refSym.flags() & PRIVATE) != 0) { + } else if ((refSym.flags() & PRIVATE) != 0 && !allowPrivateInvokeVirtual()) { return ClassFile.REF_invokeSpecial; } else if (refSym.enclClass().isInterface()) { return ClassFile.REF_invokeInterface; @@ -2322,6 +2326,13 @@ } } + private boolean allowPrivateInvokeVirtual() { + Symbol rootPack = this; + while (rootPack != null && !(rootPack instanceof RootPackageSymbol)) { + rootPack = rootPack.owner; + } + return rootPack != null && ((RootPackageSymbol) rootPack).allowPrivateInvokeVirtual; + } @Override public int poolTag() { return ClassFile.CONSTANT_MethodHandle; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java @@ -54,6 +54,7 @@ import com.sun.tools.javac.code.Type.UnknownType; import com.sun.tools.javac.code.Types.UniqueType; import com.sun.tools.javac.comp.Modules; +import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Convert; @@ -389,7 +390,10 @@ MissingInfoHandler missingInfoHandler = MissingInfoHandler.instance(context); - rootPackage = new RootPackageSymbol(names.empty, null, missingInfoHandler); + Target target = Target.instance(context); + rootPackage = new RootPackageSymbol(names.empty, null, + missingInfoHandler, + target.runtimeUseNestAccess()); // create the basic builtin symbols unnamedModule = new ModuleSymbol(names.empty, null) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -36,7 +36,6 @@ import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeTranslator; import com.sun.tools.javac.code.Attribute; -import com.sun.tools.javac.code.Scope.WriteableScope; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol; @@ -127,6 +126,9 @@ /** deduplicate lambda implementation methods */ private final boolean deduplicateLambdas; + /** lambda proxy is a dynamic nestmate */ + private final boolean nestmateLambdas; + /** Flag for alternate metafactories indicating the lambda object is intended to be serializable */ public static final int FLAG_SERIALIZABLE = 1 << 0; @@ -168,6 +170,7 @@ || options.isSet(Option.G_CUSTOM, "vars"); verboseDeduplication = options.isSet("debug.dumpLambdaToMethodDeduplication"); deduplicateLambdas = options.getBoolean("deduplicateLambdas", true); + nestmateLambdas = Target.instance(context).runtimeUseNestAccess(); } // @@ -2254,10 +2257,13 @@ } /** - * The VM does not support access across nested classes (8010319). - * Were that ever to change, this should be removed. + * This method should be called only when target release <= 14 + * where LambdaMetaFactory does not spin nestmate classes. + * + * This method should be removed when --release 14 is not supported. */ boolean isPrivateInOtherClass() { + assert !nestmateLambdas; return (tree.sym.flags() & PRIVATE) != 0 && !types.isSameType( types.erasure(tree.sym.enclClass().asType()), @@ -2304,7 +2310,7 @@ isSuper || needsVarArgsConversion() || isArrayOp() || - isPrivateInOtherClass() || + (!nestmateLambdas && isPrivateInOtherClass()) || isProtectedInSuperClassOfEnclosingClassInOtherPackage() || !receiverAccessible() || (tree.getMode() == ReferenceMode.NEW && diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java @@ -169,6 +169,14 @@ return compareTo(JDK1_11) >= 0; } + /** language runtime uses nest-based access. + * e.g. lambda and string concat spin dynamic proxy class as a nestmate + * of the target class + */ + public boolean runtimeUseNestAccess() { + return compareTo(JDK1_15) >= 0; + } + /** Does the target VM support virtual private invocations? */ public boolean hasVirtualPrivateInvoke() { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java @@ -181,4 +181,9 @@ for (Runnable r: listeners) r.run(); } + + public void clear() { + values.clear(); + listeners = List.nil(); + } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/classfile/ClassLoaderData.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/classfile/ClassLoaderData.java --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/classfile/ClassLoaderData.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/classfile/ClassLoaderData.java @@ -44,12 +44,14 @@ classLoaderFieldOffset = type.getAddressField("_class_loader").getOffset(); nextField = type.getAddressField("_next"); klassesField = new MetadataField(type.getAddressField("_klasses"), 0); + hasClassMirrorHolderField = new CIntField(type.getCIntegerField("_has_class_mirror_holder"), 0); dictionaryField = type.getAddressField("_dictionary"); } private static long classLoaderFieldOffset; private static AddressField nextField; private static MetadataField klassesField; + private static CIntField hasClassMirrorHolderField; private static AddressField dictionaryField; public ClassLoaderData(Address addr) { @@ -74,6 +76,10 @@ return vmOopHandle.resolve(); } + public boolean gethasClassMirrorHolder() { + return hasClassMirrorHolderField.getValue(this) != 0; + } + public ClassLoaderData next() { return instantiateWrapperFor(nextField.getValue(getAddress())); } diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2020, 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 @@ -425,8 +425,16 @@ } public String className() { - return classSignature.substring(1, classSignature.length()-1) - .replace('/', '.'); + String name = classSignature.substring(1, classSignature.length() - 1); + int index = name.indexOf("."); // check if it's a hidden class + if (index < 0) { + name = name.replace('/', '.'); + } else { + // the class name of a hidden class is + "/" + + name = name.substring(0, index).replace('/', '.') + "/" + + name.substring(index + 1, name.length()); + } + return name; } public String classSignature() { diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/JNITypeParser.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/JNITypeParser.java --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/JNITypeParser.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/JNITypeParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2020, 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 @@ -43,38 +43,45 @@ this.signature = signature; } - static String typeNameToSignature(String signature) { + static String typeNameToSignature(String typeName) { StringBuilder sb = new StringBuilder(); - int firstIndex = signature.indexOf('['); + int firstIndex = typeName.indexOf('['); int index = firstIndex; while (index != -1) { sb.append('['); - index = signature.indexOf('[', index + 1); + index = typeName.indexOf('[', index + 1); } if (firstIndex != -1) { - signature = signature.substring(0, firstIndex); + typeName = typeName.substring(0, firstIndex); } - if (signature.equals("boolean")) { + if (typeName.equals("boolean")) { sb.append('Z'); - } else if (signature.equals("byte")) { + } else if (typeName.equals("byte")) { sb.append('B'); - } else if (signature.equals("char")) { + } else if (typeName.equals("char")) { sb.append('C'); - } else if (signature.equals("short")) { + } else if (typeName.equals("short")) { sb.append('S'); - } else if (signature.equals("int")) { + } else if (typeName.equals("int")) { sb.append('I'); - } else if (signature.equals("long")) { + } else if (typeName.equals("long")) { sb.append('J'); - } else if (signature.equals("float")) { + } else if (typeName.equals("float")) { sb.append('F'); - } else if (signature.equals("double")) { + } else if (typeName.equals("double")) { sb.append('D'); } else { sb.append('L'); - sb.append(signature.replace('.', '/')); + index = typeName.indexOf("/"); // check if it's a hidden class + if (index < 0) { + sb.append(typeName.replace('.', '/')); + } else { + sb.append(typeName.substring(0, index).replace('.', '/')); + sb.append("."); + sb.append(typeName.substring(index+1, typeName.length())); + } sb.append(';'); } @@ -203,7 +210,13 @@ currentIndex); String retVal = signature.substring(currentIndex, endClass); - retVal = retVal.replace('/','.'); + int index = retVal.indexOf("."); + if (index < 0) { + retVal = retVal.replace('/', '.'); + } else { + retVal = retVal.substring(0, index).replace('/', '.') + "/" + + retVal.substring(index + 1, retVal.length()); + } currentIndex = endClass + 1; return retVal; diff --git a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java @@ -25,7 +25,7 @@ package jdk.nashorn.internal.runtime; -import static jdk.internal.org.objectweb.asm.Opcodes.V1_7; +import static jdk.internal.org.objectweb.asm.Opcodes.*; import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION; import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; @@ -41,6 +41,7 @@ import java.io.PrintWriter; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup.ClassOption; import java.lang.invoke.MethodType; import java.lang.invoke.SwitchPoint; import java.lang.ref.ReferenceQueue; @@ -67,7 +68,6 @@ import java.security.ProtectionDomain; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; @@ -84,10 +84,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.script.ScriptEngine; + import jdk.dynalink.DynamicLinker; import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.FieldVisitor; +import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; import jdk.nashorn.api.scripting.ClassFilter; import jdk.nashorn.api.scripting.ScriptObjectMirror; @@ -108,7 +110,6 @@ import jdk.nashorn.internal.runtime.logging.Logger; import jdk.nashorn.internal.runtime.options.LoggingOption.LoggerInfo; import jdk.nashorn.internal.runtime.options.Options; -import jdk.internal.misc.Unsafe; /** * This class manages the global state of execution. Context is immutable. @@ -318,21 +319,60 @@ private final WeakValueCache> anonymousHostClasses = new WeakValueCache<>(); private static final class AnonymousContextCodeInstaller extends ContextCodeInstaller { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private static final String ANONYMOUS_HOST_CLASS_NAME = Compiler.SCRIPTS_PACKAGE.replace('/', '.') + ".AnonymousHost"; private static final byte[] ANONYMOUS_HOST_CLASS_BYTES = getAnonymousHostClassBytes(); - private final Class hostClass; + private final MethodHandles.Lookup hostLookup; private AnonymousContextCodeInstaller(final Context context, final CodeSource codeSource, final Class hostClass) { super(context, codeSource); - this.hostClass = hostClass; + this.hostLookup = (MethodHandles.Lookup)staticFieldValue(hostClass, "LOOKUP"); + } + + private static Object staticFieldValue(Class c, String name) { + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception { + Field f = c.getDeclaredField(name); + return f.get(null); + } + }); + } catch (PrivilegedActionException e) { + throw new InternalError(e.getCause()); + } } @Override public Class install(final String className, final byte[] bytecode) { - ANONYMOUS_INSTALLED_SCRIPT_COUNT.increment(); - return UNSAFE.defineAnonymousClass(hostClass, bytecode, null); + try { + ANONYMOUS_INSTALLED_SCRIPT_COUNT.increment(); + // Workaround: define it as a hidden nestmate so that the hostLookup can find private members + return hostLookup.defineHiddenClass(bytecode, true, ClassOption.NESTMATE).lookupClass(); + } catch (IllegalAccessException e) { + throw new InternalError(e); + } + } + + @Override + public void initialize(final Collection> classes, final Source source, final Object[] constants) { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + for (final Class clazz : classes) { + try { + //use reflection to write source and constants table to installed classes + MethodHandle sourceField = hostLookup.findStaticSetter(clazz, SOURCE.symbolName(), Source.class); + sourceField.invokeExact(source); + MethodHandle constantsField = hostLookup.findStaticSetter(clazz, CONSTANTS.symbolName(), Object[].class); + constantsField.invokeExact(constants); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + return null; + } + }); } @Override @@ -350,8 +390,27 @@ } private static byte[] getAnonymousHostClassBytes() { + // Workaround: define a host class in a non-exported package. + // This should be replaced when there is a mechanism to define + // a hidden class in a given package of a given module. final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - cw.visit(V1_7, Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT, ANONYMOUS_HOST_CLASS_NAME.replace('.', '/'), null, "java/lang/Object", null); + final String cn = ANONYMOUS_HOST_CLASS_NAME.replace('.', '/'); + cw.visit(V13, ACC_PUBLIC|ACC_INTERFACE |ACC_ABSTRACT, cn, null, "java/lang/Object", null); + { + FieldVisitor fv = cw.visitField(ACC_PUBLIC|ACC_STATIC|ACC_FINAL, "LOOKUP", + "Ljava/lang/invoke/MethodHandles$Lookup;", null, null); + fv.visitEnd(); + } + { + MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); + mv.visitCode(); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", + "()Ljava/lang/invoke/MethodHandles$Lookup;", false); + mv.visitFieldInsn(PUTSTATIC, cn, "LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;"); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } cw.visitEnd(); return cw.toByteArray(); } diff --git a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptLoader.java b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptLoader.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptLoader.java +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptLoader.java @@ -74,7 +74,8 @@ .requires("java.logging") .requires(NASHORN_MODULE.getName()) .requires(structMod.getName()) - .packages(Set.of(SCRIPTS_PKG)); + .packages(Set.of(SCRIPTS_PKG)) + .exports(SCRIPTS_PKG, Set.of(NASHORN_MODULE.getName())); if (Context.javaSqlFound) { builder.requires("java.sql"); diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -30,6 +30,7 @@ import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.util.Set; @@ -636,6 +637,12 @@ */ @ForceInline public long objectFieldOffset(Field f) { + if (f == null) { + throw new NullPointerException(); + } + if (f.getDeclaringClass().isHidden()) { + throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); + } return theInternalUnsafe.objectFieldOffset(f); } @@ -658,6 +665,12 @@ */ @ForceInline public long staticFieldOffset(Field f) { + if (f == null) { + throw new NullPointerException(); + } + if (f.getDeclaringClass().isHidden()) { + throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); + } return theInternalUnsafe.staticFieldOffset(f); } @@ -673,6 +686,12 @@ */ @ForceInline public Object staticFieldBase(Field f) { + if (f == null) { + throw new NullPointerException(); + } + if (f.getDeclaringClass().isHidden()) { + throw new UnsupportedOperationException("can't get base address on a hidden class: " + f); + } return theInternalUnsafe.staticFieldBase(f); } @@ -820,11 +839,16 @@ *
  • String: any object (not just a java.lang.String) *
  • InterfaceMethodRef: (NYI) a method handle to invoke on that call site's arguments * + * + * @deprecated Use the {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...)} + * method. + * * @param hostClass context for linkage, access control, protection domain, and class loader * @param data bytes of a class file * @param cpPatches where non-null entries exist, they replace corresponding CP entries in data */ @ForceInline + @Deprecated(since = "15", forRemoval = false) public Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches) { return theInternalUnsafe.defineAnonymousClass(hostClass, data, cpPatches); } diff --git a/test/hotspot/gtest/memory/test_metaspace_allocation.cpp b/test/hotspot/gtest/memory/test_metaspace_allocation.cpp --- a/test/hotspot/gtest/memory/test_metaspace_allocation.cpp +++ b/test/hotspot/gtest/memory/test_metaspace_allocation.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, SAP. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -102,9 +102,9 @@ _spaces[i].lock = new Mutex(Monitor::native, "gtest-MetaspaceAllocationTest-lock", false, Monitor::_safepoint_check_never); ASSERT_TRUE(_spaces[i].lock != NULL); } - // Let every ~10th space be an unsafe anonymous one to test different allocation patterns. + // Let every ~10th space be a short-lived one to test different allocation patterns. const Metaspace::MetaspaceType msType = (os::random() % 100 < 10) ? - Metaspace::UnsafeAnonymousMetaspaceType : Metaspace::StandardMetaspaceType; + Metaspace::ClassMirrorHolderMetaspaceType : Metaspace::StandardMetaspaceType; { // Pull lock during space creation, since this is what happens in the VM too // (see ClassLoaderData::metaspace_non_null(), which we mimick here). diff --git a/test/hotspot/jtreg/ProblemList-graal.txt b/test/hotspot/jtreg/ProblemList-graal.txt --- a/test/hotspot/jtreg/ProblemList-graal.txt +++ b/test/hotspot/jtreg/ProblemList-graal.txt @@ -241,3 +241,6 @@ org.graalvm.compiler.core.test.deopt.CompiledMethodTest 8202955 org.graalvm.compiler.hotspot.test.ReservedStackAccessTest 8213567 windows-all + +org.graalvm.compiler.hotspot.test.CheckGraalIntrinsics 8219607 generic-all +org.graalvm.compiler.hotspot.test.LambdaStableNameTest 8219607 generic-all diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestMetaAccessProvider.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestMetaAccessProvider.java --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestMetaAccessProvider.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestMetaAccessProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2020, 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 @@ -68,7 +68,22 @@ metaAccess.encodeDeoptActionAndReason(DEOPT_ACTION, DEOPT_REASON, DEBUG_IDS[3]).asInt() }; - private static boolean isUnsafeAnoymous(ResolvedJavaType type) { + private static boolean isHiddenClass(Class cls) { + if (cls.isHidden()) { + return true; + } + + // Check array of hidden type. + while (cls.getComponentType() != null) { + cls = cls.getComponentType(); + } + if (cls.isHidden()) { + return true; + } + return false; + } + + private static boolean isUnsafeAnonymous(ResolvedJavaType type) { return type.getHostClass() != null; } @@ -77,7 +92,7 @@ for (Class c : classes) { ResolvedJavaType type = metaAccess.lookupJavaType(c); assertNotNull(c.toString(), type); - if (!isUnsafeAnoymous(type)) { + if (!isHiddenClass(c) && !isUnsafeAnonymous(type)) { assertEquals(c.toString(), type.getName(), toInternalName(c.getName())); assertEquals(c.toString(), type.getName(), toInternalName(type.toJavaName())); assertEquals(c.toString(), c.getName(), type.toClassName()); @@ -98,11 +113,11 @@ ResolvedJavaType[] result = metaAccess.lookupJavaTypes(classes.toArray(new Class[classes.size()])); int counter = 0; for (Class aClass : classes) { - if (!isUnsafeAnoymous(result[counter])) { + if (!isHiddenClass(aClass) && !isUnsafeAnonymous(result[counter])) { assertEquals("Unexpected javaType: " + result[counter] + " while expecting of class: " + aClass, result[counter].toClassName(), aClass.getName()); } counter++; - } + } } @Test(expected = NullPointerException.class) diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2020, 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 @@ -25,7 +25,8 @@ * @test * @requires vm.jvmci * @library ../../../../../ - * @modules java.base/jdk.internal.reflect + * @modules java.base/jdk.internal.org.objectweb.asm + * java.base/jdk.internal.reflect * jdk.internal.vm.ci/jdk.vm.ci.meta * jdk.internal.vm.ci/jdk.vm.ci.runtime * jdk.internal.vm.ci/jdk.vm.ci.common @@ -65,6 +66,7 @@ import org.junit.Test; +import jdk.internal.org.objectweb.asm.*; import jdk.internal.reflect.ConstantPool; import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.meta.Assumptions.AssumptionResult; @@ -156,15 +158,26 @@ } } + private static Class anonClass() throws Exception { + ClassWriter cw = new ClassWriter(0); + cw.visit(Opcodes.V1_8, Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, "Anon", null, "java/lang/Object", null); + FieldVisitor intField = cw.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "intField", "I", null, 0); + intField.visitEnd(); + cw.visitEnd(); + return unsafe.defineAnonymousClass(TypeUniverse.class, cw.toByteArray(), null); + } + @Test - public void getHostClassTest() { + public void getHostClassTest() throws Exception { + ResolvedJavaType type = metaAccess.lookupJavaType(anonClass()); + ResolvedJavaType host = type.getHostClass(); + assertNotNull(host); for (Class c : classes) { - ResolvedJavaType type = metaAccess.lookupJavaType(c); - ResolvedJavaType host = type.getHostClass(); - if (!type.equals(predicateType)) { - assertNull(host); - } else { - assertNotNull(host); + type = metaAccess.lookupJavaType(c); + host = type.getHostClass(); + assertNull(host); + if (type.equals(predicateType)) { + assertTrue(c.isHidden()); } } @@ -176,9 +189,10 @@ Supplier lambda = () -> () -> System.out.println("run"); ResolvedJavaType lambdaType = metaAccess.lookupJavaType(lambda.getClass()); ResolvedJavaType nestedLambdaType = metaAccess.lookupJavaType(lambda.get().getClass()); - assertNotNull(lambdaType.getHostClass()); - assertNotNull(nestedLambdaType.getHostClass()); - assertEquals(lambdaType.getHostClass(), nestedLambdaType.getHostClass()); + assertNull(lambdaType.getHostClass()); + assertTrue(lambda.getClass().isHidden()); + assertNull(nestedLambdaType.getHostClass()); + assertTrue(lambda.get().getClass().isHidden()); } @Test @@ -766,8 +780,8 @@ if (f.getDeclaringClass().equals(metaAccess.lookupJavaType(ConstantPool.class)) && f.getName().equals("constantPoolOop")) { return true; } - if (f.getDeclaringClass().equals(metaAccess.lookupJavaType(Class.class)) && f.getName().equals("classLoader")) { - return true; + if (f.getDeclaringClass().equals(metaAccess.lookupJavaType(Class.class))) { + return f.getName().equals("classLoader") || f.getName().equals("classData"); } if (f.getDeclaringClass().equals(metaAccess.lookupJavaType(Lookup.class))) { return f.getName().equals("allowedModes") || f.getName().equals("lookupClass"); diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/CastToParentTest.java b/test/hotspot/jtreg/runtime/HiddenClasses/CastToParentTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/CastToParentTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, 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 + * @summary Test that a hidden class can be cast to its parent. + * @library /test/lib + * @modules jdk.compiler + * @run main CastToParentTest + */ + +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +// This test is based on vmTestbase/vm/mlvm/anonloader/func/castToGrandparent/Test.java +public class CastToParentTest { + + static byte klassbuf[] = InMemoryJavaCompiler.compile("TestClass", + "public class TestClass extends CastToParentTest { " + + " public static void concat(String one, String two) throws Throwable { " + + " System.out.println(one + two);" + + " } } "); + + public static void main(String[] args) throws Throwable { + Lookup lookup = MethodHandles.lookup(); + Class c = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + Object hiddenClassObj = c.newInstance(); + + // Cast hidden class to its parent. + CastToParentTest parentObj = (CastToParentTest)hiddenClassObj; + + if (!parentObj.equals(hiddenClassObj)) { + throw new RuntimeException("Hidden class object cannot be cast to parent"); + } + + // Try to cast using a different mechanism. + new CastToParentTest().getClass().cast(hiddenClassObj); + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/DefineHiddenClass.java b/test/hotspot/jtreg/runtime/HiddenClasses/DefineHiddenClass.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/DefineHiddenClass.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @build jdk.test.lib.JDKToolLauncher + * jdk.test.lib.process.ProcessTools + * jdk.test.lib.Utils + * @run main/othervm -Xverify:remote DefineHiddenClass + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import jdk.internal.misc.Unsafe; + +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.Utils; + +/* package-private */ interface Test { + void test(); +} + + +public class DefineHiddenClass { + + static final Class klass = DefineHiddenClass.class; + static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "hidden"); + static final Path CLASSES_DIR = Paths.get(Utils.TEST_CLASSES, "hidden"); + + static void compileSources(String sourceFile) throws Throwable { + JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("javac"); + launcher.addToolArg("-cp") + .addToolArg(Utils.TEST_CLASSES.toString()) + .addToolArg("-d") + .addToolArg(CLASSES_DIR.toString()) + .addToolArg(Paths.get(SRC_DIR.toString(), sourceFile).toString()); + + int exitCode = ProcessTools.executeCommand(launcher.getCommand()) + .getExitValue(); + if (exitCode != 0) { + throw new RuntimeException("Compilation of the test failed. " + + "Unexpected exit code: " + exitCode); + } + } + + static byte[] readClassFile(String classFileName) throws Exception { + File classFile = new File(CLASSES_DIR + File.separator + classFileName); + try (FileInputStream in = new FileInputStream(classFile); + ByteArrayOutputStream out = new ByteArrayOutputStream()) + { + int b; + while ((b = in.read()) != -1) { + out.write(b); + } + return out.toByteArray(); + } + } + + public static void main(String[] args) throws Throwable { + compileSources("NameInString.java"); + Lookup lookup = MethodHandles.lookup(); + byte[] bytes = readClassFile("NameInString.class"); + Class c = lookup.defineHiddenClass(bytes, true, NESTMATE).lookupClass(); + Test t = (Test) c.newInstance(); + t.test(); + } + +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/FieldInSuper.java b/test/hotspot/jtreg/runtime/HiddenClasses/FieldInSuper.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/FieldInSuper.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020, 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 8238550 + * @summary Test that verifer assignability checks involving the 'this' pointer + * work ok for hidden classes. + * @compile FieldInSuperSub.jasm + * @run main/othervm -Xverify:remote FieldInSuper + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FieldInSuper { + + static final Path CLASSES_DIR = Paths.get(System.getProperty("test.classes")); + String hello = "hello"; + + static byte[] readClassFile(String classFileName) throws Exception { + File classFile = new File(CLASSES_DIR + File.separator + classFileName); + try (FileInputStream in = new FileInputStream(classFile); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + int b; + while ((b = in.read()) != -1) { + out.write(b); + } + return out.toByteArray(); + } + } + + public static void main(String[] args) throws Throwable { + Lookup lookup = MethodHandles.lookup(); + byte[] bytes = readClassFile("FieldInSuperSub.class"); + + // Define a hidden class that loads the 'this' pointer and then + // references field 'hello' using: + // getfield Field FieldInSuper.hello:"Ljava/lang/String;"; + // This will cause the verifier to check that the 'this' pointer for + // the hidden class is assignable to FieldInSuper. + Class c = lookup.defineHiddenClass(bytes, true, NESTMATE).lookupClass(); + Object fss = c.newInstance(); + c.getMethod("printMe").invoke(fss); + } + +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/FieldInSuperSub.jasm b/test/hotspot/jtreg/runtime/HiddenClasses/FieldInSuperSub.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/FieldInSuperSub.jasm @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, 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. + */ + +super public class FieldInSuperSub extends FieldInSuper version 59:0 { + + public Method "":"()V" stack 1 locals 1 { + aload_0; + invokespecial Method FieldInSuper."":"()V"; + return; + } + + // Change getfield to reference field 'hello' in super class. + public Method printMe:"()V" stack 2 locals 1 { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + aload_0; + getfield Field FieldInSuper.hello:"Ljava/lang/String;"; + invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; + return; + } + +} // end Class FieldInSuperSub diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/GCHiddenClass.java b/test/hotspot/jtreg/runtime/HiddenClasses/GCHiddenClass.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/GCHiddenClass.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, 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 + * @summary Test that hidden classes get garbage collected. + * @library /test/lib + * @modules jdk.compiler + * @run main GCHiddenClass + */ + + +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; + +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +// This test is based on test vmTestbase/vm/mlvm/anonloader/func/isGarbageCollected/Test.java +public class GCHiddenClass { + + static byte klassbuf[] = InMemoryJavaCompiler.compile("TestClass", + "public class TestClass { " + + " public TestClass() { " + + " System.out.println(\"Hello\"); " + + " } } "); + + // A private method is great to keep hidden Class reference local to make it + // GCed on the next cycle + private PhantomReference> createClass(ReferenceQueue> refQueue) throws Exception { + Lookup lookup = MethodHandles.lookup(); + Class cl = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + return new PhantomReference>(cl, refQueue); + } + + public boolean run() throws Exception { + ReferenceQueue> refQueue = new ReferenceQueue>(); + PhantomReference> hiddenClassRef = createClass(refQueue); + System.gc(); + Reference> deletedObject = refQueue.remove(); + return hiddenClassRef.equals(deletedObject); + } + + public static void main(String[] args) throws Throwable { + GCHiddenClass gcHC = new GCHiddenClass(); + if (!gcHC.run()) { + throw new RuntimeException("Test failed"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/HiddenClassStack.java b/test/hotspot/jtreg/runtime/HiddenClasses/HiddenClassStack.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/HiddenClassStack.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020, 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 + * @summary Test that stack tracing isn't broken if an exception is thrown + * in a hidden class. + * @library /test/lib + * @modules jdk.compiler + * @run main HiddenClassStack + */ + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +// This test is based on vmTestbase/vm/mlvm/anonloader/func/classNameInStackTrace/Test.java +public class HiddenClassStack { + + static byte klassbuf[] = InMemoryJavaCompiler.compile("TestClass", + "public class TestClass { " + + " public TestClass() { " + + " throw new RuntimeException(\"boom\"); " + + " } } "); + + public static void main(String[] args) throws Throwable { + + // An exception is thrown by class loaded by lookup.defineHiddenClass(). + // Verify that the exception's stack trace contains name of the current + // test class. + try { + Lookup lookup = MethodHandles.lookup(); + Class cl = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + Object obj = cl.newInstance(); + throw new Exception("Expected RuntimeException not thrown"); + } catch (RuntimeException e) { + if (!e.getMessage().contains("boom")) { + throw new RuntimeException("Wrong RuntimeException, e: " + e.toString()); + } + ByteArrayOutputStream byteOS = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(byteOS); + e.printStackTrace(printStream); + printStream.close(); + String stackTrace = byteOS.toString("ASCII"); + if (!stackTrace.contains(HiddenClassStack.class.getName())) { + throw new RuntimeException("HiddenClassStack missing from stacktrace: " + + stackTrace); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/HiddenDefMeths.java b/test/hotspot/jtreg/runtime/HiddenClasses/HiddenDefMeths.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/HiddenDefMeths.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2020, 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 + * @summary Tests a hidden class that implements interfaces with default methods. + * @library /testlibrary + * @modules java.base/jdk.internal.org.objectweb.asm + * java.management + * @run main HiddenDefMeths + */ + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Type; + +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; +import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; +import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; +import static jdk.internal.org.objectweb.asm.Opcodes.DUP; +import static jdk.internal.org.objectweb.asm.Opcodes.GETFIELD; +import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD; +import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; +import static jdk.internal.org.objectweb.asm.Opcodes.V1_8; + +public class HiddenDefMeths { + + interface Resource { + Pointer ptr(); + } + + interface Struct extends Resource { + StructPointer ptr(); + } + + interface Pointer { } + + interface StructPointer extends Pointer { } + + interface I extends Struct { + void m(); + } + + static String IMPL_PREFIX = "$$impl"; + static String PTR_FIELD_NAME = "ptr"; + + // Generate a class similar to: + // + // public class HiddenDefMeths$I$$impl implements HiddenDefMeths$I, HiddenDefMeths$Struct { + // + // public HiddenDefMeths$StructPointer ptr; + // + // public HiddenDefMeths$I$$impl(HiddenDefMeths$StructPointer p) { + // ptr = p; + // } + // + // public HiddenDefMeths$StructPointer ptr() { + // return ptr; + // } + // } + // + byte[] generate(Class iface) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + String ifaceTypeName = Type.getInternalName(iface); + String proxyClassName = ifaceTypeName + IMPL_PREFIX; + // class definition + cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, proxyClassName, + desc(Object.class) + desc(ifaceTypeName) + desc(Struct.class), + name(Object.class), + new String[] { ifaceTypeName, name(Struct.class) }); + + cw.visitField(ACC_PUBLIC, PTR_FIELD_NAME, desc(StructPointer.class), desc(StructPointer.class), null); + cw.visitEnd(); + + // constructor + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", + meth(desc(void.class), desc(StructPointer.class)), + meth(desc(void.class), desc(StructPointer.class)), null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, name(Object.class), "", meth(desc(void.class)), false); + mv.visitVarInsn(ALOAD, 1); + // Execution of this PUTFIELD instruction causes the bug's ClassNotFoundException. + mv.visitFieldInsn(PUTFIELD, proxyClassName, PTR_FIELD_NAME, desc(StructPointer.class)); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // ptr() impl + mv = cw.visitMethod(ACC_PUBLIC, PTR_FIELD_NAME, meth(desc(StructPointer.class)), + meth(desc(StructPointer.class)), null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, proxyClassName, PTR_FIELD_NAME, desc(StructPointer.class)); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + return cw.toByteArray(); + } + + String name(Class clazz) { + if (clazz.isPrimitive()) { + throw new IllegalStateException(); + } else if (clazz.isArray()) { + return desc(clazz); + } else { + return clazz.getName().replaceAll("\\.", "/"); + } + } + + String desc(Class clazz) { + String mdesc = MethodType.methodType(clazz).toMethodDescriptorString(); + return mdesc.substring(mdesc.indexOf(')') + 1); + } + + String desc(String clazzName) { + return "L" + clazzName + ";"; + } + + String gen(String clazz, String... typeargs) { + return clazz.substring(0, clazz.length() - 1) + Stream.of(typeargs).collect(Collectors.joining("", "<", ">")) + ";"; + } + + String meth(String restype, String... argtypes) { + return Stream.of(argtypes).collect(Collectors.joining("", "(", ")")) + restype; + } + + String meth(Method m) { + return MethodType.methodType(m.getReturnType(), m.getParameterTypes()).toMethodDescriptorString(); + } + + public static void main(String[] args) throws Throwable { + byte[] bytes = new HiddenDefMeths().generate(I.class); + Lookup lookup = MethodHandles.lookup(); + Class cl = lookup.defineHiddenClass(bytes, false, NESTMATE).lookupClass(); + I i = (I)cl.getConstructors()[0].newInstance(new Object[] { null }); + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/HiddenGetModule.java b/test/hotspot/jtreg/runtime/HiddenClasses/HiddenGetModule.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/HiddenGetModule.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020, 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 + * @summary Test that a hidden class has the same module as its lookup class. + * @library /test/lib + * @modules jdk.compiler + * @compile pkg/HasNamedModule.java + * @run main/othervm HiddenGetModule + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import java.io.IOException; +import java.lang.ModuleLayer; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.reflect.Method; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +public class HiddenGetModule { + + static byte unnamedKlassbuf[] = InMemoryJavaCompiler.compile("TestClass", + "public class TestClass { " + + " public static void concat(String one, String two) throws Throwable { " + + " System.out.println(one + two);" + + " } } "); + + public static ModuleFinder finderOf(ModuleDescriptor... descriptors) { + + // Create a ModuleReference for each module + Map namesToReference = new HashMap<>(); + + for (ModuleDescriptor descriptor : descriptors) { + String name = descriptor.name(); + + URI uri = URI.create("module:/" + name); + + ModuleReference mref = new ModuleReference(descriptor, uri) { + @Override + public ModuleReader open() { + throw new UnsupportedOperationException(); + } + }; + + namesToReference.put(name, mref); + } + + return new ModuleFinder() { + @Override + public Optional find(String name) { + Objects.requireNonNull(name); + return Optional.ofNullable(namesToReference.get(name)); + } + @Override + public Set findAll() { + return new HashSet<>(namesToReference.values()); + } + }; + } + + public static void main(String[] args) throws Throwable { + + // Test unnamed module. + Lookup lookup = MethodHandles.lookup(); + Class cl = lookup.defineHiddenClass(unnamedKlassbuf, false, NESTMATE).lookupClass(); + if (cl.getModule() != HiddenGetModule.class.getModule()) { + throw new RuntimeException("hidden class and lookup class have different unnamed modules"); + } + + // Test named module. + MyClassLoader myClassLoader = new MyClassLoader(); + + // Define a module named HiddenModule containing package pkg. + ModuleDescriptor descriptor = ModuleDescriptor.newModule("HiddenModule") + .requires("java.base") + .exports("pkg") + .build(); + + // Set up a ModuleFinder containing the module for this layer. + ModuleFinder finder = finderOf(descriptor); + + // Resolves "HiddenModule" + Configuration cf = ModuleLayer.boot() + .configuration() + .resolve(finder, ModuleFinder.of(), Set.of("HiddenModule")); + + // map module to class loader + Map map = new HashMap<>(); + map.put("HiddenModule", myClassLoader); + + // Create layer that contains HiddenModule + ModuleLayer layer = ModuleLayer.boot().defineModules(cf, map::get); + + byte klassbuf[] = InMemoryJavaCompiler.compile("pkg.TestClass", + "package pkg; " + + "public class TestClass { " + + " public static void concat(String one, String two) throws Throwable { " + + " System.out.println(one + two);" + + " } } "); + + // Load the class and call the method that defines a hidden class and compares modules. + Classc = Class.forName("pkg.HasNamedModule", true, myClassLoader); + if (c.getClassLoader() != myClassLoader) { + throw new RuntimeException("pkg.HasNamedModule defined by wrong classloader: " + c.getClassLoader()); + } + Method m = c.getDeclaredMethod("compareModules", byte[].class); + m.invoke(null, klassbuf); + } + + + public static class MyClassLoader extends ClassLoader { + + public static final String CLASS_NAME = "HasNamedModule"; + + static ByteBuffer readClassFile(String name) { + File f = new File(System.getProperty("test.classes", "."), name); + try (FileInputStream fin = new FileInputStream(f); + FileChannel fc = fin.getChannel()) { + return fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + } catch (IOException e) { + throw new RuntimeException("Can't open file: " + name + ", " + e.toString()); + } + } + + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class c; + if (!name.contains(CLASS_NAME)) { + c = super.loadClass(name, resolve); + } else { + // should not delegate to the system class loader + c = findClass(name); + if (resolve) { + resolveClass(c); + } + } + return c; + } + + protected Class findClass(String name) throws ClassNotFoundException { + if (!name.contains(CLASS_NAME)) { + throw new ClassNotFoundException("Unexpected class: " + name); + } + return defineClass(name, readClassFile(name.replace(".", File.separator) + ".class"), null); + } + } /* MyClassLoader */ + +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/InstantiateHiddenClass.java b/test/hotspot/jtreg/runtime/HiddenClasses/InstantiateHiddenClass.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/InstantiateHiddenClass.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, 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 + * @summary Test issues with instantiating hidden classes. + * @library /test/lib + * @modules jdk.compiler + * @run main InstantiateHiddenClass + */ + +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +public class InstantiateHiddenClass { + + static byte klassbuf[] = InMemoryJavaCompiler.compile("TestClass", + "public class TestClass { " + + " public static void concat(String one, String two) throws Throwable { " + + " System.out.println(one + two);" + + " } } "); + + public static void main(String[] args) throws Throwable { + + // Test that a hidden class cannot be found through its name. + try { + Lookup lookup = MethodHandles.lookup(); + Class cl = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + Class.forName(cl.getName()).newInstance(); + throw new RuntimeException("Expected ClassNotFoundException not thrown"); + } catch (ClassNotFoundException e ) { + // Test passed + } + + + // Create two hidden classes and instantiate an object from each of them. + // Verify that the references to these objects are different and references + // to their classes are not equal either. + Lookup lookup = MethodHandles.lookup(); + Class c1 = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + Class c2 = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + Object o1 = c1.newInstance(); + Object o2 = c2.newInstance(); + if (o1 == o2) { + throw new RuntimeException("Objects should not be equal"); + } + if (o1.getClass() == o2.getClass()) { + throw new RuntimeException("Classes should not be equal"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/NestedHidden.java b/test/hotspot/jtreg/runtime/HiddenClasses/NestedHidden.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/NestedHidden.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, 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 + * @summary Creates a hidden class inside of a hidden class. + * @library /test/lib + * @modules jdk.compiler + * @run main p.NestedHidden + */ + +package p; + +import java.lang.*; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + + +// Test that a hidden class can define its own hidden class by calling +// lookup.defineHiddenClass(). +public class NestedHidden { + static byte klassbuf[] = InMemoryJavaCompiler.compile("p.TestClass", + "package p; " + + "public class TestClass { " + + " public static void concat(String one, String two) throws Throwable { " + + " System.out.println(one + two);" + + " } } "); + + public static void main(String args[]) throws Exception { + // The hidden class calls lookup.defineHiddenClass(), creating a nested hidden class. + byte klassbuf2[] = InMemoryJavaCompiler.compile("p.TestClass2", + "package p; " + + "import java.lang.invoke.MethodHandles; " + + "import java.lang.invoke.MethodHandles.Lookup; " + + "import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; " + + "public class TestClass2 { " + + " public static void doit() throws Throwable { " + + " Lookup lookup = MethodHandles.lookup(); " + + " Class klass2 = lookup.defineHiddenClass(p.NestedHidden.klassbuf, true, NESTMATE).lookupClass(); " + + " Class[] dArgs = new Class[2]; " + + " dArgs[0] = String.class; " + + " dArgs[1] = String.class; " + + " try { " + + " klass2.getMethod(\"concat\", dArgs).invoke(null, \"CC\", \"DD\"); " + + " } catch (Throwable ex) { " + + " throw new RuntimeException(\"Exception: \" + ex.toString()); " + + " } " + + "} } "); + + Lookup lookup = MethodHandles.lookup(); + Class klass2 = lookup.defineHiddenClass(klassbuf2, true, NESTMATE).lookupClass(); + klass2.getMethod("doit").invoke(null); + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/StressHiddenClasses.java b/test/hotspot/jtreg/runtime/HiddenClasses/StressHiddenClasses.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/StressHiddenClasses.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, 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 + * @summary Stress defining hidden classes. + * @requires !vm.graal.enabled + * @library /test/lib + * @modules jdk.compiler + * @run main/othervm StressHiddenClasses + */ + +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +// This test is based on vmTestbase/vm/mlvm/anonloader/share/StressClassLoadingTest.java +public class StressHiddenClasses { + + private static final int PARSE_TIMEOUT = 0; + private static final int ITERATIONS = 40000; + + static byte klassbuf[] = InMemoryJavaCompiler.compile("TestClass", + "public class TestClass { " + + " public static void concat(String one, String two) throws Throwable { " + + " System.out.println(one + two);" + + " } } "); + + + public void run() throws Exception { + for (int x = 0; x < ITERATIONS; x++) { + Thread parserThread = new Thread() { + public void run() { + try { + Lookup lookup = MethodHandles.lookup(); + Class c = lookup.defineHiddenClass(klassbuf, true, NESTMATE).lookupClass(); + } catch (Throwable e) { + throw new RuntimeException("Unexpected exception: " + e.toString()); + } + } + }; + + parserThread.start(); + parserThread.join(PARSE_TIMEOUT); + + if (parserThread.isAlive()) { + System.out.println("parser thread may be hung!"); + StackTraceElement[] stack = parserThread.getStackTrace(); + System.out.println("parser thread stack len: " + stack.length); + System.out.println(parserThread + " stack trace:"); + for (int i = 0; i < stack.length; ++i) { + System.out.println(parserThread + "\tat " + stack[i]); + } + + parserThread.join(); // Wait until either thread finishes or test times out. + } + } + } + + + public static void main(String[] args) throws Throwable { + StressHiddenClasses shc = new StressHiddenClasses(); + shc.run(); + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/TestHiddenClassUnloading.java b/test/hotspot/jtreg/runtime/HiddenClasses/TestHiddenClassUnloading.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/TestHiddenClassUnloading.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2020, 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 + * @summary Test unloading of hidden classes. + * @modules java.management + * @library /test/lib / + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * sun.hotspot.WhiteBox$WhiteBoxPermission + * + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-BackgroundCompilation + * compiler.classUnloading.hiddenClass.TestHiddenClassUnloading + */ + +package compiler.classUnloading.hiddenClass; + +import sun.hotspot.WhiteBox; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLConnection; +import compiler.whitebox.CompilerWhiteBoxTest; + +// This is based on test compiler/classUnloading/anonymousClass/TestAnonymousClassUnloading.java +public class TestHiddenClassUnloading { + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + /** + * We override hashCode here to be able to access this implementation + * via an Object reference (we cannot cast to TestHiddenClassUnloading). + */ + @Override + public int hashCode() { + return 42; + } + + /** + * Does some work by using the hiddenClass. + * @param hiddenClass Class performing some work (will be unloaded) + */ + static private void doWork(Class hiddenClass) throws InstantiationException, IllegalAccessException { + // Create a new instance + Object anon = hiddenClass.newInstance(); + // We would like to call a method of hiddenClass here but we cannot cast because the class + // was loaded by a different class loader. One solution would be to use reflection but since + // we want C2 to implement the call as an IC we call Object::hashCode() here which actually + // calls hiddenClass::hashCode(). C2 will then implement this call as an IC. + if (anon.hashCode() != 42) { + new RuntimeException("Work not done"); + } + } + + /** + * Makes sure that method is compiled by forcing compilation if not yet compiled. + * @param m Method to be checked + */ + static private void makeSureIsCompiled(Method m) { + // Make sure background compilation is disabled + if (WHITE_BOX.getBooleanVMFlag("BackgroundCompilation")) { + throw new RuntimeException("Background compilation enabled"); + } + + // Check if already compiled + if (!WHITE_BOX.isMethodCompiled(m)) { + // If not, try to compile it with C2 + if(!WHITE_BOX.enqueueMethodForCompilation(m, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION)) { + // C2 compiler not available, try to compile with C1 + WHITE_BOX.enqueueMethodForCompilation(m, CompilerWhiteBoxTest.COMP_LEVEL_SIMPLE); + } + // Because background compilation is disabled, method should now be compiled + if(!WHITE_BOX.isMethodCompiled(m)) { + throw new RuntimeException(m + " not compiled"); + } + } + } + + /** + * This test creates stale Klass* metadata referenced by a compiled IC. + * + * The following steps are performed: + * (1) A hidden version of TestHiddenClassUnloading is loaded by a custom class loader + * (2) The method doWork that calls a method of the hidden class is compiled. The call + * is implemented as an IC referencing Klass* metadata of the hidden class. + * (3) Unloading of the hidden class is enforced. The IC now references dead metadata. + */ + static public void main(String[] args) throws Exception { + // (1) Load a hidden version of this class using method lookup.defineHiddenClass(). + String rn = TestHiddenClassUnloading.class.getSimpleName() + ".class"; + URL classUrl = TestHiddenClassUnloading.class.getResource(rn); + URLConnection connection = classUrl.openConnection(); + + int length = connection.getContentLength(); + byte[] classBytes = connection.getInputStream().readAllBytes(); + if (length != -1 && classBytes.length != length) { + throw new IOException("Expected:" + length + ", actual: " + classBytes.length); + } + + Lookup lookup = MethodHandles.lookup(); + Class hiddenClass = lookup.defineHiddenClass(classBytes, true, NESTMATE).lookupClass(); + + // (2) Make sure all paths of doWork are profiled and compiled + for (int i = 0; i < 100000; ++i) { + doWork(hiddenClass); + } + + // Make sure doWork is compiled now + Method doWork = TestHiddenClassUnloading.class.getDeclaredMethod("doWork", Class.class); + makeSureIsCompiled(doWork); + + // (3) Throw away reference to hiddenClass to allow unloading + hiddenClass = null; + + // Force garbage collection to trigger unloading of hiddenClass + WHITE_BOX.fullGC(); + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/hidden/NameInString.java b/test/hotspot/jtreg/runtime/HiddenClasses/hidden/NameInString.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/hidden/NameInString.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * Test that a string that is the same as its hidden class name does not + * get clobbered when the JVM changes the hidden class's name. + */ +public class NameInString implements Test { + + private String realTest() { + return "NameInString"; + } + + public void test() { + String result = realTest(); + // Make sure that the Utf8 constant pool entry for "NameInString" is okay. + if (!result.substring(0, 6).equals("NameIn") || + !result.substring(6).equals("String")) { + throw new RuntimeException("'NameInString is bad: " + result); + } + + } +} diff --git a/test/hotspot/jtreg/runtime/HiddenClasses/pkg/HasNamedModule.java b/test/hotspot/jtreg/runtime/HiddenClasses/pkg/HasNamedModule.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/HiddenClasses/pkg/HasNamedModule.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, 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 pkg; + +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; + +// This class is used by test HiddenGetModule.java. +public class HasNamedModule { + + public static void compareModules(byte[] klassbuf) throws Throwable { + String moduleName = HasNamedModule.class.getModule().toString(); + System.out.println("HasNamedModule module: " + moduleName); + Lookup lookup = MethodHandles.lookup(); + Class cl = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + if (cl.getModule() != HasNamedModule.class.getModule()) { + System.out.println("HasNamedModule: " + moduleName + + ", hidden class module: " + cl.getModule()); + throw new RuntimeException("hidden class and lookup class have different modules"); + } + if (!moduleName.contains("HiddenModule")) { + throw new RuntimeException("wrong module name: " + moduleName); + } + } +} diff --git a/test/hotspot/jtreg/runtime/Nestmates/membership/Helper.java b/test/hotspot/jtreg/runtime/Nestmates/membership/Helper.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/Nestmates/membership/Helper.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020, 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. + */ + +// Helper for TestNestHostErrorWithClassUnload. +// This class is loaded into the new classloader to do the test and we invoke +// its test() method reflectively. It has to be public to be accessible from +// the un-named package of another loader. +public class Helper { + public static void test() { + try { + P2.PackagedNestHost2.Member.m(); + throw new RuntimeException("Call to P2.PackagedNestHost2.Member.m() succeeded unexpectedly!"); + } + catch (IllegalAccessError iae) { + System.out.println("Got expected exception:" + iae); + } + } +} diff --git a/test/hotspot/jtreg/runtime/Nestmates/membership/OtherPackage.java b/test/hotspot/jtreg/runtime/Nestmates/membership/OtherPackage.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/Nestmates/membership/OtherPackage.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018, 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 test; + +public class OtherPackage { +} diff --git a/test/hotspot/jtreg/runtime/Nestmates/membership/TestDynamicNestmateMembership.java b/test/hotspot/jtreg/runtime/Nestmates/membership/TestDynamicNestmateMembership.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/Nestmates/membership/TestDynamicNestmateMembership.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2018, 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 + * @summary Test the rules for dynamic nest membership using the Lookup.defineHiddenClass API + * @compile OtherPackage.java + * @run main/othervm TestDynamicNestmateMembership + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.Path; +import java.lang.invoke.MethodHandles; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; + +/* package */ class DynamicNestmate { } + +/* package */ class DynamicNestmate2 { } + +/* package */ class StaticHost { + static class StaticMember { + } +} + +public class TestDynamicNestmateMembership { + + static class Member { + static MethodHandles.Lookup getLookup() { + return MethodHandles.lookup(); + } + } + + static final String CLASSES = System.getProperty("test.classes"); + static final Path CLASSES_DIR = Paths.get(CLASSES); + + static byte[] getBytesForClass(String name) throws IOException { + Path classFile; + if (name.indexOf('.') > 0) { + // it's in a package + String[] paths = name.split("\\."); + classFile = CLASSES_DIR.resolve(paths[0]); + classFile = classFile.resolve(paths[1] + ".class"); + } + else { + classFile = CLASSES_DIR.resolve(name + ".class"); + } + return Files.readAllBytes(classFile); + } + + static final Class cls = TestDynamicNestmateMembership.class; + static final MethodHandles.Lookup main_lookup = MethodHandles.lookup(); + + public static void main(String[] args) throws Throwable { + test_validInjection(); + test_hostIsMember(); + test_otherPackage(); + test_alreadyNestMember(); + test_alreadyNestHost(); + } + + // Inject a valid class into the nest of the current class + static void test_validInjection() { + String name = "DynamicNestmate"; + inject(name, null); + } + + // Try to inject a class into a "host" that is itself a member. + // This is redirected at the defineClass level to the member's + // host and so will succeed. + static void test_hostIsMember() { + String name = "DynamicNestmate2"; + inject(name, Member.getLookup(), null); + } + + // Try to inject a class that has a static NestHost attribute + // No error since the nest host has been set when it's created. + // Static nest membership is effectively ignored. + static void test_alreadyNestMember() { + String name = "StaticHost$StaticMember"; + inject(name, null); + } + + // Try to inject a class that has the NestMembers attribute. + // No error since the nest host has been set when it's created. + // Static nest membership is effectively ignored. + static void test_alreadyNestHost() { + String name = "StaticHost"; + inject(name, null); + } + + // Try to inject a class that is in another package + static void test_otherPackage() { + String name = "test.OtherPackage"; + inject(name, IllegalArgumentException.class); + } + + static void inject(String name, Class ex) { + inject(name, main_lookup, ex); + } + + static void inject(String name, MethodHandles.Lookup lookup, + Class ex) { + Class target = lookup.lookupClass(); + String action = "Injecting " + name + " into the nest of " + + target.getSimpleName(); + try { + byte[] bytes = getBytesForClass(name); + Class nestmate = lookup.defineHiddenClass(bytes, false, NESTMATE).lookupClass(); + if (ex != null) { + throw new RuntimeException(action + " was expected to throw " + + ex.getSimpleName()); + } + Class actualHost = nestmate.getNestHost(); + Class expectedHost = target.getNestHost(); + if (actualHost != expectedHost) { + throw new RuntimeException(action + " expected a nest-host of " + + expectedHost.getSimpleName() + + " but got " + actualHost.getSimpleName()); + } + System.out.print("Ok: " + action + " succeeded: "); + if (actualHost != target) { + System.out.print("(re-directed to target's nest-host) "); + } + System.out.println("Nesthost of " + nestmate.getName() + + " is " + actualHost.getName()); + } catch (Throwable t) { + if (t.getClass() == ex) { + System.out.println("Ok: " + action + " got expected exception: " + + t.getClass().getSimpleName() + ":" + + t.getMessage()); + } + else { + throw new RuntimeException(action + " got unexpected exception " + + t.getClass().getSimpleName(), t); + } + } + } + +} + diff --git a/test/hotspot/jtreg/runtime/Nestmates/membership/TestNestHostErrorWithClassUnload.java b/test/hotspot/jtreg/runtime/Nestmates/membership/TestNestHostErrorWithClassUnload.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/Nestmates/membership/TestNestHostErrorWithClassUnload.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, 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 + * @summary Test the ability to safely unload a class that has an error + * with its designated nest host. The nest host class must resolve + * successfully but fail validation. This tests a specific, otherwise + * untested, code path in ResolutionErrorTable::free_entry. + * + * @library /runtime/testlibrary + * @compile TestNestHostErrorWithClassUnload.java + * Helper.java + * PackagedNestHost.java + * PackagedNestHost2.java + * @compile PackagedNestHost2Member.jcod + * + * @run main/othervm -Xlog:class+unload=trace TestNestHostErrorWithClassUnload + */ + +// Test setup: +// PackagedNestHost.java defines P1.PackagedNestHost, which is referenced +// by P2.PackageNestHost2.Member +// PackagedNestHost2.java defines P2.PackagedNestHost2 and its nested Member +// class. +// PackagedNestHost2Member.jcod changes P2.PackagedNestHost.Member to claim +// it is in the nest of P1.PackagedNestHost. +// Helper.java is a helper class to run the test under the other classloader. +// A separate class is used to avoid confusion because if a public nested +// class were to be used, then the main test class would be loaded a second +// time in the other loader, and also subsequently unloaded. +// +// We load all classes into a new classloader and then try to call +// P2.PackagedNestHost.Member.m() which will be private at runtime. That will +// trigger nest host resolution and validation and the invocation will fail +// with IllegalAccessError. We then drop the classloader and invoke GC to force +// the unloading of the classes. Not all GC configurations are guaranteed to +// result in unloading, but that isn't essential for the test to succeed - we +// know that when unloading does occur we have tested the desired code path. + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +public class TestNestHostErrorWithClassUnload { + + static final MethodType INVOKE_T = MethodType.methodType(void.class); + + public static void main(String[] args) throws Throwable { + ClassLoader cl = ClassUnloadCommon.newClassLoader(); + Class c = cl.loadClass("Helper"); + MethodHandle mh = MethodHandles.lookup().findStatic(c, "test", INVOKE_T); + mh.invoke(); + // now drop all references so we can trigger unloading + mh = null; + c = null; + cl = null; + ClassUnloadCommon.triggerUnloading(); + } +} diff --git a/test/hotspot/jtreg/runtime/Nestmates/membership/TestNestmateMembership.java b/test/hotspot/jtreg/runtime/Nestmates/membership/TestNestmateMembership.java --- a/test/hotspot/jtreg/runtime/Nestmates/membership/TestNestmateMembership.java +++ b/test/hotspot/jtreg/runtime/Nestmates/membership/TestNestmateMembership.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020, 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 @@ -697,8 +697,8 @@ check_expected(expected, msg); } - msg = "class TestNestmateMembership$CallerNoHost tried to access " + - "private method 'void TestNestmateMembership$Target.m()'"; + msg = "class TestNestmateMembership$CallerNoHost tried to access private " + + "method 'void TestNestmateMembership$Target.m()'"; try { CallerNoHost.invokeTarget(); throw new Error("Missing IllegalAccessError: " + msg); @@ -720,19 +720,21 @@ static void test_SelfHostInvoke() throws Throwable { System.out.println("Testing for class that lists itself as nest-host"); String msg = "Type TestNestmateMembership$TargetSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member)"; try { Caller.invokeTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetSelfHost with modifiers \"private static\""; try { Caller.invokeTargetSelfHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "no such method: TestNestmateMembership$TargetSelfHost.m()void/invokeStatic"; @@ -745,43 +747,42 @@ } msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.invokeTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.invokeTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } static void test_MissingHostInvoke() throws Throwable { System.out.println("Testing for nest-host class that does not exist"); - String msg = "Unable to load nest-host class (NoTargetMissingHost) of " + - "TestNestmateMembership$TargetMissingHost"; - String cause_msg = "NoTargetMissingHost"; + String msg = "Nest host resolution of TestNestmateMembership$TargetMissingHost with host" + + " NoTargetMissingHost failed: java.lang.NoClassDefFoundError: NoTargetMissingHost"; try { Caller.invokeTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class" + + " TestNestmateMembership$TargetMissingHost with modifiers \"private static\""; try { Caller.invokeTargetMissingHostReflectively(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessException expected) { + check_expected(expected, msg); } msg = "no such method: TestNestmateMembership$TargetMissingHost.m()void/invokeStatic"; try { @@ -800,44 +801,42 @@ check_expected(expected, msg); } - msg = "Unable to load nest-host class (NoCallerMissingHost) of " + - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; + msg = "Nest host resolution of TestNestmateMembership$CallerMissingHost with host" + + " NoCallerMissingHost failed: java.lang.NoClassDefFoundError: NoCallerMissingHost"; try { CallerMissingHost.invokeTarget(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } - msg = "Unable to load nest-host class (NoCallerMissingHost) of "+ - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; try { CallerMissingHost.invokeTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } } static void test_NotInstanceHostInvoke() throws Throwable { System.out.println("Testing for nest-host class that is not an instance class"); String msg = "Type TestNestmateMembership$TargetNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { Caller.invokeTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class "+ + "TestNestmateMembership$TargetNotInstanceHost with modifiers \"private static\""; try { Caller.invokeTargetNotInstanceHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "no such method: TestNestmateMembership$TargetNotInstanceHost.m()void/invokeStatic"; @@ -849,42 +848,42 @@ check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a " + + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { CallerNotInstanceHost.invokeTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; try { CallerNotInstanceHost.invokeTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } static void test_NotOurHostInvoke() throws Throwable { System.out.println("Testing for nest-host class that does not list us in its nest"); - String msg = "Type TestNestmateMembership$TargetNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + String msg = "Type TestNestmateMembership$TargetNotOurHost (loader: 'app') is not a " + + "nest member of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { Caller.invokeTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetNotOurHost with modifiers \"private static\""; try { Caller.invokeTargetNotOurHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "no such method: TestNestmateMembership$TargetNotOurHost.m()void/invokeStatic"; @@ -897,21 +896,19 @@ } msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { CallerNotOurHost.invokeTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { CallerNotOurHost.invokeTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -919,19 +916,19 @@ static void test_WrongPackageHostInvoke() { System.out.println("Testing for nest-host and nest-member in different packages"); String msg = "Type P2.PackagedNestHost2$Member (loader: 'app') is not a nest member of " + - "P1.PackagedNestHost (loader: 'app'): types are in different packages"; + "type P1.PackagedNestHost (loader: 'app'): types are in different packages"; try { P1.PackagedNestHost.doInvoke(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } try { P2.PackagedNestHost2.Member.doInvoke(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -953,9 +950,9 @@ "method 'void TestNestmateMembership$TargetNoHost.()'"; try { Caller.newTargetNoHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } msg = "class TestNestmateMembership$Caller cannot access a member of class " + @@ -980,18 +977,18 @@ "method 'void TestNestmateMembership$Target.()'"; try { CallerNoHost.newTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } msg = "class TestNestmateMembership$CallerNoHost tried to access private " + "method 'void TestNestmateMembership$TargetNoHost.()'"; try { CallerNoHost.newTargetNoHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -999,19 +996,21 @@ static void test_SelfHostConstruct() throws Throwable { System.out.println("Testing for class that lists itself as nest-host"); String msg = "Type TestNestmateMembership$TargetSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member"; try { Caller.newTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetSelfHost with modifiers \"private\""; try { Caller.newTargetSelfHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "no such constructor: TestNestmateMembership$TargetSelfHost.()void/newInvokeSpecial"; @@ -1024,43 +1023,42 @@ } msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.newTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.newTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } static void test_MissingHostConstruct() throws Throwable { System.out.println("Testing for nest-host class that does not exist"); - String msg = "Unable to load nest-host class (NoTargetMissingHost) of " + - "TestNestmateMembership$TargetMissingHost"; - String cause_msg = "NoTargetMissingHost"; + String msg = "Nest host resolution of TestNestmateMembership$TargetMissingHost with " + + "host NoTargetMissingHost failed: java.lang.NoClassDefFoundError: NoTargetMissingHost"; try { Caller.newTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetMissingHost with modifiers \"private\""; try { Caller.newTargetMissingHostReflectively(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessException expected) { + check_expected(expected, msg); } msg = "no such constructor: TestNestmateMembership$TargetMissingHost.()void/newInvokeSpecial"; try { @@ -1071,44 +1069,42 @@ check_expected(expected, msg); } - msg = "Unable to load nest-host class (NoCallerMissingHost) of " + - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; + msg = "Nest host resolution of TestNestmateMembership$CallerMissingHost with host " + + "NoCallerMissingHost failed: java.lang.NoClassDefFoundError: NoCallerMissingHost"; try { CallerMissingHost.newTarget(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } - msg = "Unable to load nest-host class (NoCallerMissingHost) of "+ - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; try { CallerMissingHost.newTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } } static void test_NotInstanceHostConstruct() throws Throwable { System.out.println("Testing for nest-host class that is not an instance class"); String msg = "Type TestNestmateMembership$TargetNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { Caller.newTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetNotInstanceHost with modifiers \"private\""; try { Caller.newTargetNotInstanceHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "no such constructor: TestNestmateMembership$TargetNotInstanceHost.()void/newInvokeSpecial"; @@ -1121,21 +1117,19 @@ } msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { CallerNotInstanceHost.newTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; try { CallerNotInstanceHost.newTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1143,19 +1137,21 @@ static void test_NotOurHostConstruct() throws Throwable { System.out.println("Testing for nest-host class that does not list us in its nest"); String msg = "Type TestNestmateMembership$TargetNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { Caller.newTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetNotOurHost with modifiers \"private\""; try { Caller.newTargetNotOurHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "no such constructor: TestNestmateMembership$TargetNotOurHost.()void/newInvokeSpecial"; @@ -1168,21 +1164,21 @@ } msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { CallerNotOurHost.newTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { CallerNotOurHost.newTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1190,19 +1186,19 @@ static void test_WrongPackageHostConstruct() { System.out.println("Testing for nest-host and nest-member in different packages"); String msg = "Type P2.PackagedNestHost2$Member (loader: 'app') is not a nest member of " + - "P1.PackagedNestHost (loader: 'app'): types are in different packages"; + "type P1.PackagedNestHost (loader: 'app'): types are in different packages"; try { P1.PackagedNestHost.doConstruct(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } try { P2.PackagedNestHost2.Member.doConstruct(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1224,9 +1220,9 @@ "field TestNestmateMembership$TargetNoHost.f"; try { Caller.getFieldTargetNoHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } msg = "class TestNestmateMembership$Caller cannot access a member of class " + @@ -1251,18 +1247,18 @@ "field TestNestmateMembership$Target.f"; try { CallerNoHost.getFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } msg = "class TestNestmateMembership$CallerNoHost tried to access private " + "field TestNestmateMembership$TargetNoHost.f"; try { CallerNoHost.getFieldTargetNoHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1270,140 +1266,143 @@ static void test_SelfHostGetField() throws Throwable { System.out.println("Testing for class that lists itself as nest-host"); String msg = "Type TestNestmateMembership$TargetSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member"; try { Caller.getFieldTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetSelfHost with modifiers \"private static\""; try { Caller.getFieldTargetSelfHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetSelfHost.f/int/getStatic, " + + "from class TestNestmateMembership$Caller"; try { Caller.getFieldTargetSelfHostMH(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.getFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.getFieldTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } static void test_MissingHostGetField() throws Throwable { System.out.println("Testing for nest-host class that does not exist"); - String msg = "Unable to load nest-host class (NoTargetMissingHost) of " + - "TestNestmateMembership$TargetMissingHost"; - String cause_msg = "NoTargetMissingHost"; + String msg = "Nest host resolution of TestNestmateMembership$TargetMissingHost with " + + "host NoTargetMissingHost failed: java.lang.NoClassDefFoundError: NoTargetMissingHost"; try { Caller.getFieldTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetMissingHost with modifiers \"private static\""; try { Caller.getFieldTargetMissingHostReflectively(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessException expected) { + check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetMissingHost.f/int/getStatic, " + + "from class TestNestmateMembership$Caller"; try { Caller.getFieldTargetMissingHostMH(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessException expected) { + check_expected(expected, msg); } - msg = "Unable to load nest-host class (NoCallerMissingHost) of " + - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; + msg = "Nest host resolution of TestNestmateMembership$CallerMissingHost with " + + "host NoCallerMissingHost failed: java.lang.NoClassDefFoundError: NoCallerMissingHost"; try { CallerMissingHost.getFieldTarget(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } - msg = "Unable to load nest-host class (NoCallerMissingHost) of "+ - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; try { CallerMissingHost.getFieldTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } } static void test_NotInstanceHostGetField() throws Throwable { System.out.println("Testing for nest-host class that is not an instance class"); String msg = "Type TestNestmateMembership$TargetNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { Caller.getFieldTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetNotInstanceHost with modifiers \"private static\""; try { Caller.getFieldTargetNotInstanceHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetNotInstanceHost.f/int/getStatic, " + + "from class TestNestmateMembership$Caller"; try { Caller.getFieldTargetNotInstanceHostMH(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { CallerNotInstanceHost.getFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; try { CallerNotInstanceHost.getFieldTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1411,45 +1410,47 @@ static void test_NotOurHostGetField() throws Throwable { System.out.println("Testing for nest-host class that does not list us in its nest"); String msg = "Type TestNestmateMembership$TargetNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { Caller.getFieldTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetNotOurHost with modifiers \"private static\""; try { Caller.getFieldTargetNotOurHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetNotOurHost.f/int/getStatic, " + + "from class TestNestmateMembership$Caller"; try { Caller.getFieldTargetNotOurHostMH(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { CallerNotOurHost.getFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { CallerNotOurHost.getFieldTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1457,19 +1458,19 @@ static void test_WrongPackageHostGetField() { System.out.println("Testing for nest-host and nest-member in different packages"); String msg = "Type P2.PackagedNestHost2$Member (loader: 'app') is not a nest member of " + - "P1.PackagedNestHost (loader: 'app'): types are in different packages"; + "type P1.PackagedNestHost (loader: 'app'): types are in different packages"; try { P1.PackagedNestHost.doGetField(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } try { P2.PackagedNestHost2.Member.doGetField(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1489,9 +1490,9 @@ "field TestNestmateMembership$TargetNoHost.f"; try { Caller.putFieldTargetNoHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } msg = "class TestNestmateMembership$Caller cannot access a member of class " + @@ -1516,18 +1517,18 @@ "field TestNestmateMembership$Target.f"; try { CallerNoHost.putFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } msg = "class TestNestmateMembership$CallerNoHost tried to access private " + "field TestNestmateMembership$TargetNoHost.f"; try { CallerNoHost.putFieldTargetNoHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1535,140 +1536,142 @@ static void test_SelfHostPutField() throws Throwable { System.out.println("Testing for class that lists itself as nest-host"); String msg = "Type TestNestmateMembership$TargetSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$TargetSelfHost (loader: 'app'): current type is not listed as a nest member"; try { Caller.putFieldTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetSelfHost with modifiers \"private static\""; try { Caller.putFieldTargetSelfHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetSelfHost.f/int/putStatic, " + + "from class TestNestmateMembership$Caller"; try { Caller.putFieldTargetSelfHostMH(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; + " of type TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.putFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerSelfHost (loader: 'app') is not a nest member" + - " of TestNestmateMembership$CallerSelfHost (loader: 'app'): current type is not listed as a nest member"; try { CallerSelfHost.putFieldTargetSelfHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } static void test_MissingHostPutField() throws Throwable { System.out.println("Testing for nest-host class that does not exist"); - String msg = "Unable to load nest-host class (NoTargetMissingHost) of " + - "TestNestmateMembership$TargetMissingHost"; - String cause_msg = "NoTargetMissingHost"; + String msg = "Nest host resolution of TestNestmateMembership$TargetMissingHost with " + + "host NoTargetMissingHost failed: java.lang.NoClassDefFoundError: NoTargetMissingHost"; try { Caller.putFieldTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetMissingHost with modifiers \"private static\""; try { Caller.putFieldTargetMissingHostReflectively(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessException expected) { + check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetMissingHost.f/int/putStatic, " + + "from class TestNestmateMembership$Caller"; try { Caller.putFieldTargetMissingHostMH(); - throw new Error("Missing NoClassDefFoundError: " + msg); - } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + throw new Error("Missing IllegalAccessException: " + msg); } - - msg = "Unable to load nest-host class (NoCallerMissingHost) of " + - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; + catch (IllegalAccessException expected) { + check_expected(expected, msg); + } + msg = "Nest host resolution of TestNestmateMembership$CallerMissingHost with host " + + "NoCallerMissingHost failed: java.lang.NoClassDefFoundError: NoCallerMissingHost"; try { CallerMissingHost.putFieldTarget(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } - msg = "Unable to load nest-host class (NoCallerMissingHost) of "+ - "TestNestmateMembership$CallerMissingHost"; - cause_msg = "NoCallerMissingHost"; try { CallerMissingHost.putFieldTargetMissingHost(); - throw new Error("Missing NoClassDefFoundError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (NoClassDefFoundError expected) { - check_expected(expected, msg, cause_msg); + catch (IllegalAccessError expected) { + check_expected(expected, msg); } } static void test_NotInstanceHostPutField() throws Throwable { System.out.println("Testing for nest-host class that is not an instance class"); String msg = "Type TestNestmateMembership$TargetNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { Caller.putFieldTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetNotInstanceHost with modifiers \"private static\""; try { Caller.putFieldTargetNotInstanceHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetNotInstanceHost.f/int/putStatic, " + + "from class TestNestmateMembership$Caller"; try { Caller.putFieldTargetNotInstanceHostMH(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessException: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessException expected) { check_expected(expected, msg); } msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; + "nest member of type [LInvalidNestHost; (loader: 'app'): host is not an instance class"; try { CallerNotInstanceHost.putFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } - msg = "Type TestNestmateMembership$CallerNotInstanceHost (loader: 'app') is not a "+ - "nest member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member"; try { CallerNotInstanceHost.putFieldTargetNotInstanceHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1676,45 +1679,46 @@ static void test_NotOurHostPutField() throws Throwable { System.out.println("Testing for nest-host class that does not list us in its nest"); String msg = "Type TestNestmateMembership$TargetNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; try { Caller.putFieldTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); + } + catch (IllegalAccessError expected) { + check_expected(expected, msg); } - catch (IncompatibleClassChangeError expected) { + msg = "class TestNestmateMembership$Caller cannot access a member of class " + + "TestNestmateMembership$TargetNotOurHost with modifiers \"private static\""; + try { + Caller.putFieldTargetNotOurHostReflectively(); + throw new Error("Missing IllegalAccessException: " + msg); + } + catch (IllegalAccessException expected) { check_expected(expected, msg); } + msg = "member is private: TestNestmateMembership$TargetNotOurHost.f/int/putStatic, " + + "from class TestNestmateMembership$Caller"; try { - Caller.putFieldTargetNotOurHostReflectively(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + Caller.putFieldTargetNotOurHostMH(); + throw new Error("Missing IllegalAccessException: " + msg); + } + catch (IllegalAccessException expected) { + check_expected(expected, msg); } - catch (IncompatibleClassChangeError expected) { + msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + + " of type InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; + try { + CallerNotOurHost.putFieldTarget(); + throw new Error("Missing IllegalAccessError: " + msg); + } + catch (IllegalAccessError expected) { check_expected(expected, msg); } try { - Caller.putFieldTargetNotOurHostMH(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); - } - catch (IncompatibleClassChangeError expected) { - check_expected(expected, msg); + CallerNotOurHost.putFieldTargetNotOurHost(); + throw new Error("Missing IllegalAccessError: " + msg); } - - msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; - try { - CallerNotOurHost.putFieldTarget(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); - } - catch (IncompatibleClassChangeError expected) { - check_expected(expected, msg); - } - msg = "Type TestNestmateMembership$CallerNotOurHost (loader: 'app') is not a nest member" + - " of InvalidNestHost (loader: 'app'): current type is not listed as a nest member"; - try { - CallerNotOurHost.putFieldTargetNotOurHost(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); - } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1722,19 +1726,19 @@ static void test_WrongPackageHostPutField() { System.out.println("Testing for nest-host and nest-member in different packages"); String msg = "Type P2.PackagedNestHost2$Member (loader: 'app') is not a nest member of " + - "P1.PackagedNestHost (loader: 'app'): types are in different packages"; + "type P1.PackagedNestHost (loader: 'app'): types are in different packages"; try { P1.PackagedNestHost.doPutField(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } try { P2.PackagedNestHost2.Member.doPutField(); - throw new Error("Missing IncompatibleClassChangeError: " + msg); + throw new Error("Missing IllegalAccessError: " + msg); } - catch (IncompatibleClassChangeError expected) { + catch (IllegalAccessError expected) { check_expected(expected, msg); } } @@ -1745,31 +1749,8 @@ if (!expected.getMessage().contains(msg)) { throw new Error("Wrong " + expected.getClass().getSimpleName() +": \"" + expected.getMessage() + "\" does not contain \"" + - msg + "\""); + msg + "\"", expected); } System.out.println("OK - got expected exception: " + expected); } - - static void check_expected(Throwable expected, String msg, String cause_msg) { - if (!expected.getMessage().contains(msg)) { - throw new Error("Wrong " + expected.getClass().getSimpleName() +": \"" + - expected.getMessage() + "\" does not contain \"" + - msg + "\""); - } - Throwable cause = expected.getCause(); - if (cause instanceof NoClassDefFoundError) { - if (!cause.getMessage().contains(cause_msg)) { - throw new Error(expected.getClass().getSimpleName() + - " has wrong cause " + cause.getClass().getSimpleName() +": \"" + - cause.getMessage() + "\" does not contain \"" + - cause_msg + "\""); - } - } - else throw new Error(expected.getClass().getSimpleName() + - " has wrong cause " + cause.getClass().getSimpleName()); - - System.out.println("OK - got expected exception: " + expected + - " with cause " + cause); - } - } diff --git a/test/hotspot/jtreg/runtime/Nestmates/privateMethods/TestInvokeErrors.java b/test/hotspot/jtreg/runtime/Nestmates/privateMethods/TestInvokeErrors.java --- a/test/hotspot/jtreg/runtime/Nestmates/privateMethods/TestInvokeErrors.java +++ b/test/hotspot/jtreg/runtime/Nestmates/privateMethods/TestInvokeErrors.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020, 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 @@ -74,8 +74,12 @@ m.priv_invoke(); throw new Error("Unexpected success invoking MissingNestHost.priv_invoke"); } - catch (NoClassDefFoundError ncdfe) { - System.out.println("Got expected exception:" + ncdfe); + catch (IllegalAccessError iae) { + if (iae.getMessage().contains("java.lang.NoClassDefFoundError: NoSuchClass")) { + System.out.println("Got expected exception:" + iae); + } else { + throw new Error("Unexpected exception", iae); + } } } } @@ -105,11 +109,11 @@ try { Helper.doTest(); } - catch (NoClassDefFoundError ncdfe) { + catch (IllegalAccessError iae) { if (verifying) - System.out.println("Got expected exception:" + ncdfe); + System.out.println("Got expected exception:" + iae); else - throw new Error("Unexpected error loading Helper class with verification disabled"); + throw new Error("Unexpected error loading Helper class with verification disabled", iae); } } } diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderHierarchyTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderHierarchyTest.java --- a/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderHierarchyTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderHierarchyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,8 +27,7 @@ * @summary Test of diagnostic command VM.classloaders * @library /test/lib * @modules java.base/jdk.internal.misc - * java.compiler - * java.management + * jdk.compiler * jdk.internal.jvmstat/sun.jvmstat.monitor * @run testng ClassLoaderHierarchyTest */ @@ -97,6 +96,7 @@ output.shouldMatch("Kevin.*TestClassLoader"); output.shouldMatch("Bill.*TestClassLoader"); output.shouldContain("TestClass2"); + output.shouldContain("Hidden Classes:"); } static class TestClassLoader extends ClassLoader { diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderStatsTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderStatsTest.java --- a/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderStatsTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/ClassLoaderStatsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, 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 @@ -29,7 +29,7 @@ * java.compiler * java.management * jdk.internal.jvmstat/sun.jvmstat.monitor - * @run testng ClassLoaderStatsTest + * @run testng/othervm --add-exports=java.base/jdk.internal.misc=ALL-UNNAMED --add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED ClassLoaderStatsTest */ import org.testng.annotations.Test; @@ -39,27 +39,34 @@ import jdk.test.lib.dcmd.CommandExecutor; import jdk.test.lib.dcmd.JMXExecutor; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; +import jdk.internal.misc.Unsafe; + public class ClassLoaderStatsTest { + // Expected output from VM.classloader_stats: // ClassLoader Parent CLD* Classes ChunkSz BlockSz Type - // 0x00000007c0215928 0x0000000000000000 0x0000000000000000 0 0 0 org.eclipse.osgi.baseadaptor.BaseAdaptor$1 - // 0x00000007c0009868 0x0000000000000000 0x00007fc52aebcc80 1 6144 3768 sun.reflect.DelegatingClassLoader - // 0x00000007c0009868 0x0000000000000000 0x00007fc52b8916d0 1 6144 3688 sun.reflect.DelegatingClassLoader - // 0x00000007c0009868 0x00000007c0038ba8 0x00007fc52afb8760 1 6144 3688 sun.reflect.DelegatingClassLoader - // 0x00000007c0009868 0x0000000000000000 0x00007fc52afbb1a0 1 6144 3688 sun.reflect.DelegatingClassLoader - // 0x0000000000000000 0x0000000000000000 0x00007fc523416070 5019 30060544 29956216 - // 455 1210368 672848 + unsafe anonymous classes - // 0x00000007c016b5c8 0x00000007c0038ba8 0x00007fc52a995000 5 8192 5864 org.netbeans.StandardModule$OneModuleClassLoader - // 0x00000007c0009868 0x00000007c016b5c8 0x00007fc52ac13640 1 6144 3896 sun.reflect.DelegatingClassLoader + // 0x0000000800bd3830 0x000000080037f468 0x00007f001c2ea170 1 10240 4672 ClassLoaderStatsTest$DummyClassLoader + // 1 2048 1080 + unsafe anonymous classes + // 1 2048 1088 + hidden classes + // 0x0000000000000000 0x0000000000000000 0x00007f00e852d190 1607 4628480 3931216 + // 38 124928 85856 + hidden classes + // 0x00000008003b5508 0x0000000000000000 0x00007f001c2d4760 1 6144 4040 jdk.internal.reflect.DelegatingClassLoader + // 0x000000080037f468 0x000000080037ee80 0x00007f00e868e3f0 228 1368064 1286672 jdk.internal.loader.ClassLoaders$AppClassLoader // ... static Pattern clLine = Pattern.compile("0x\\p{XDigit}*\\s*0x\\p{XDigit}*\\s*0x\\p{XDigit}*\\s*(\\d*)\\s*(\\d*)\\s*(\\d*)\\s*(.*)"); @@ -69,7 +76,7 @@ public void run(CommandExecutor executor) throws ClassNotFoundException { - // create a classloader and load our special class + // create a classloader and load our special classes dummyloader = new DummyClassLoader(); Class c = Class.forName("TestClass", true, dummyloader); if (c.getClassLoader() != dummyloader) { @@ -82,9 +89,9 @@ String line = lines.next(); Matcher m = clLine.matcher(line); if (m.matches()) { - // verify that DummyClassLoader has loaded 1 class and 1 anonymous class + // verify that DummyClassLoader has loaded 1 class, 1 anonymous class, and 1 hidden class if (m.group(4).equals("ClassLoaderStatsTest$DummyClassLoader")) { - System.out.println("line: " + line); + System.out.println("DummyClassLoader line: " + line); if (!m.group(1).equals("1")) { Assert.fail("Should have loaded 1 class: " + line); } @@ -92,7 +99,10 @@ checkPositiveInt(m.group(3)); String next = lines.next(); - System.out.println("next: " + next); + System.out.println("DummyClassLoader next: " + next); + if (!next.contains("unsafe anonymous classes")) { + Assert.fail("Should have an anonymous class"); + } Matcher m1 = anonLine.matcher(next); m1.matches(); if (!m1.group(1).equals("1")) { @@ -100,6 +110,19 @@ } checkPositiveInt(m1.group(2)); checkPositiveInt(m1.group(3)); + + next = lines.next(); + System.out.println("DummyClassLoader next: " + next); + if (!next.contains("hidden classes")) { + Assert.fail("Should have a hidden class"); + } + Matcher m2 = anonLine.matcher(next); + m2.matches(); + if (!m2.group(1).equals("1")) { + Assert.fail("Should have loaded 1 hidden class, but found : " + m2.group(1)); + } + checkPositiveInt(m2.group(2)); + checkPositiveInt(m2.group(3)); } } } @@ -117,8 +140,7 @@ static ByteBuffer readClassFile(String name) { - File f = new File(System.getProperty("test.classes", "."), - name); + File f = new File(System.getProperty("test.classes", "."), name); try (FileInputStream fin = new FileInputStream(f); FileChannel fc = fin.getChannel()) { @@ -163,10 +185,41 @@ } } +class HiddenClass { } + class TestClass { + private static final String HCName = "HiddenClass.class"; + private static final String DIR = System.getProperty("test.classes"); + static Unsafe unsafe = Unsafe.getUnsafe(); + static { - // force creation of anonymous class (for the lambdaform) - Runnable r = () -> System.out.println("Hello"); - r.run(); + try { + // Create a hidden non-strong class and an anonymous class. + byte[] klassBuf = readClassFile(DIR + File.separator + HCName); + Class hc = defineHiddenClass(klassBuf); + Class ac = unsafe.defineAnonymousClass(TestClass.class, klassBuf, new Object[0]); + } catch (Throwable e) { + throw new RuntimeException("Unexpected exception in TestClass: " + e.getMessage()); + } + } + + + static byte[] readClassFile(String classFileName) throws Exception { + File classFile = new File(classFileName); + try (FileInputStream in = new FileInputStream(classFile); + ByteArrayOutputStream out = new ByteArrayOutputStream()) + { + int b; + while ((b = in.read()) != -1) { + out.write(b); + } + return out.toByteArray(); + } + } + + static Class defineHiddenClass(byte[] bytes) throws Exception { + Lookup lookup = MethodHandles.lookup(); + Class hc = lookup.defineHiddenClass(bytes, false, NESTMATE).lookupClass(); + return hc; } } diff --git a/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/P/Q/HiddenClassSigTest.java b/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/P/Q/HiddenClassSigTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/P/Q/HiddenClassSigTest.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019, 2020, 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 + * @library /test/lib + * @modules java.base/jdk.internal.misc + * jdk.compiler + * @compile HiddenClassSigTest.java + * @run main/othervm/native -agentlib:HiddenClassSigTest P.Q.HiddenClassSigTest + */ + +package P.Q; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +import jdk.test.lib.Utils; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + + +interface Test { + String test(T t); +} + +class HiddenClassSig implements Test { + private String realTest() { return "HiddenClassSig: "; } + + public String test(T t) { + String str = realTest(); + return str + t.toString(); + } +} + +public class HiddenClassSigTest { + private static void log(String str) { System.out.println(str); } + + private static final String HCName = "P/Q/HiddenClassSig.class"; + private static final String DIR = Utils.TEST_CLASSES; + private static final String LOG_PREFIX = "HiddenClassSigTest: "; + private static final String ARR_PREFIX = "[[L"; + private static final String ARR_POSTFIX = "[][]"; + private static final String CLASS_PREFIX = "class "; + + static native void checkHiddenClass(Class klass, String sig); + static native void checkHiddenClassArray(Class array, String sig); + static native boolean checkFailed(); + + static { + try { + System.loadLibrary("HiddenClassSigTest"); + } catch (UnsatisfiedLinkError ule) { + System.err.println("Could not load HiddenClassSigTest library"); + System.err.println("java.library.path: " + + System.getProperty("java.library.path")); + throw ule; + } + } + + static byte[] readClassFile(String classFileName) throws Exception { + File classFile = new File(classFileName); + try (FileInputStream in = new FileInputStream(classFile); + ByteArrayOutputStream out = new ByteArrayOutputStream()) + { + int b; + while ((b = in.read()) != -1) { + out.write(b); + } + return out.toByteArray(); + } + } + + static Class defineHiddenClass(String classFileName) throws Exception { + Lookup lookup = MethodHandles.lookup(); + byte[] bytes = readClassFile(DIR + File.separator + classFileName); + Class hc = lookup.defineHiddenClass(bytes, false).lookupClass(); + return hc; + } + + static void logClassInfo(Class klass) { + log("\n### Testing class: " + klass); + log(LOG_PREFIX + "isHidden: " + klass.isHidden()); + log(LOG_PREFIX + "getName: " + klass.getName()); + log(LOG_PREFIX + "typeName: " + klass.getTypeName()); + log(LOG_PREFIX + "toString: " + klass.toString()); + log(LOG_PREFIX + "toGenStr: " + klass.toGenericString()); + log(LOG_PREFIX + "elem type: " + klass.componentType()); + } + + static boolean checkName(String name, String expName, String msgPart) { + boolean failed = false; + if (!name.equals(expName)) { + log("Test FAIL: result of " + msgPart + " does not match expectation"); + failed = true; + } + return failed; + } + + static boolean checkNameHas(String name, String expNamePart, String msgPart) { + boolean failed = false; + if (name.indexOf(expNamePart) < 0) { + log("Test FAIL: result of " + msgPart + " does not match expectation"); + failed = true; + } + return failed; + } + + static boolean checkArray(Class arrClass) { + boolean failed = false; + Class elemClass = arrClass.componentType(); + + String arrName = arrClass.getName(); + String arrType = arrClass.getTypeName(); + String arrStr = arrClass.toString().substring(CLASS_PREFIX.length()); + String arrGen = arrClass.toGenericString(); + + String elemName = elemClass.getName(); + String elemType = elemClass.getTypeName(); + String elemStr = elemClass.toString().substring(CLASS_PREFIX.length()); + String elemGen = elemClass.toGenericString(); + + if (elemClass.isHidden()) { + elemGen = elemGen.substring(CLASS_PREFIX.length()); + } + failed |= checkNameHas(arrName, elemName, "klass.getName()"); + failed |= checkNameHas(arrStr, elemStr, "klass.toString()"); + failed |= checkNameHas(arrType, elemType, "klass.getTypeName()"); + failed |= checkNameHas(arrGen, elemGen, "klass.getGenericString()"); + return failed; + } + + static boolean testClass(Class klass, int arrLevel, String baseName) { + boolean failed = false; + boolean isHidden = (arrLevel == 0); + String prefix = (arrLevel == 0) ? "" : ARR_PREFIX.substring(2 - arrLevel); + String postfix = (arrLevel == 0) ? "" : ";"; + + logClassInfo(klass); + + String expName = ("" + klass).substring(CLASS_PREFIX.length()); + String expType = baseName; + String expStr = CLASS_PREFIX + prefix + baseName + postfix; + String expGen = baseName + "" + ARR_POSTFIX.substring(0, 2*arrLevel); + + if (arrLevel > 0) { + expType = expType + ARR_POSTFIX.substring(0, 2*arrLevel); + } else { + expGen = CLASS_PREFIX + expGen; + } + failed |= checkName(klass.getName(), expName, "klass.getName()"); + failed |= checkName(klass.getTypeName(), expType, "klass.getTypeName()"); + failed |= checkName(klass.toString(), expStr, "klass.toString()"); + failed |= checkName(klass.toGenericString(), expGen, "klass.toGenericString()"); + + if (klass.isHidden() != isHidden) { + log("Test FAIL: result of klass.isHidden() does not match expectation"); + failed = true; + } + String sig = hcSignature(klass); + if (arrLevel == 0) { + checkHiddenClass(klass, sig); + } else { + failed |= checkArray(klass); + checkHiddenClassArray(klass, sig); + } + return failed; + } + + static String hcSignature(Class klass) { + boolean isArray = klass.isArray(); + String sig = klass.getName(); + String prefix = isArray ? "" : "L"; + String postfix = isArray ? "" : ";"; + int idx = sig.indexOf("/"); + + sig = prefix + sig.substring(0, idx).replace('.', '/') + + "." + + sig.substring(idx + 1, sig.length()) + + postfix; + return sig; + } + + public static void main(String args[]) throws Exception { + log(LOG_PREFIX + "started"); + Class hc = defineHiddenClass(HCName); + String baseName = ("" + hc).substring("class ".length()); + + Test t = (Test)hc.newInstance(); + String str = t.test("Test generic hidden class"); + log(LOG_PREFIX + "hc.test() returned string: " + str); + + boolean failed = testClass(hc, 0, baseName); + + Class hcArr = hc.arrayType(); + failed |= testClass(hcArr, 1, baseName); + + Class hcArrArr = hcArr.arrayType(); + failed |= testClass(hcArrArr, 2, baseName); + + if (failed) { + throw new RuntimeException("FAIL: failed status from java part"); + } + if (checkFailed()) { + throw new RuntimeException("FAIL: failed status from native agent"); + } + log(LOG_PREFIX + "finished"); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/libHiddenClassSigTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/libHiddenClassSigTest.cpp new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/libHiddenClassSigTest.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2019, 2020, 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. + */ + +#include +#include "jvmti.h" + +extern "C" { + +static const char* EXP_INTERF_SIG = "LP/Q/Test;"; +static const char* SIG_START = "LP/Q/HiddenClassSig"; +static const size_t SIG_START_LEN = strlen(SIG_START); +static const int ACC_INTERFACE = 0x0200; // Interface class modifiers bit + +static jvmtiEnv *jvmti = NULL; +static jint class_load_count = 0; +static bool failed = false; + +#define LOG0(str) { printf(str); fflush(stdout); } +#define LOG1(str, arg) { printf(str, arg); fflush(stdout); } +#define LOG2(str, arg1, arg2) { printf(str, arg1, arg2); fflush(stdout); } + +#define CHECK_JVMTI_ERROR(jni, err, msg) \ + if (err != JVMTI_ERROR_NONE) { \ + LOG1("CHECK_JVMTI_ERROR: JVMTI function returned error: %d\n", err); \ + jni->FatalError(msg); \ + return; \ + } + +/* Return the jmethodID of j.l.Class.isHidden() method. */ +static jmethodID +is_hidden_mid(JNIEnv* jni) { + char* csig = NULL; + jint count = 0; + jmethodID *methods = NULL; + jclass clazz = jni->FindClass("java/lang/Class"); + if (clazz == NULL) { + jni->FatalError("is_hidden_mid: Error: FindClass returned NULL for java/lang/Class\n"); + return NULL; + } + + // find the jmethodID of j.l.Class.isHidden() method + jmethodID mid = jni->GetMethodID(clazz, "isHidden", "()Z"); + if (mid == NULL) { + jni->FatalError("is_hidden_mid: Error in jni GetMethodID: Cannot find j.l.Class.isHidden method\n"); + } + return mid; +} + +/* Return true if the klass is hidden. */ +static bool +is_hidden(JNIEnv* jni, jclass klass) { + static jmethodID is_hid_mid = NULL; + + if (is_hid_mid == NULL) { + is_hid_mid = is_hidden_mid(jni); + } + // invoke j.l.Class.isHidden() method + return jni->CallBooleanMethod(klass, is_hid_mid); +} + +/* Check the class signature matches the expected. */ +static void +check_class_signature(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass, bool is_hidden, const char* exp_sig) { + jint class_modifiers = 0; + char* sig = NULL; + char* gsig = NULL; + jvmtiError err; + + err = jvmti->GetClassSignature(klass, &sig, &gsig); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class: Error in JVMTI GetClassSignature"); + + LOG1("check_class_signature: class with sig: %s\n", sig); + LOG1("check_class_signature: class with gsig: %s\n", gsig); + + if (strcmp(sig, exp_sig) != 0) { + LOG2("check_class_signature: FAIL: Hidden class signature %s does not mach expected: %s\n", sig, exp_sig); + failed = true; + } + if (is_hidden && gsig == NULL) { + LOG0("check_class_signature: FAIL: unexpected NULL generic signature for hidden class\n"); + failed = true; + } +} + +/* Test hidden class flags: it should not be interface, array nor modifiable. */ +static void +check_hidden_class_flags(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) { + jint modifiers = 0; + jboolean flag = false; + jvmtiError err; + + err = jvmti->GetClassModifiers(klass, &modifiers); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_flags: Error in JVMTI GetClassModifiers"); + LOG1("check_hidden_class_flags: hidden class modifiers: 0x%x\n", modifiers); + if ((modifiers & ACC_INTERFACE) != 0) { + LOG0("check_hidden_class_flags: FAIL: unexpected ACC_INTERFACE bit in hidden class modifiers\n"); + failed = true; + return; + } + + err = jvmti->IsInterface(klass, &flag); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_flags: Error in JVMTI IsInterface"); + if (flag) { + LOG0("check_hidden_class_flags: FAIL: hidden class is not expected to be interface\n"); + failed = true; + return; + } + + err = jvmti->IsArrayClass(klass, &flag); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_flags: Error in JVMTI IsArrayClass"); + if (flag) { + LOG0("check_hidden_class_flags: FAIL: hidden class is not expected to be array\n"); + failed = true; + return; + } + err = jvmti->IsModifiableClass(klass, &flag); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_flags: Error in JVMTI IsModifiableClass"); + if (flag) { + LOG0("check_hidden_class_flags: FAIL: hidden class is not expected to be modifiable\n"); + failed = true; + } +} + +/* Test GetClassLoaderClasses: it should not return any hidden classes. */ +static void +check_hidden_class_loader(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) { + jint count = 0; + jobject loader = NULL; + jclass* loader_classes = NULL; + jboolean found = false; + jvmtiError err; + + err = jvmti->GetClassLoader(klass, &loader); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_loader: Error in JVMTI GetClassLoader"); + + err = jvmti->GetClassLoaderClasses(loader, &count, &loader_classes); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_loader: Error in JVMTI GetClassLoaderClasses"); + + for (int idx = 0; idx < count; idx++) { + char* sig = NULL; + jclass kls = loader_classes[idx]; + + if (!is_hidden(jni, kls)) { + continue; + } + err = jvmti->GetClassSignature(kls, &sig, NULL); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_loader: Error in JVMTI GetClassSignature"); + + LOG1("check_hidden_class_loader: FAIL: JVMTI GetClassLoaderClasses returned hidden class: %s\n", sig); + failed = true; + return; + } + LOG0("check_hidden_class_loader: not found hidden class in its loader classes as expected\n"); +} + +/* Test the hidden class implements expected interface. */ +static void +check_hidden_class_impl_interf(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) { + char* sig = NULL; + jint count = 0; + jclass* interfaces = NULL; + jvmtiError err; + + // check that hidden class implements just one interface + err = jvmti->GetImplementedInterfaces(klass, &count, &interfaces); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_impl_interf: Error in JVMTI GetImplementedInterfaces"); + if (count != 1) { + LOG1("check_hidden_class_impl_interf: FAIL: implemented interfaces count: %d, expected to be 1\n", count); + failed = true; + return; + } + + // check the interface signature is matching the expected + err = jvmti->GetClassSignature(interfaces[0], &sig, NULL); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_impl_interf: Error in JVMTI GetClassSignature for implemented interface"); + + if (strcmp(sig, EXP_INTERF_SIG) != 0) { + LOG2("check_hidden_class_impl_interf: FAIL: implemented interface signature: %s, expected to be: %s\n", + sig, EXP_INTERF_SIG); + failed = true; + } +} + +/* Test hidden class. */ +static void +check_hidden_class(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass, const char* exp_sig) { + char* source_file_name = NULL; + + LOG1("\n### Native agent: check_hidden_class started: class: %s\n", exp_sig); + + check_class_signature(jvmti, jni, klass, true /* not hidden */, exp_sig); + if (failed) return; + + check_hidden_class_flags(jvmti, jni, klass); + if (failed) return; + + check_hidden_class_loader(jvmti, jni, klass); + if (failed) return; + + check_hidden_class_impl_interf(jvmti, jni, klass); + if (failed) return; + + LOG0("### Native agent: check_hidden_class finished\n"); +} + +/* Test hidden class array. */ +static void +check_hidden_class_array(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass_array, const char* exp_sig) { + char* source_file_name = NULL; + + LOG1("\n### Native agent: check_hidden_class_array started: array: %s\n", exp_sig); + + check_class_signature(jvmti, jni, klass_array, false /* is hidden */, exp_sig); + if (failed) return; + + LOG0("### Native agent: check_hidden_class_array finished\n"); +} + +/* Enable CLASS_LOAD event notification mode. */ +static void JNICALL +VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { + jvmtiError err; + + printf("VMInit event: SIG_START: %s, SIG_START_LEN: %d\n", SIG_START, (int)SIG_START_LEN); + fflush(stdout); + + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL); + CHECK_JVMTI_ERROR(jni, err, "VMInit event: Error in enabling ClassLoad events notification"); +} + +/* Check CLASS_LOAD event is generated for the given hidden class. */ +static void JNICALL +ClassLoad(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jclass klass) { + char* sig = NULL; + char* gsig = NULL; + char* src_name = NULL; + jvmtiError err; + + err = jvmti->GetClassSignature(klass, &sig, &gsig); + CHECK_JVMTI_ERROR(jni, err, "ClassLoad event: Error in JVMTI GetClassSignature"); + + if (strlen(sig) > strlen(SIG_START) && + strncmp(sig, SIG_START, SIG_START_LEN) == 0 && + is_hidden(jni, klass)) { + class_load_count++; + if (gsig == NULL) { + LOG0("ClassLoad event: FAIL: GetClassSignature returned NULL generic signature for hidden class\n"); + failed = true; + } + LOG1("ClassLoad event: hidden class with sig: %s\n", sig); + LOG1("ClassLoad event: hidden class with gsig: %s\n", gsig); + } +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jvmtiEventCallbacks callbacks; + jvmtiCapabilities caps; + jvmtiError err; + + LOG0("Agent_OnLoad: started\n"); + if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) { + LOG0("Agent_OnLoad: Error in GetEnv in obtaining jvmtiEnv*\n"); + failed = true; + return JNI_ERR; + } + + // set required event callbacks + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.ClassLoad = &ClassLoad; + callbacks.VMInit = &VMInit; + + err = jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks)); + if (err != JVMTI_ERROR_NONE) { + LOG1("Agent_OnLoad: Error in JVMTI SetEventCallbacks: %d\n", err); + failed = true; + return JNI_ERR; + } + + // add required capabilities + memset(&caps, 0, sizeof(caps)); + caps.can_get_source_file_name = 1; + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG1("Agent_OnLoad: Error in JVMTI AddCapabilities: %d\n", err); + failed = true; + return JNI_ERR; + } + + // enable VM_INIT event notification mode + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL); + if (err != JVMTI_ERROR_NONE) { + LOG1("Agent_OnLoad: Error in JVMTI SetEventNotificationMode: %d\n", err); + failed = true; + return JNI_ERR; + } + + LOG0("Agent_OnLoad: finished\n"); + return JNI_OK; +} + +/* Native method: checkHiddenClass(). */ +JNIEXPORT void JNICALL +Java_P_Q_HiddenClassSigTest_checkHiddenClass(JNIEnv *jni, jclass klass, jclass hidden_klass, jstring exp_sig_str) { + const char* exp_sig = jni->GetStringUTFChars(exp_sig_str, NULL); + + if (exp_sig == NULL) { + jni->FatalError("check_hidden_class: Error: JNI GetStringChars returned NULL for jstring\n"); + return; + } + check_hidden_class(jvmti, jni, hidden_klass, exp_sig); + + jni->ReleaseStringUTFChars(exp_sig_str, exp_sig); +} + +/* Native method: checkHiddenClassArray(). */ +JNIEXPORT void JNICALL +Java_P_Q_HiddenClassSigTest_checkHiddenClassArray(JNIEnv *jni, jclass klass, jclass hidden_klass_array, jstring exp_sig_str) { + const char* exp_sig = jni->GetStringUTFChars(exp_sig_str, NULL); + + if (exp_sig == NULL) { + jni->FatalError("check_hidden_class_array: Error: JNI GetStringChars returned NULL for jstring\n"); + return; + } + check_hidden_class_array(jvmti, jni, hidden_klass_array, exp_sig); + + jni->ReleaseStringUTFChars(exp_sig_str, exp_sig); +} + +/* Native method: checkFailed(). */ +JNIEXPORT jboolean JNICALL +Java_P_Q_HiddenClassSigTest_checkFailed(JNIEnv *jni, jclass klass) { + if (class_load_count == 0) { + LOG0("Native Agent: missed ClassLoad event for hidden class\n"); + failed = true; + } + return failed; +} + +} // extern "C" diff --git a/test/jdk/java/lang/instrument/HiddenClass/HiddenClassAgent.java b/test/jdk/java/lang/instrument/HiddenClass/HiddenClassAgent.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/instrument/HiddenClass/HiddenClassAgent.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020, 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 + * @library /test/lib + * @modules java.instrument + * jdk.compiler + * @build jdk.test.lib.compiler.CompilerUtils + * jdk.test.lib.Utils * + * @run shell ../MakeJAR3.sh HiddenClassAgent 'Can-Retransform-Classes: true' + * @run main/othervm/native -javaagent:HiddenClassAgent.jar HiddenClassApp + */ + +import java.lang.instrument.*; + +/* Test that Instrumentation getAllLoadedClasses includes + * hidden classes but getInitiatedClasses does not. + * Check that all hidden classes are non-retransformable. + */ +public class HiddenClassAgent extends Thread { + private static volatile boolean completed = false; + private static volatile boolean failed = false; + private static volatile boolean hiddenClassLoaded = false; + + private static Instrumentation instr = null; + private static Object monitor = new Object(); + + static void log(String str) { System.err.println(str); } + public static boolean failed() { return failed; } + + public static + boolean checkWaitForCompleteness() { + try { + synchronized (monitor) { + while (!completed) { + monitor.wait(100); + } + } + } catch (InterruptedException ex) { + ex.printStackTrace(); + log("HiddenClassAgent: waitCheckForCompletness: Caught InterruptedException: " + ex); + } + return completed; + } + + public static + void setHiddenClassLoaded() { + synchronized (monitor) { + hiddenClassLoaded = true; + monitor.notifyAll(); + } + } + + private static + void waitForHiddenClassLoad() { + try { + synchronized (monitor) { + while (!hiddenClassLoaded) { + monitor.wait(100); + } + } + } catch (InterruptedException ex) { + ex.printStackTrace(); + log("HiddenClassAgent: waitForHiddenClassLoad: Caught InterruptedException: " + ex); + } + } + + private static + ClassLoader testGetAllLoadedClasses() { + boolean hiddenClassFound = false; + ClassLoader loader = null; + Class[] classes = instr.getAllLoadedClasses(); + + for (int i = 0; i < classes.length; i++) { + Class klass = classes[i]; + + if (!klass.isHidden() || !klass.getName().contains("HiddenClass/")) { + continue; + } + log("HiddenClassAgent: getAllLoadedClasses returned hidden class: " + klass); + hiddenClassFound = true; + loader = klass.getClassLoader(); + log("HiddenClassAgent: class loader of hidden class: " + loader); + + try { + instr.retransformClasses(klass); + log("HiddenClassAgent: FAIL: hidden class is retransformable: " + klass); + failed = true; + } catch (UnmodifiableClassException e) { + log("HiddenClassAgent: Got expected UnmodifiableClassException for class: " + klass); + } catch (Throwable e) { + log("HiddenClassAgent: FAIL: unexpected throwable in hidden class retransform: " + klass); + log("HiddenClassAgent: got Throwable" + e); + failed = true; + } + } + if (!hiddenClassFound) { + log("HiddenClassAgent: FAIL: a hidden class is not found in getAllLoadedClasses list"); + failed = true; + } + return loader; + } + + private static + void testGetInitiatedClasses(ClassLoader loader) { + Class[] classes = instr.getInitiatedClasses(loader); + for (int i = 0; i < classes.length; i++) { + Class klass = classes[i]; + + if (klass.isHidden()) { + log("HiddenClassAgent: FAIL: getInitiatedClasses returned hidden class: " + klass); + failed = true; + return; + } + } + log("HiddenClassAgent: getInitiatedClasses returned no hidden classes as expected"); + } + + public static void + premain(String agentArgs, Instrumentation instrumentation) { + instr = instrumentation; + Thread agentThread = new HiddenClassAgent(); + agentThread.start(); + } + + public void run () { + log("HiddenClassAgent: started"); + waitForHiddenClassLoad(); + + // Test getAllLoadedClasses + ClassLoader loader = testGetAllLoadedClasses(); + + // Test getInitiatedClasses + testGetInitiatedClasses(null); + if (loader != null) { + testGetInitiatedClasses(loader); + } + + synchronized (monitor) { + completed = true; + monitor.notifyAll(); + } + log("HiddenClassAgent: finished"); + } +} diff --git a/test/jdk/java/lang/instrument/HiddenClass/HiddenClassApp.java b/test/jdk/java/lang/instrument/HiddenClass/HiddenClassApp.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/instrument/HiddenClass/HiddenClassApp.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, 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. + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import jdk.test.lib.compiler.CompilerUtils; +import jdk.test.lib.Utils; + + +interface Test { + void test(); +} + +public class HiddenClassApp { + static void log(String str) { System.out.println(str); } + + static final String HCName = "HiddenClass.java"; + static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "hidden"); + static final Path CLASSES_DIR = Paths.get(Utils.TEST_CLASSES, "hidden"); + + static void compileSources(String sourceFile) throws Throwable { + boolean ok = CompilerUtils.compile(SRC_DIR.resolve(sourceFile), CLASSES_DIR, + "-cp", Utils.TEST_CLASSES.toString()); + if (!ok){ + throw new RuntimeException("HiddenClassApp: Compilation of the test failed. "); + } + } + + static byte[] readClassFile(String classFileName) throws Exception { + File classFile = new File(CLASSES_DIR + File.separator + classFileName); + try (FileInputStream in = new FileInputStream(classFile); + ByteArrayOutputStream out = new ByteArrayOutputStream()) + { + int b; + while ((b = in.read()) != -1) { + out.write(b); + } + return out.toByteArray(); + } + } + + static Class defineHiddenClass(String name) throws Exception { + Lookup lookup = MethodHandles.lookup(); + byte[] bytes = readClassFile(name + ".class"); + Class hc = lookup.defineHiddenClass(bytes, false).lookupClass(); + return hc; + } + + public static void main(String args[]) throws Exception { + log("HiddenClassApp: started"); + try { + compileSources(HCName); + log("HiddenClassApp: compiled " + HCName); + } catch (Throwable t) { + t.printStackTrace(); + throw new Exception("HiddenClassApp: Failed to compile " + HCName); + } + + Class c = defineHiddenClass("HiddenClass"); + log("HiddenClassApp: Defined HiddenClass with name: " + c.getName()); + HiddenClassAgent.setHiddenClassLoaded(); + + Test t = (Test) c.newInstance(); + t.test(); + log("HiddenClassApp: Tested HiddenClass"); + + if (!HiddenClassAgent.checkWaitForCompleteness()) { + throw new Exception("HiddenClassApp: FAIL: HiddenClassAgent did not complete"); + } + if (HiddenClassAgent.failed()) { + throw new Exception("HiddenClassApp: FAIL: HiddenClassAgent failed"); + } + log("HiddenClassApp: finished"); + } +} diff --git a/test/jdk/java/lang/instrument/HiddenClass/hidden/HiddenClass.java b/test/jdk/java/lang/instrument/HiddenClass/hidden/HiddenClass.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/instrument/HiddenClass/hidden/HiddenClass.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020, 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. + */ + +public class HiddenClass implements Test { + private String realTest() { + return this.getClass().getName(); + } + + public void test() { + String name = realTest(); + if (name.indexOf("HiddenClass") == -1) { + throw new RuntimeException("'HiddenClass string is bad: " + name); + } + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2019, 2020, 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 + * @modules java.base/jdk.internal.org.objectweb.asm + * jdk.compiler + * @library /test/lib + * @build jdk.test.lib.Utils + * jdk.test.lib.compiler.CompilerUtils + * @run testng/othervm --enable-preview BasicTest + */ + +import java.io.File; +import java.io.IOException; +import java.lang.invoke.MethodHandles.Lookup; + +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.Type; +import jdk.test.lib.compiler.CompilerUtils; +import jdk.test.lib.Utils; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static org.testng.Assert.*; + +interface HiddenTest { + void test(); +} + +public class BasicTest { + + private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src"); + private static final Path CLASSES_DIR = Paths.get("classes"); + private static final Path CLASSES_10_DIR = Paths.get("classes_10"); + + private static byte[] hiddenClassBytes; + + @BeforeTest + static void setup() throws IOException { + compileSources(SRC_DIR, CLASSES_DIR, + "--enable-preview", "-source", String.valueOf(Runtime.version().feature())); + hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenClass.class")); + + // compile with --release 10 with no NestHost and NestMembers attribute + compileSources(SRC_DIR.resolve("Outer.java"), CLASSES_10_DIR, "--release", "10"); + compileSources(SRC_DIR.resolve("EnclosingClass.java"), CLASSES_10_DIR, "--release", "10"); + } + + static void compileSources(Path sourceFile, Path dest, String... options) throws IOException { + Stream ops = Stream.of("-cp", Utils.TEST_CLASSES + File.pathSeparator + CLASSES_DIR); + if (options != null && options.length > 0) { + ops = Stream.concat(ops, Arrays.stream(options)); + } + if (!CompilerUtils.compile(sourceFile, dest, ops.toArray(String[]::new))) { + throw new RuntimeException("Compilation of the test failed: " + sourceFile); + } + } + + static Class defineHiddenClass(String name) throws Exception { + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); + Class hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + assertHiddenClass(hc); + singletonNest(hc); + return hc; + } + + // basic test on a hidden class + @Test + public void hiddenClass() throws Throwable { + HiddenTest t = (HiddenTest)defineHiddenClass("HiddenClass").newInstance(); + t.test(); + + // sanity check + Class c = t.getClass(); + Class[] intfs = c.getInterfaces(); + assertTrue(c.isHidden()); + assertFalse(c.isPrimitive()); + assertTrue(intfs.length == 1); + assertTrue(intfs[0] == HiddenTest.class); + assertTrue(c.getCanonicalName() == null); + assertTrue(c.getName().startsWith("HiddenClass/")); + + // test array of hidden class + testHiddenArray(c); + + // test setAccessible + checkSetAccessible(c, "realTest"); + checkSetAccessible(c, "test"); + } + + // primitive class is not a hidden class + @Test + public void primitiveClass() { + assertFalse(int.class.isHidden()); + assertFalse(String.class.isHidden()); + } + + private void testHiddenArray(Class type) throws Exception { + // array of hidden class + Object array = Array.newInstance(type, 2); + Class arrayType = array.getClass(); + assertTrue(arrayType.isArray()); + assertTrue(Array.getLength(array) == 2); + assertFalse(arrayType.isHidden()); + assertTrue(arrayType.getName().startsWith("[LHiddenClass/"), "unexpected name: " + arrayType.getName()); + + assertTrue(arrayType.getComponentType().isHidden()); + assertTrue(arrayType.getComponentType() == type); + Object t = type.newInstance(); + Array.set(array, 0, t); + Object o = Array.get(array, 0); + assertTrue(o == t); + } + + private void checkSetAccessible(Class c, String name, Class... ptypes) throws Exception { + Method m = c.getDeclaredMethod(name, ptypes); + assertTrue(m.trySetAccessible()); + m.setAccessible(true); + } + + // Define a hidden class that uses lambda + // This verifies LambdaMetaFactory supports the caller which is a hidden class + @Test + public void testLambda() throws Throwable { + HiddenTest t = (HiddenTest)defineHiddenClass("Lambda").newInstance(); + try { + t.test(); + } catch (Error e) { + if (!e.getMessage().equals("thrown by " + t.getClass().getName())) { + throw e; + } + } + } + + // Verify the nest host and nest members of a hidden class and hidden nestmate class + @Test + public void testHiddenNestHost() throws Throwable { + byte[] hc1 = hiddenClassBytes; + Lookup lookup1 = lookup().defineHiddenClass(hc1, false); + Class host = lookup1.lookupClass(); + + byte[] hc2 = Files.readAllBytes(CLASSES_DIR.resolve("Lambda.class")); + Lookup lookup2 = lookup1.defineHiddenClass(hc2, false, NESTMATE); + Class member = lookup2.lookupClass(); + + // test nest membership and reflection API + assertTrue(host.isNestmateOf(member)); + assertTrue(host.getNestHost() == host); + // getNestHost and getNestMembers return the same value when calling + // on a nest member and the nest host + assertTrue(member.getNestHost() == host.getNestHost()); + assertTrue(Arrays.equals(member.getNestMembers(), host.getNestMembers())); + // getNestMembers includes the nest host that can be a hidden class but + // only includes static nest members + assertTrue(host.getNestMembers().length == 1); + assertTrue(host.getNestMembers()[0] == host); + } + + @DataProvider(name = "hiddenClasses") + private Object[][] hiddenClasses() { + return new Object[][] { + new Object[] { "HiddenInterface", false }, + new Object[] { "AbstractClass", false }, + // a hidden annotation is useless because it cannot be referenced by any class + new Object[] { "HiddenAnnotation", false }, + // class file with bad NestHost, NestMembers and InnerClasses or EnclosingMethod attribute + // define them as nestmate to verify Class::getNestHost and getNestMembers + new Object[] { "Outer", true }, + new Object[] { "Outer$Inner", true }, + new Object[] { "EnclosingClass", true }, + new Object[] { "EnclosingClass$1", true }, + }; + } + + /* + * Test that class file bytes that can be defined as a normal class + * can be successfully created as a hidden class even it might not + * make sense as a hidden class. For example, a hidden annotation + * is not useful as it cannot be referenced and an outer/inner class + * when defined as a hidden effectively becomes a final top-level class. + */ + @Test(dataProvider = "hiddenClasses") + public void defineHiddenClass(String name, boolean nestmate) throws Exception { + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); + Class hc; + Class host; + if (nestmate) { + hc = lookup().defineHiddenClass(bytes, false, NESTMATE).lookupClass(); + host = lookup().lookupClass().getNestHost(); + } else { + hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + host = hc; + } + assertTrue(hc.getNestHost() == host); + assertTrue(hc.getNestMembers().length == 1); + assertTrue(hc.getNestMembers()[0] == host); + } + + @DataProvider(name = "emptyClasses") + private Object[][] emptyClasses() { + return new Object[][] { + new Object[] { "EmptyHiddenSynthetic", ACC_SYNTHETIC }, + new Object[] { "EmptyHiddenEnum", ACC_ENUM }, + new Object[] { "EmptyHiddenAbstractClass", ACC_ABSTRACT }, + new Object[] { "EmptyHiddenInterface", ACC_ABSTRACT|ACC_INTERFACE }, + new Object[] { "EmptyHiddenAnnotation", ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE }, + }; + } + + /* + * Test if an empty class with valid access flags can be created as a hidden class + * as long as it does not violate the restriction of a hidden class. + * + * A meaningful enum type defines constants of that enum type. So + * enum class containing constants of its type should not be a hidden + * class. + */ + @Test(dataProvider = "emptyClasses") + public void emptyHiddenClass(String name, int accessFlags) throws Exception { + byte[] bytes = (accessFlags == ACC_ENUM) ? classBytes(name, Enum.class, accessFlags) + : classBytes(name, accessFlags); + Class hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + switch (accessFlags) { + case ACC_SYNTHETIC: + assertTrue(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertFalse(hc.isInterface()); + break; + case ACC_ENUM: + assertFalse(hc.isSynthetic()); + assertTrue(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertFalse(hc.isInterface()); + break; + case ACC_ABSTRACT: + assertFalse(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertFalse(hc.isInterface()); + break; + case ACC_ABSTRACT|ACC_INTERFACE: + assertFalse(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertTrue(hc.isInterface()); + break; + case ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE: + assertFalse(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertTrue(hc.isAnnotation()); + assertTrue(hc.isInterface()); + break; + default: + throw new IllegalArgumentException("unexpected access flag: " + accessFlags); + } + assertTrue(hc.isHidden()); + assertTrue(hc.getModifiers() == (ACC_PUBLIC|accessFlags)); + assertFalse(hc.isLocalClass()); + assertFalse(hc.isMemberClass()); + assertFalse(hc.isAnonymousClass()); + assertFalse(hc.isArray()); + } + + // These class files can't be defined as hidden classes + @DataProvider(name = "cantBeHiddenClasses") + private Object[][] cantBeHiddenClasses() { + return new Object[][] { + // a hidden class can't be a field's declaring type + // enum class with static final HiddenEnum[] $VALUES: + new Object[] { "HiddenEnum" }, + // supertype of this class is a hidden class + new Object[] { "HiddenSuper" }, + // a record class whose equals(HiddenRecord, Object) method + // refers to a hidden class in the parameter type and fails + // verification. Perhaps this method signature should be reconsidered. + new Object[] { "HiddenRecord" }, + }; + } + + /* + * These class files + */ + @Test(dataProvider = "cantBeHiddenClasses", expectedExceptions = NoClassDefFoundError.class) + public void failToDeriveAsHiddenClass(String name) throws Exception { + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); + Class hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + } + + /* + * A hidden class can be successfully created but fails to be reflected + * if it refers to its own type in the descriptor. + * e.g. Class::getMethods resolves the declaring type of fields, + * parameter types and return type. + */ + @Test + public void hiddenCantReflect() throws Throwable { + HiddenTest t = (HiddenTest)defineHiddenClass("HiddenCantReflect").newInstance(); + t.test(); + + Class c = t.getClass(); + Class[] intfs = c.getInterfaces(); + assertTrue(intfs.length == 1); + assertTrue(intfs[0] == HiddenTest.class); + + try { + // this would cause loading of class HiddenCantReflect and NCDFE due + // to error during verification + c.getDeclaredMethods(); + } catch (NoClassDefFoundError e) { + Throwable x = e.getCause(); + if (x == null || !(x instanceof ClassNotFoundException && x.getMessage().contains("HiddenCantReflect"))) { + throw e; + } + } + } + + @Test(expectedExceptions = {IllegalArgumentException.class}) + public void cantDefineModule() throws Throwable { + Path src = Paths.get("module-info.java"); + Path dir = CLASSES_DIR.resolve("m"); + Files.write(src, List.of("module m {}"), StandardCharsets.UTF_8); + compileSources(src, dir); + + byte[] bytes = Files.readAllBytes(dir.resolve("module-info.class")); + lookup().defineHiddenClass(bytes, false); + } + + @Test(expectedExceptions = {IllegalArgumentException.class}) + public void cantDefineClassInAnotherPackage() throws Throwable { + Path src = Paths.get("ClassInAnotherPackage.java"); + Files.write(src, List.of("package p;", "public class ClassInAnotherPackage {}"), StandardCharsets.UTF_8); + compileSources(src, CLASSES_DIR); + + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("p").resolve("ClassInAnotherPackage.class")); + lookup().defineHiddenClass(bytes, false); + } + + @Test(expectedExceptions = {IllegalAccessException.class}) + public void lessPrivilegedLookup() throws Throwable { + Lookup lookup = lookup().dropLookupMode(Lookup.PRIVATE); + lookup.defineHiddenClass(hiddenClassBytes, false); + } + + @DataProvider(name = "nestedTypesOrAnonymousClass") + private Object[][] nestedTypesOrAnonymousClass() { + return new Object[][] { + // class file with bad InnerClasses or EnclosingMethod attribute + new Object[] { "Outer", null }, + new Object[] { "Outer$Inner", "Outer" }, + new Object[] { "EnclosingClass", null }, + new Object[] { "EnclosingClass$1", "EnclosingClass" }, + }; + } + + @Test(dataProvider = "nestedTypesOrAnonymousClass") + public void hasInnerClassesOrEnclosingMethodAttribute(String className, String badDeclaringClassName) throws Throwable { + byte[] bytes = Files.readAllBytes(CLASSES_10_DIR.resolve(className + ".class")); + Class hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + hiddenClassWithBadAttribute(hc, badDeclaringClassName); + } + + // define a hidden class with static nest membership + @Test + public void hasStaticNestHost() throws Exception { + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer$Inner.class")); + Class hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + hiddenClassWithBadAttribute(hc, "Outer"); + } + + @Test + public void hasStaticNestMembers() throws Throwable { + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer.class")); + Class hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + assertHiddenClass(hc); + assertTrue(hc.getNestHost() == hc); + Class[] members = hc.getNestMembers(); + assertTrue(members.length == 1 && members[0] == hc); + } + + // a hidden class with bad InnerClasses or EnclosingMethod attribute + private void hiddenClassWithBadAttribute(Class hc, String badDeclaringClassName) { + assertTrue(hc.isHidden()); + assertTrue(hc.getCanonicalName() == null); + assertTrue(hc.getName().contains("/")); + + if (badDeclaringClassName == null) { + // the following reflection API assumes a good name in InnerClasses + // or EnclosingMethod attribute can successfully be resolved. + assertTrue(hc.getSimpleName().length() > 0); + assertFalse(hc.isAnonymousClass()); + assertFalse(hc.isLocalClass()); + assertFalse(hc.isMemberClass()); + } else { + declaringClassNotFound(hc, badDeclaringClassName); + } + + // validation of nest membership + assertTrue(hc.getNestHost() == hc); + // validate the static nest membership + Class[] members = hc.getNestMembers(); + assertTrue(members.length == 1 && members[0] == hc); + } + + // Class::getSimpleName, Class::isMemberClass + private void declaringClassNotFound(Class c, String cn) { + try { + // fail to find declaring/enclosing class + c.isMemberClass(); + assertTrue(false); + } catch (NoClassDefFoundError e) { + if (!e.getMessage().equals(cn)) { + throw e; + } + } + try { + // fail to find declaring/enclosing class + c.getSimpleName(); + assertTrue(false); + } catch (NoClassDefFoundError e) { + if (!e.getMessage().equals(cn)) { + throw e; + } + } + } + + private static void singletonNest(Class hc) { + assertTrue(hc.getNestHost() == hc); + assertTrue(hc.getNestMembers().length == 1); + assertTrue(hc.getNestMembers()[0] == hc); + } + + private static void assertHiddenClass(Class hc) { + assertTrue(hc.isHidden()); + assertTrue(hc.getCanonicalName() == null); + assertTrue(hc.getName().contains("/")); + assertFalse(hc.isAnonymousClass()); + assertFalse(hc.isLocalClass()); + assertFalse(hc.isMemberClass()); + assertFalse(hc.getSimpleName().isEmpty()); // sanity check + } + + private static byte[] classBytes(String classname, int accessFlags) { + return classBytes(classname, Object.class, accessFlags); + } + + private static byte[] classBytes(String classname, Class supertType, int accessFlags) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + cw.visit(V14, ACC_PUBLIC|accessFlags, classname, null, Type.getInternalName(supertType), null); + cw.visitEnd(); + + return cw.toByteArray(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/HiddenNestmateTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/HiddenNestmateTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/HiddenNestmateTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @library /test/lib + * @modules java.base/jdk.internal.org.objectweb.asm + * @build HiddenNestmateTest + * @run testng/othervm HiddenNestmateTest + */ + +import java.lang.invoke.*; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.stream.Stream; +import java.util.Arrays; + +import jdk.internal.org.objectweb.asm.*; +import org.testng.annotations.Test; + +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import static java.lang.invoke.MethodHandles.Lookup.*; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static org.testng.Assert.*; + +public class HiddenNestmateTest { + private static final byte[] bytes = classBytes("HiddenInjected"); + + private static void assertNestmate(Lookup lookup) { + assertTrue((lookup.lookupModes() & PRIVATE) != 0); + assertTrue((lookup.lookupModes() & MODULE) != 0); + + Class hiddenClass = lookup.lookupClass(); + Class nestHost = hiddenClass.getNestHost(); + assertTrue(hiddenClass.isHidden()); + assertTrue(nestHost == MethodHandles.lookup().lookupClass()); + + // hidden nestmate is not listed in the return array of getNestMembers + assertTrue(Stream.of(nestHost.getNestMembers()).noneMatch(k -> k == hiddenClass)); + assertTrue(hiddenClass.isNestmateOf(lookup.lookupClass())); + assertTrue(Arrays.equals(hiddenClass.getNestMembers(), nestHost.getNestMembers())); + } + + /* + * Test a hidden class to have no access to private members of another class + */ + @Test + public void hiddenClass() throws Throwable { + // define a hidden class + Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false); + Class c = lookup.lookupClass(); + assertTrue(lookup.hasFullPrivilegeAccess()); + assertTrue(c.getNestHost() == c); // host of its own nest + assertTrue(c.isHidden()); + + // invoke int test(HiddenNestmateTest o) via MethodHandle + MethodHandle ctor = lookup.findConstructor(c, MethodType.methodType(void.class)); + MethodHandle mh = lookup.findVirtual(c, "test", MethodType.methodType(int.class, HiddenNestmateTest.class)); + try { + int x = (int) mh.bindTo(ctor.invoke()).invokeExact(this); + throw new RuntimeException("should fail when accessing HiddenNestmateTest.privMethod()"); + } catch (IllegalAccessError e) {} + + // invoke int test(HiddenNestmateTest o) + try { + int x1 = testInjectedClass(c); + throw new RuntimeException("should fail when accessing HiddenNestmateTest.privMethod()"); + } catch (IllegalAccessError e) {} + } + + /* + * Test a hidden class to have access to private members of its nestmates + */ + @Test + public void hiddenNestmate() throws Throwable { + // define a hidden nestmate class + Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false, NESTMATE); + Class c = lookup.lookupClass(); + assertNestmate(lookup); + + // invoke int test(HiddenNestmateTest o) via MethodHandle + MethodHandle ctor = lookup.findConstructor(c, MethodType.methodType(void.class)); + MethodHandle mh = lookup.findVirtual(c, "test", MethodType.methodType(int.class, HiddenNestmateTest.class)); + int x = (int)mh.bindTo(ctor.invoke()).invokeExact( this); + assertTrue(x == privMethod()); + + // invoke int test(HiddenNestmateTest o) + int x1 = testInjectedClass(c); + assertTrue(x1 == privMethod()); + } + + /* + * Test a hidden class created with NESTMATE and STRONG option is a nestmate + */ + @Test + public void hiddenStrongClass() throws Throwable { + // define a hidden class strongly referenced by the class loader + Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false, NESTMATE, STRONG); + assertNestmate(lookup); + } + + /* + * Fail to create a hidden class if dropping PRIVATE lookup mode + */ + @Test(expectedExceptions = IllegalAccessException.class) + public void noPrivateLookupAccess() throws Throwable { + Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE); + lookup.defineHiddenClass(bytes, false, NESTMATE); + } + + public void teleportToNestmate() throws Throwable { + Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false, NESTMATE); + assertNestmate(lookup); + + // Teleport to a hidden nestmate + Lookup lc = MethodHandles.lookup().in(lookup.lookupClass()); + assertTrue((lc.lookupModes() & PRIVATE) != 0); + Lookup lc2 = lc.defineHiddenClass(bytes, false, NESTMATE); + assertNestmate(lc2); + } + + /* + * Fail to create a hidden class in a different package from the lookup class' package + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void notSamePackage() throws Throwable { + MethodHandles.lookup().defineHiddenClass(classBytes("p/HiddenInjected"), false, NESTMATE); + } + + /* + * invoke int test(HiddenNestmateTest o) method defined in the injected class + */ + private int testInjectedClass(Class c) throws Throwable { + try { + Method m = c.getMethod("test", HiddenNestmateTest.class); + return (int) m.invoke(c.newInstance(), this); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + private static byte[] classBytes(String classname) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + MethodVisitor mv; + + cw.visit(V12, ACC_FINAL, classname, null, "java/lang/Object", null); + + { + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + { + // access a private member of the nest host class + mv = cw.visitMethod(ACC_PUBLIC, "test", "(LHiddenNestmateTest;)I", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "HiddenNestmateTest", "privMethod", "()I"); + mv.visitInsn(IRETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + cw.visitEnd(); + + return cw.toByteArray(); + } + + private int privMethod() { return 1234; } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/LambdaNestedInnerTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/LambdaNestedInnerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/LambdaNestedInnerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019, 2020, 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 + * @summary define a lambda proxy class whose target class has an invalid + * nest membership + * @run testng/othervm p.LambdaNestedInnerTest + */ + +package p; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.testng.Assert.*; + +public class LambdaNestedInnerTest { + private static final String INNER_CLASSNAME = "p.LambdaNestedInnerTest$Inner"; + private static final String DIR = "missingOuter"; + public static class Inner implements Runnable { + // generate lambda proxy class + private Runnable lambda1 = this::doit; + + @Override + public void run() { + // validate the lambda proxy class + Class lambdaProxyClass = lambda1.getClass(); + assertTrue(lambdaProxyClass.isHidden()); + System.out.format("%s nest host %s nestmate of Inner class %s%n", + lambdaProxyClass, lambdaProxyClass.getNestHost(), + lambdaProxyClass.isNestmateOf(Inner.class)); + assertTrue(lambdaProxyClass.getNestHost() == Inner.class.getNestHost()); + assertTrue(Arrays.equals(lambdaProxyClass.getNestMembers(), Inner.class.getNestMembers())); + assertTrue(lambdaProxyClass.isNestmateOf(Inner.class)); + lambda1.run(); + } + + // testng may not be visible to this class + private static void assertTrue(boolean x) { + if (!x) { + throw new AssertionError("expected true but found false"); + } + } + + private void doit() { + } + } + + @BeforeTest + public void setup() throws IOException { + String filename = INNER_CLASSNAME.replace('.', File.separatorChar) + ".class"; + Path src = Paths.get(System.getProperty("test.classes"), filename); + Path dest = Paths.get(DIR, filename); + Files.createDirectories(dest.getParent()); + Files.copy(src, dest, REPLACE_EXISTING); + } + + @Test + public void test() throws Exception { + Class inner = Class.forName(INNER_CLASSNAME); + // inner class is a nest member of LambdaNestedInnerTest + Class nestHost = inner.getNestHost(); + assertTrue(nestHost == LambdaNestedInnerTest.class); + Set> members = Arrays.stream(nestHost.getNestMembers()).collect(Collectors.toSet()); + assertEquals(members, Set.of(nestHost, inner, TestLoader.class)); + + // spin lambda proxy hidden class + Runnable runnable = (Runnable) inner.newInstance(); + runnable.run(); + } + + @Test + public void nestHostNotExist() throws Exception { + URL[] urls = new URL[] { Paths.get(DIR).toUri().toURL() }; + URLClassLoader loader = new URLClassLoader(urls, null); + Class inner = loader.loadClass(INNER_CLASSNAME); + assertTrue(inner.getClassLoader() == loader); + assertTrue(inner.getNestHost() == inner); // linkage error ignored + + Runnable runnable = (Runnable) inner.newInstance(); + // this validates the lambda proxy class + runnable.run(); + } + + /* + * Tests IncompatibleClassChangeError thrown since the true nest host is not + * in the same runtime package as the hidden class + */ + @Test + public void nestHostNotSamePackage() throws Exception { + URL[] urls = new URL[] { Paths.get(DIR).toUri().toURL() }; + TestLoader loader = new TestLoader(urls); + + Class inner = loader.loadClass(INNER_CLASSNAME); + assertTrue(inner.getClassLoader() == loader); + assertTrue(inner.getNestHost() == inner); // linkage error ignored. + + Runnable runnable = (Runnable) inner.newInstance(); + // this validates the lambda proxy class + runnable.run(); + } + + static class TestLoader extends URLClassLoader { + TestLoader(URL[] urls) { + super(urls, TestLoader.class.getClassLoader()); + } + public Class loadClass(String name) throws ClassNotFoundException { + if (INNER_CLASSNAME.equals(name)) { + return findClass(name); + } else { + // delegate to its parent + return loadClass(name, false); + } + } + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/SelfRefField.java b/test/jdk/java/lang/invoke/defineHiddenClass/SelfRefField.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/SelfRefField.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * The classfile for this class will be used to define a hidden class + * The load of this class will fail because hidden classes cannot + * use their name in field signatures. + */ +public class SelfRefField implements Test { + + SelfRefField other = null; + + private void realTest() { + other = this; // field signature test + } + + public void test() { + realTest(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/SelfRefMethod.java b/test/jdk/java/lang/invoke/defineHiddenClass/SelfRefMethod.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/SelfRefMethod.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * The classfile for this class will be used to define a hidden class + * The load of this class will fail because hidden classes cannot + * use their names in method signatures. + */ +public class SelfRefMethod implements Test { + + private void realTest() { + SelfRefMethod local = this; + set_other(local); // method signature test + } + + private void set_other(SelfRefMethod t) { + } + + public void test() { + realTest(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/SelfReferenceDescriptor.java b/test/jdk/java/lang/invoke/defineHiddenClass/SelfReferenceDescriptor.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/SelfReferenceDescriptor.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019, 2020, 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 + * @modules jdk.compiler + * @library /test/lib + * @build jdk.test.lib.Utils + * jdk.test.lib.compiler.CompilerUtils + * SelfReferenceDescriptor + * @run main/othervm -Xverify:remote SelfReferenceDescriptor + * @summary Test that a hidden class cannot be referenced in descriptor + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import static java.lang.invoke.MethodHandles.lookup; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.stream.Stream; + +import jdk.test.lib.compiler.CompilerUtils; + +import jdk.test.lib.Utils; + +/* package-private */ interface Test { + void test(); +} + +public class SelfReferenceDescriptor { + + private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC); + private static final Path CLASSES_DIR = Paths.get("classes"); + + static void compileSources(Path sourceFile, String... options) throws IOException { + Stream ops = Stream.of("-cp", Utils.TEST_CLASSES + File.pathSeparator + CLASSES_DIR); + if (options != null && options.length > 0) { + ops = Stream.concat(ops, Arrays.stream(options)); + } + if (!CompilerUtils.compile(sourceFile, CLASSES_DIR, ops.toArray(String[]::new))) { + throw new RuntimeException("Compilation of the test failed: " + sourceFile); + } + } + + // Test that a hidden class cannot use its own name in a field + // signature. + public static void hiddenClassInFieldDescriptor() throws Exception { + compileSources(SRC_DIR.resolve("SelfRefField.java")); + Path path = CLASSES_DIR.resolve("SelfRefField.class"); + byte[] bytes = Files.readAllBytes(path); + try { + lookup().defineHiddenClass(bytes, false, NESTMATE); + throw new RuntimeException("expected NCDFE in defining SelfRefField hidden class"); + } catch (NoClassDefFoundError e) { + if (!e.getMessage().contains("SelfRefField")) throw e; + } + } + + // Test that a hidden class cannot use its own name in a method + // signature. + public static void hiddenClassInMethodDescriptor() throws Exception { + compileSources(SRC_DIR.resolve("SelfRefMethod.java")); + Path path = CLASSES_DIR.resolve("SelfRefMethod.class"); + byte[] bytes = Files.readAllBytes(path); + try { + lookup().defineHiddenClass(bytes, false, NESTMATE); + throw new RuntimeException("expected NCDFE in defining SelfRefMethod hidden class"); + } catch (NoClassDefFoundError e) { + if (!e.getMessage().contains("SelfRefMethod")) throw e; + } + } + + public static void main(String... args) throws Exception { + hiddenClassInMethodDescriptor(); + hiddenClassInFieldDescriptor(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/UnloadingTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/UnloadingTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/UnloadingTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2020, 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 + * @summary verify if the hidden class is unloaded when the class loader is GC'ed + * @modules jdk.compiler + * @library /test/lib/ + * @build jdk.test.lib.util.ForceGC + * @run testng/othervm UnloadingTest + */ + +import java.io.IOException; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.test.lib.util.ForceGC; + +import jdk.test.lib.compiler.CompilerUtils; +import jdk.test.lib.Utils; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; +import static org.testng.Assert.*; + +public class UnloadingTest { + private static final Path CLASSES_DIR = Paths.get("classes"); + private static byte[] hiddenClassBytes; + + @BeforeTest + static void setup() throws IOException { + Path src = Paths.get(Utils.TEST_SRC, "src", "LookupHelper.java"); + if (!CompilerUtils.compile(src, CLASSES_DIR)) { + throw new RuntimeException("Compilation of the test failed: " + src); + } + + hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("LookupHelper.class")); + } + + /* + * Test that a hidden class is unloaded while the loader remains strongly reachable + */ + @Test + public void unloadable() throws Exception { + TestLoader loader = new TestLoader(); + Class helper = Class.forName("LookupHelper", true, loader); + Method m = helper.getMethod("getLookup"); + Lookup lookup = (Lookup)m.invoke(null); + HiddenClassUnloader unloader = createHiddenClass(lookup, false); + // the hidden class should be unloaded + unloader.unload(); + + // loader is strongly reachable + Reference.reachabilityFence(loader); + } + + /* + * Test that a hidden class is not unloaded when the loader is strongly reachable + */ + @Test + public void notUnloadable() throws Exception { + TestLoader loader = new TestLoader(); + Class helper = Class.forName("LookupHelper", true, loader); + Method m = helper.getMethod("getLookup"); + Lookup lookup = (Lookup)m.invoke(null); + HiddenClassUnloader unloader = createHiddenClass(lookup, true); + assertFalse(unloader.tryUnload()); // hidden class is not unloaded + + // loader is strongly reachable + Reference.reachabilityFence(loader); + } + + /* + * Create a nest of two hidden classes. + * They can be unloaded even the loader is strongly reachable + */ + @Test + public void hiddenClassNest() throws Exception { + TestLoader loader = new TestLoader(); + Class helper = Class.forName("LookupHelper", true, loader); + Method m = helper.getMethod("getLookup"); + Lookup lookup = (Lookup)m.invoke(null); + HiddenClassUnloader[] unloaders = createNestOfTwoHiddenClasses(lookup, false, false); + + // keep a strong reference to the nest member class + Class member = unloaders[1].weakRef.get(); + assertTrue(member != null); + // nest host and member will not be unloaded + assertFalse(unloaders[0].tryUnload()); + assertFalse(unloaders[1].tryUnload()); + + // clear the reference to the nest member + Reference.reachabilityFence(member); + member = null; + + // nest host and member will be unloaded + unloaders[0].unload(); + unloaders[1].unload(); + + // loader is strongly reachable + Reference.reachabilityFence(loader); + } + + /* + * Create a nest with a hidden class nest host and strong nest member. + * Test that both are not unloaded + */ + @Test + public void hiddenClassNestStrongMember() throws Exception { + TestLoader loader = new TestLoader(); + Class helper = Class.forName("LookupHelper", true, loader); + Method m = helper.getMethod("getLookup"); + Lookup lookup = (Lookup)m.invoke(null); + HiddenClassUnloader[] unloaders = createNestOfTwoHiddenClasses(lookup, false, true); + assertFalse(unloaders[0].tryUnload()); // nest host cannot be unloaded + assertFalse(unloaders[1].tryUnload()); // nest member cannot be unloaded + + // loader is strongly reachable + Reference.reachabilityFence(loader); + } + + /* + * Create a nest with a strong hidden nest host and a hidden class member. + * The nest member can be unloaded whereas the nest host will not be unloaded. + */ + @Test + public void hiddenClassNestStrongHost() throws Exception { + TestLoader loader = new TestLoader(); + Class helper = Class.forName("LookupHelper", true, loader); + Method m = helper.getMethod("getLookup"); + Lookup lookup = (Lookup)m.invoke(null); + HiddenClassUnloader[] unloaders = createNestOfTwoHiddenClasses(lookup, true, false); + assertFalse(unloaders[0].tryUnload()); // nest host cannot be unloaded + unloaders[1].unload(); + + // loader is strongly reachable + Reference.reachabilityFence(loader); + } + + /* + * Create a HiddenClassUnloader that holds a weak reference to the newly created + * hidden class. + */ + static HiddenClassUnloader createHiddenClass(Lookup lookup, boolean strong) throws Exception { + Class hc; + if (strong) { + hc = lookup.defineHiddenClass(hiddenClassBytes, false, STRONG).lookupClass(); + } else { + hc = lookup.defineHiddenClass(hiddenClassBytes, false).lookupClass(); + } + assertTrue(hc.getClassLoader() == lookup.lookupClass().getClassLoader()); + return new HiddenClassUnloader(hc); + } + + /* + * Create an array of HiddenClassUnloader with two elements: the first element + * is for the nest host and the second element is for the nest member. + */ + static HiddenClassUnloader[] createNestOfTwoHiddenClasses(Lookup lookup, boolean strongHost, boolean strongMember) throws Exception { + Lookup hostLookup; + if (strongHost) { + hostLookup = lookup.defineHiddenClass(hiddenClassBytes, false, STRONG); + } else { + hostLookup = lookup.defineHiddenClass(hiddenClassBytes, false); + } + Class host = hostLookup.lookupClass(); + Class member; + if (strongMember) { + member = hostLookup.defineHiddenClass(hiddenClassBytes, false, NESTMATE, STRONG).lookupClass(); + } else { + member = hostLookup.defineHiddenClass(hiddenClassBytes, false, NESTMATE).lookupClass(); + } + assertTrue(member.getNestHost() == host); + return new HiddenClassUnloader[] { new HiddenClassUnloader(host), new HiddenClassUnloader(member) }; + } + + static class HiddenClassUnloader { + private final WeakReference> weakRef; + private HiddenClassUnloader(Class hc) { + assertTrue(hc.isHidden()); + this.weakRef = new WeakReference<>(hc); + } + + void unload() { + // Force garbage collection to trigger unloading of class loader and native library + ForceGC gc = new ForceGC(); + assertTrue(gc.await(() -> weakRef.get() == null)); + + if (weakRef.get() != null) { + throw new RuntimeException("loader " + " not unloaded!"); + } + } + + boolean tryUnload() { + ForceGC gc = new ForceGC(); + return gc.await(() -> weakRef.get() == null); + } + } + + static class TestLoader extends URLClassLoader { + static URL[] toURLs() { + try { + return new URL[] { CLASSES_DIR.toUri().toURL() }; + } catch (MalformedURLException e) { + throw new Error(e); + } + } + + static AtomicInteger counter = new AtomicInteger(); + TestLoader() { + super("testloader-" + counter.addAndGet(1), toURLs(), ClassLoader.getSystemClassLoader()); + } + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/UnreflectTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/UnreflectTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/UnreflectTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020, 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 + * @compile src/Fields.java + * @run testng/othervm UnreflectTest + * @summary Test Lookup::unreflectSetter and Lookup::unreflectVarHandle + */ + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class UnreflectTest { + static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + static final Class hiddenClass = defineHiddenClass(); + private static Class defineHiddenClass() { + String classes = System.getProperty("test.classes"); + Path cf = Paths.get(classes, "Fields.class"); + try { + byte[] bytes = Files.readAllBytes(cf); + return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /* + * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that + * can write the value of a non-static final field in a normal class + */ + @Test + public void testFieldsInNormalClass() throws Throwable { + // despite the name "HiddenClass", this class is loaded by the + // class loader as non-hidden class + Class c = Fields.class; + Fields o = new Fields(); + assertFalse(c.isHidden()); + readOnlyAccessibleObject(c, "STATIC_FINAL", null, true); + readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false); + readWriteAccessibleObject(c, "FINAL", o, true); + readWriteAccessibleObject(c, "NON_FINAL", o, false); + } + + /* + * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that + * has NO write the value of a non-static final field in a hidden class + */ + @Test + public void testFieldsInHiddenClass() throws Throwable { + assertTrue(hiddenClass.isHidden()); + Object o = hiddenClass.newInstance(); + readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true); + readWriteAccessibleObject(hiddenClass, "STATIC_NON_FINAL", null, false); + readOnlyAccessibleObject(hiddenClass, "FINAL", o, true); + readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false); + } + + /* + * Verify read-only access via unreflectSetter and unreflectVarHandle + */ + private static void readOnlyAccessibleObject(Class c, String name, Object o, boolean isFinal) throws Throwable { + Field f = c.getDeclaredField(name); + int modifier = f.getModifiers(); + if (isFinal) { + assertTrue(Modifier.isFinal(modifier)); + } else { + assertFalse(Modifier.isFinal(modifier)); + } + assertTrue(f.trySetAccessible()); + + // Field object with read-only access + MethodHandle mh = LOOKUP.unreflectGetter(f); + Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o); + assertTrue(value == f.get(o)); + try { + LOOKUP.unreflectSetter(f); + assertTrue(false, "should fail to unreflect a setter for " + name); + } catch (IllegalAccessException e) { + } + + VarHandle vh = LOOKUP.unreflectVarHandle(f); + if (isFinal) { + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET)); + } else { + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET)); + } + } + + private static void readWriteAccessibleObject(Class c, String name, Object o, boolean isFinal) throws Throwable { + Field f = c.getDeclaredField(name); + int modifier = f.getModifiers(); + if (isFinal) { + assertTrue(Modifier.isFinal(modifier)); + } else { + assertFalse(Modifier.isFinal(modifier)); + } + assertTrue(f.trySetAccessible()); + + // Field object with read-write access + MethodHandle mh = MethodHandles.lookup().unreflectGetter(f); + Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o); + assertTrue(value == f.get(o)); + try { + MethodHandle setter = MethodHandles.lookup().unreflectSetter(f); + if (Modifier.isStatic(modifier)) { + setter.invokeExact(value); + } else { + setter.invoke(o, value); + } + } catch (IllegalAccessException e) { + throw e; + } + + VarHandle vh = LOOKUP.unreflectVarHandle(f); + if (isFinal) { + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET)); + } else { + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET)); + } + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/AbstractClass.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/AbstractClass.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/AbstractClass.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +public abstract class AbstractClass { + abstract void test(); +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/EnclosingClass.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/EnclosingClass.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/EnclosingClass.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +public class EnclosingClass { + public static void run() { + Runnable r = new Runnable() { + public void run() {} + }; + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/Fields.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/Fields.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/Fields.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, 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. + */ + +public class Fields { + static final Object STATIC_FINAL = new Object(); + static Object STATIC_NON_FINAL = new Object(); + final Object FINAL = new Object(); + Object NON_FINAL = new Object(); + + public String name() { + return this.getClass().getName(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenAnnotation.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenAnnotation.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenAnnotation.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020, 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. + */ + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; + +@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE}) +@Retention(RetentionPolicy.SOURCE) +public @interface HiddenAnnotation { +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenCantReflect.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenCantReflect.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenCantReflect.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * The classfile does not fail verification but would fail when + * getMethod + */ +public class HiddenCantReflect implements HiddenTest { + + HiddenCantReflect other = null; + + private String realTest() { + Object o = other; + HiddenCantReflect local = this; + local = other; + local = (HiddenCantReflect) o; + local = new HiddenCantReflect(); + + set_other(null); + + local = getThis(); + + set_other_maybe(new Object()); + set_other_maybe(this); + return "HiddenCantReflect"; + } + + private HiddenCantReflect getThis() { + return null; + } + + private void set_other(HiddenCantReflect t) { + other = t; + } + + private void set_other_maybe(Object o) { + if (o instanceof HiddenCantReflect) { + } + } + + public void test() { + String result = realTest(); + // Make sure that the Utf8 constant pool entry for "HiddenCantReflect" is okay. + if (!result.substring(0, 7).equals("HiddenC") || + !result.substring(7).equals("antReflect")) { + throw new RuntimeException("'HiddenCantReflect string is bad: " + result); + } + + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenClass.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenClass.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenClass.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * The classfile for this class will be loaded directly and used to define + * a hidden class. + */ +public class HiddenClass implements HiddenTest { + + HiddenClass other = null; + + private String realTest() { + Object o = other; + HiddenClass local = this; + local = other; + local = (HiddenClass) o; + local = new HiddenClass(); + + set_other_maybe(new Object()); + set_other_maybe(this); + return "HiddenClass"; + } + + private void set_other_maybe(Object o) { + if (o instanceof HiddenClass) { + } + } + + public void test() { + String result = realTest(); + // Make sure that the Utf8 constant pool entry for "HiddenClass" is okay. + if (!result.substring(0, 7).equals("HiddenC") || + !result.substring(7).equals("lass")) { + throw new RuntimeException("'HiddenClass string is bad: " + result); + } + + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenClassThrow.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenClassThrow.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenClassThrow.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * The classfile for this class will be loaded directly and used to define + * a hidden class. + */ +public class HiddenClassThrow implements HiddenTest { + + public void test() { + throw new RuntimeException(this.getClass().getName()); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenEnum.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenEnum.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenEnum.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020, 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. + */ + +// fail to create HiddenEnum as a hidden class as it fails verification +// initializes the static final HiddenEnum[] $VALUES field +public enum HiddenEnum { +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenInterface.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenInterface.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenInterface.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * The classfile for this class will be used to define a hidden interface. + * This class will fail to be created as a hidden class because hidden classes + * cannot user their name in field signatures. + */ +public interface HiddenInterface { + default void test() { + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenRecord.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenRecord.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenRecord.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020, 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. + */ + +// fail to create HiddenRecord as a hidden class +// verification fails in the BSM to invoke equals(HiddenRecord, Object) method +record HiddenRecord(int i) { } diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenSuper.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenSuper.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenSuper.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +public class HiddenSuper extends HiddenClass implements HiddenTest { + private void realTest() { + } + + public void test() { + realTest(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/Lambda.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/Lambda.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/Lambda.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.function.Function; + +public class Lambda implements HiddenTest { + public void test() { + Function f = Object::toString; + String s = f.apply(this); + throw new Error("thrown by " + s); + } + + public String toString() { + return getClass().getName(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/LookupHelper.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/LookupHelper.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/LookupHelper.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, 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. + */ + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +public class LookupHelper { + private Lookup lookup() { + return MethodHandles.lookup(); + } + public static Lookup getLookup() { + return (new LookupHelper()).lookup(); + } +} diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/src/Outer.java b/test/jdk/java/lang/invoke/defineHiddenClass/src/Outer.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/Outer.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +public class Outer { + public static class Inner { + } +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/InheritedProtectedMethod.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/InheritedProtectedMethod.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/InheritedProtectedMethod.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020, 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 8239384 + * @modules jdk.compiler + * @library /test/lib + * @build jdk.test.lib.Utils + * jdk.test.lib.compiler.CompilerUtils + * @run testng/othervm InheritedProtectedMethod + * @summary Test method reference to a method inherited from its + * superclass in a different package. Such method's modifier + * is changed from public to protected. + */ + +import jdk.test.lib.compiler.CompilerUtils; +import jdk.test.lib.Utils; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.testng.Assert.*; + +public class InheritedProtectedMethod { + private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src"); + private static final Path CLASSES_DIR = Paths.get("classes"); + + @BeforeTest + static void setup() throws IOException { + assertTrue(CompilerUtils.compile(SRC_DIR, CLASSES_DIR)); + + // compile the modified version of MethodSupplierOuter.java + Path file = Paths.get(Utils.TEST_SRC, "modified", "MethodSupplierOuter.java"); + assertTrue(CompilerUtils.compile(file, CLASSES_DIR)); + } + + @Test + public static void run() throws Exception { + URLClassLoader loader = new URLClassLoader("loader", new URL[]{ CLASSES_DIR.toUri().toURL()}, + ClassLoader.getPlatformClassLoader()); + Class methodInvokeClass = Class.forName("MethodInvoker", false, loader); + Method invokeMethod = methodInvokeClass.getMethod("invoke"); + + String result = (String)invokeMethod.invoke(null); + assertEquals(result, "protected inherited method"); + } +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/SuperMethodTest.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/SuperMethodTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/SuperMethodTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8227415 + * @run main p.SuperMethodTest + * @summary method reference to a protected method inherited from its + * superclass in a different package must be accessed via + * a bridge method. Lambda proxy class has no access to it. + */ + +package p; + +import q.I; +import q.J; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Function; + +public class SuperMethodTest { + public static void main(String... args) { + Sub_I sub = new Sub_I(); + sub.test(Paths.get("test")); + } + + public static class Sub_J extends J { + Sub_J(Function function) { + super(function); + } + } + + public static class Sub_I extends I { + public void test(Path path) { + /* + * The method reference to an inherited protected method + * in another package is desugared with REF_invokeVirtual on + * a bridge method to invoke protected q.I::filename method + */ + Sub_J c = new Sub_J(this::filename); + c.check(path); + } + } +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/modified/MethodSupplierOuter.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/modified/MethodSupplierOuter.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/modified/MethodSupplierOuter.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, 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 anotherpkg; + +public class MethodSupplierOuter { + // MethodSupplier is "public" for javac compilation, modified to "protected" for test. + public static class MethodSupplier { + protected String m() + { + return "protected inherited method"; + } + } +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/q/I.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/q/I.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/q/I.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package q; + +import java.nio.file.Path; + +public class I { + protected String filename(Path file) { + return file.toString(); + } +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/q/J.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/q/J.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/q/J.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package q; + +import java.nio.file.Path; +import java.util.function.Function; + +public class J { + protected final Function fileReader; + + public J(Function fileReader) { + this.fileReader = fileReader; + } + + public void check(Path file) { + fileReader.apply(file); + } +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MethodInvoker.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MethodInvoker.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MethodInvoker.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020, 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. + */ + +import anotherpkg.MethodSupplierOuter; + +public class MethodInvoker extends MethodSupplierOuter.MethodSupplier { + public static String invoke() { + MyFunctionalInterface fi = new MethodInvoker()::m; + return fi.invokeMethodReference(); + } +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MyFunctionalInterface.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MyFunctionalInterface.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/MyFunctionalInterface.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020, 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. + */ + +interface MyFunctionalInterface { + String invokeMethodReference(); +} diff --git a/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/anotherpkg/MethodSupplierOuter.java b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/anotherpkg/MethodSupplierOuter.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/superProtectedMethod/src/anotherpkg/MethodSupplierOuter.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, 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 anotherpkg; + +public class MethodSupplierOuter { + // MethodSupplier is "public" for javac compilation, modified to "protected" for test. + public static class MethodSupplier { + public String m() + { + return "public inherited method"; + } + } +} diff --git a/test/jdk/java/lang/reflect/AccessibleObject/Fields.java b/test/jdk/java/lang/reflect/AccessibleObject/Fields.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/reflect/AccessibleObject/Fields.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, 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. + */ + +public class Fields { + private static final Object STATIC_FINAL = new Object(); + private static Object STATIC_NON_FINAL = new Object(); + private final Object FINAL = new Object(); + private Object NON_FINAL = new Object(); + + public String name() { + return this.getClass().getName(); + } +} diff --git a/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java b/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020, 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 + * @build Fields HiddenClassTest + * @run testng/othervm HiddenClassTest + * @summary Test java.lang.reflect.AccessibleObject with modules + */ + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class HiddenClassTest { + static final Class hiddenClass = defineHiddenClass(); + private static Class defineHiddenClass() { + String classes = System.getProperty("test.classes"); + Path cf = Paths.get(classes, "Fields.class"); + try { + byte[] bytes = Files.readAllBytes(cf); + return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /* + * Test Field::set that can write the value of a non-static final field + * in a normal class + */ + @Test + public void testFieldsInNormalClass() throws Throwable { + // despite the name "HiddenClass", this class is loaded by the + // class loader as non-hidden class + Class c = Fields.class; + Fields o = new Fields(); + assertFalse(c.isHidden()); + readOnlyAccessibleObject(c, "STATIC_FINAL", null, true); + readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false); + readWriteAccessibleObject(c, "FINAL", o, true); + readWriteAccessibleObject(c, "NON_FINAL", o, false); + } + + /* + * Test Field::set that fails to write the value of a non-static final field + * in a hidden class + */ + @Test + public void testFieldsInHiddenClass() throws Throwable { + assertTrue(hiddenClass.isHidden()); + Object o = hiddenClass.newInstance(); + readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true); + readWriteAccessibleObject(hiddenClass, "STATIC_NON_FINAL", null, false); + readOnlyAccessibleObject(hiddenClass, "FINAL", o, true); + readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false); + } + + private static void readOnlyAccessibleObject(Class c, String name, Object o, boolean isFinal) throws Exception { + Field f = c.getDeclaredField(name); + int modifier = f.getModifiers(); + if (isFinal) { + assertTrue(Modifier.isFinal(modifier)); + } else { + assertFalse(Modifier.isFinal(modifier)); + } + assertTrue(f.trySetAccessible()); + assertTrue(f.get(o) != null); + try { + f.set(o, null); + assertTrue(false, "should fail to set " + name); + } catch (IllegalAccessException e) { + } + } + + private static void readWriteAccessibleObject(Class c, String name, Object o, boolean isFinal) throws Exception { + Field f = c.getDeclaredField(name); + int modifier = f.getModifiers(); + if (isFinal) { + assertTrue(Modifier.isFinal(modifier)); + } else { + assertFalse(Modifier.isFinal(modifier)); + } + assertTrue(f.trySetAccessible()); + assertTrue(f.get(o) != null); + try { + f.set(o, null); + } catch (IllegalAccessException e) { + throw e; + } + } +} diff --git a/test/jdk/java/lang/reflect/Nestmates/TestReflectionAPI.java b/test/jdk/java/lang/reflect/Nestmates/TestReflectionAPI.java --- a/test/jdk/java/lang/reflect/Nestmates/TestReflectionAPI.java +++ b/test/jdk/java/lang/reflect/Nestmates/TestReflectionAPI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020, 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 @@ -176,7 +176,7 @@ HostWithDuplicateMembers.Member2.class }, true); - // Hosts with "bad" members + // Hosts with only "bad" members Class[] bad = { HostOfMemberNoHost.class, HostOfMemberMissingHost.class, @@ -184,44 +184,7 @@ HostOfMemberNotInstanceHost.class, HostOfMemberMalformedHost.class, }; - Class[] exceptions = { - IncompatibleClassChangeError.class, - NoClassDefFoundError.class, - IncompatibleClassChangeError.class, - IncompatibleClassChangeError.class, - ClassFormatError.class, - }; - String[] messages = { - "Nest member HostOfMemberNoHost$MemberNoHost in HostOfMemberNoHost " + - "declares a different nest host of HostOfMemberNoHost$MemberNoHost", - "Unable to load nest-host class (NestHost) of " + - "HostOfMemberMissingHost$MemberMissingHost", - "Type HostOfMemberNotOurHost$MemberNotOurHost (loader: 'app') is not a nest member " + - "of InvalidNestHost (loader: 'app'): current type is not listed as a nest member", - "Type HostOfMemberNotInstanceHost$MemberNotInstanceHost (loader: 'app') is not a nest " + - "member of [LInvalidNestHost; (loader: 'app'): current type is not listed as a nest member", - "Incompatible magic value 3735928559 in class file MalformedHost", - }; - for (int i = 0; i < bad.length; i++) { - try { - bad[i].getNestMembers(); - throw new Error("getNestMembers() succeeded for class " + - bad[i].getName()); - } catch (LinkageError e) { - checkException(e, messages[i], exceptions[i]); - } - } - } - - static void checkException(Throwable actual, String msg, Class expected) { - if (!actual.getClass().equals(expected)) - throw new Error("Unexpected exception: got " + actual.getClass().getName() - + " but expected " + expected.getName()); - if (!actual.getMessage().contains(msg)) - throw new Error("Wrong " + actual.getClass().getSimpleName() +": \"" + - actual.getMessage() + "\" does not contain \"" + - msg + "\""); - System.out.println("OK - got expected exception: " + actual); + checkSingletonNests(bad); } static void checkHost(Class target, Class expected) { diff --git a/test/jdk/jdk/internal/reflect/Reflection/Filtering.java b/test/jdk/jdk/internal/reflect/Reflection/Filtering.java --- a/test/jdk/jdk/internal/reflect/Reflection/Filtering.java +++ b/test/jdk/jdk/internal/reflect/Reflection/Filtering.java @@ -54,6 +54,7 @@ return new Object[][]{ { AccessibleObject.class, "override" }, { Class.class, "classLoader" }, + { Class.class, "classData" }, { ClassLoader.class, "parent" }, { Field.class, "clazz" }, { Field.class, "modifiers" }, diff --git a/test/jdk/jdk/jfr/event/runtime/TestClassDefineEvent.java b/test/jdk/jdk/jfr/event/runtime/TestClassDefineEvent.java --- a/test/jdk/jdk/jfr/event/runtime/TestClassDefineEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestClassDefineEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2020, 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 @@ -41,6 +41,7 @@ * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk + * @modules java.base/jdk.internal.misc * @build jdk.jfr.event.runtime.TestClasses * @run main/othervm jdk.jfr.event.runtime.TestClassDefineEvent */ diff --git a/test/jdk/jdk/jfr/event/runtime/TestClassLoadEvent.java b/test/jdk/jdk/jfr/event/runtime/TestClassLoadEvent.java --- a/test/jdk/jdk/jfr/event/runtime/TestClassLoadEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestClassLoadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2020, 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 @@ -42,6 +42,7 @@ * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk + * @modules java.base/jdk.internal.misc * @build jdk.jfr.event.runtime.TestClasses * @run main/othervm jdk.jfr.event.runtime.TestClassLoadEvent */ diff --git a/test/jdk/jdk/jfr/event/runtime/TestClassLoaderStatsEvent.java b/test/jdk/jdk/jfr/event/runtime/TestClassLoaderStatsEvent.java --- a/test/jdk/jdk/jfr/event/runtime/TestClassLoaderStatsEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestClassLoaderStatsEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, 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 @@ -28,6 +28,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.List; @@ -39,11 +40,16 @@ import jdk.test.lib.jfr.EventNames; import jdk.test.lib.jfr.Events; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + /** * @test * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk + * @modules java.base/jdk.internal.misc + * jdk.compiler + * jdk.jfr * @build jdk.jfr.event.runtime.TestClasses * @run main/othervm jdk.jfr.event.runtime.TestClassLoaderStatsEvent */ @@ -73,12 +79,15 @@ if (CLASSLOADER_TYPE_NAME.equals(recordedClassLoader.getType().getName())) { Asserts.assertEquals(CLASS_LOADER_NAME, recordedClassLoader.getName(), "Expected class loader name " + CLASS_LOADER_NAME + ", got name " + recordedClassLoader.getName()); - Events.assertField(event, "classCount").equal(1L); + Events.assertField(event, "classCount").equal(2L); Events.assertField(event, "chunkSize").above(1L); Events.assertField(event, "blockSize").above(1L); - Events.assertField(event, "unsafeAnonymousClassCount").equal(1L); - Events.assertField(event, "unsafeAnonymousChunkSize").above(1L); - Events.assertField(event, "unsafeAnonymousBlockSize").above(1L); + Events.assertField(event, "unsafeAnonymousClassCount").equal(2L); + Events.assertField(event, "unsafeAnonymousChunkSize").above(0L); + Events.assertField(event, "unsafeAnonymousBlockSize").above(0L); + Events.assertField(event, "hiddenClassCount").equal(2L); + Events.assertField(event, "hiddenChunkSize").above(0L); + Events.assertField(event, "hiddenBlockSize").above(0L); isAnyFound = true; } } @@ -91,6 +100,18 @@ if (c.getClassLoader() != dummyloader) { throw new RuntimeException("TestClass defined by wrong classloader: " + c.getClassLoader()); } + + // Compile a class for method createNonFindableClasses() to use to create both a + // weak hidden class and an anonymous class. + byte klassbuf[] = InMemoryJavaCompiler.compile("jdk.jfr.event.runtime.TestClass", + "package jdk.jfr.event.runtime; " + + "public class TestClass { " + + " public static void concat(String one, String two) throws Throwable { " + + " } } "); + + Method m = c.getDeclaredMethod("createNonFindableClasses", byte[].class); + m.setAccessible(true); + m.invoke(null, klassbuf); } public static class DummyClassLoader extends ClassLoader { diff --git a/test/jdk/jdk/jfr/event/runtime/TestClassLoadingStatisticsEvent.java b/test/jdk/jdk/jfr/event/runtime/TestClassLoadingStatisticsEvent.java --- a/test/jdk/jdk/jfr/event/runtime/TestClassLoadingStatisticsEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestClassLoadingStatisticsEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2020, 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 @@ -39,6 +39,7 @@ * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk + * @modules java.base/jdk.internal.misc * @build jdk.jfr.event.runtime.TestClasses * @run main/othervm jdk.jfr.event.runtime.TestClassLoadingStatisticsEvent */ diff --git a/test/jdk/jdk/jfr/event/runtime/TestClassUnloadEvent.java b/test/jdk/jdk/jfr/event/runtime/TestClassUnloadEvent.java --- a/test/jdk/jdk/jfr/event/runtime/TestClassUnloadEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestClassUnloadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2020, 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 @@ -43,6 +43,7 @@ * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk + * @modules java.base/jdk.internal.misc * @build jdk.jfr.event.runtime.TestClasses * @run main/othervm -Xlog:class+unload -Xlog:gc -Xmx16m jdk.jfr.event.runtime.TestClassUnloadEvent */ diff --git a/test/jdk/jdk/jfr/event/runtime/TestClasses.java b/test/jdk/jdk/jfr/event/runtime/TestClasses.java --- a/test/jdk/jdk/jfr/event/runtime/TestClasses.java +++ b/test/jdk/jdk/jfr/event/runtime/TestClasses.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2020, 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 @@ -25,6 +25,14 @@ package jdk.jfr.event.runtime; +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Array; +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; + +import jdk.internal.misc.Unsafe; + public class TestClasses { protected TestClassPrivate testClassPrivate; @@ -76,8 +84,20 @@ class TestClass { static { - // force creation of anonymous class (for the lambda form) + // force creation of hidden class (for the lambda form) Runnable r = () -> System.out.println("Hello"); r.run(); } + + public static void createNonFindableClasses(byte[] klassbuf) throws Throwable { + // Create a hidden class and an array of hidden classes. + Lookup lookup = MethodHandles.lookup(); + Class clh = lookup.defineHiddenClass(klassbuf, false, NESTMATE).lookupClass(); + Class arrayOfHidden = Array.newInstance(clh, 10).getClass(); // HAS ISSUES? + + // Create an Unsafe anonymous class and an array of unsafe anonymous classes. + Unsafe unsafe = Unsafe.getUnsafe(); + Class clu = unsafe.defineAnonymousClass(TestClass.class, klassbuf, new Object[0]); + final Class arrayOfUAC = Array.newInstance(clu, 15).getClass(); + } } diff --git a/test/jdk/jdk/jfr/event/runtime/TestTableStatisticsEvent.java b/test/jdk/jdk/jfr/event/runtime/TestTableStatisticsEvent.java --- a/test/jdk/jdk/jfr/event/runtime/TestTableStatisticsEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestTableStatisticsEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, 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 @@ -41,6 +41,7 @@ * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk + * @modules java.base/jdk.internal.misc * @build jdk.jfr.event.runtime.TestClasses * @run main/othervm jdk.jfr.event.runtime.TestTableStatisticsEvent * @bug 8185525 diff --git a/test/jdk/sun/misc/UnsafeFieldOffsets.java b/test/jdk/sun/misc/UnsafeFieldOffsets.java new file mode 100644 --- /dev/null +++ b/test/jdk/sun/misc/UnsafeFieldOffsets.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020, 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 + * @summary Ensure that sun.misc.Unsafe::objectFieldOffset and staticFieldOffset + * throw UnsupportedOperationException on Field of a hidden class + * @modules jdk.unsupported + * @run main UnsafeFieldOffsets + */ + +import sun.misc.Unsafe; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class UnsafeFieldOffsets { + static class Fields { + static final Object STATIC_FINAL = new Object(); + static Object STATIC_NON_FINAL = new Object(); + final Object FINAL = new Object(); + Object NON_FINAL = new Object(); + } + + private static Unsafe UNSAFE = getUnsafe(); + private static final Class HIDDEN_CLASS = defineHiddenClass(); + + private static Unsafe getUnsafe() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get(null); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static Class defineHiddenClass() { + String classes = System.getProperty("test.classes"); + Path cf = Paths.get(classes, "UnsafeFieldOffsets$Fields.class"); + try { + byte[] bytes = Files.readAllBytes(cf); + Class c = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); + assertHiddenClass(c); + return c; + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) throws Exception { + // non-hidden class + testStaticField(Fields.class, "STATIC_FINAL"); + testStaticField(Fields.class, "STATIC_NON_FINAL"); + testInstanceField(Fields.class, "FINAL"); + testInstanceField(Fields.class, "NON_FINAL"); + + // hidden class + testStaticField(HIDDEN_CLASS, "STATIC_FINAL"); + testStaticField(HIDDEN_CLASS, "STATIC_NON_FINAL"); + testInstanceField(HIDDEN_CLASS, "FINAL"); + testInstanceField(HIDDEN_CLASS, "NON_FINAL"); + } + + private static void testStaticField(Class c, String name) throws Exception { + Field f = c.getDeclaredField(name); + try { + UNSAFE.staticFieldOffset(f); + assertNonHiddenClass(c); + } catch (UnsupportedOperationException e) { + assertHiddenClass(c); + } + } + + private static void testInstanceField(Class c, String name) throws Exception { + Field f = c.getDeclaredField(name); + try { + UNSAFE.objectFieldOffset(f); + assertNonHiddenClass(c); + } catch (UnsupportedOperationException e) { + assertHiddenClass(c); + } + } + + private static void assertNonHiddenClass(Class c) { + if (c.isHidden()) + throw new RuntimeException("Expected UOE but not thrown: " + c); + } + + private static void assertHiddenClass(Class c) { + if (!c.isHidden()) + throw new RuntimeException("Expected hidden class but is not: " + c); + } +} diff --git a/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTargetRelease14Test.java b/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTargetRelease14Test.java new file mode 100644 --- /dev/null +++ b/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTargetRelease14Test.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020, 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 8238358 + * @summary Checking ACC_SYNTHETIC flag is generated for bridge method + * generated for lambda expressions and method references when + * compiling with --release 14. + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.jdeps/com.sun.tools.classfile + * @library /tools/lib /tools/javac/lib ../lib + * @build toolbox.ToolBox InMemoryFileManager TestResult TestBase + * @build SyntheticTestDriver ExpectedClass ExpectedClasses + * @compile --source 14 -target 14 -XDdeduplicateLambdas=false BridgeMethodsForLambdaTargetRelease14Test.java + * @run main SyntheticTestDriver BridgeMethodsForLambdaTargetRelease14Test + */ + +import java.util.Comparator; +import java.util.stream.IntStream; + +/** + * Synthetic members: + * 1. inner class for Inner1. + * 2. method for () -> {} in Inner1 + * 3. method for () -> {} in Inner2 + * 4. method references to private methods. + * 5. method for super::function() + * 6. method references to private static methods. + * 7. access method for private method function(). + * 8. access method for private static method staticFunction(). + * 9. method reference to vararg method. + * 10. method reference to array's method. + * 11. constructors for Inner1 and Inner2. + */ +@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test", + expectedMethods = {"()", "()", "function(java.lang.Integer[])"}, + expectedNumberOfSyntheticMethods = 6) +@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner1", + expectedMethods = {"(BridgeMethodsForLambdaTargetRelease14Test)", "function()", "run()"}, + expectedFields = "lambda1", + expectedNumberOfSyntheticMethods = 1, + expectedNumberOfSyntheticFields = 1) +@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner2", + expectedMethods = {"()", "staticFunction()"}, + expectedFields = "lambda1", + expectedNumberOfSyntheticMethods = 1) +@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner3", + expectedMethods = {"(BridgeMethodsForLambdaTargetRelease14Test)", "function()"}, + expectedNumberOfSyntheticFields = 1) +@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner4", + expectedMethods = {"(BridgeMethodsForLambdaTargetRelease14Test)", "function()"}, + expectedNumberOfSyntheticMethods = 1, + expectedNumberOfSyntheticFields = 1) +public class BridgeMethodsForLambdaTargetRelease14Test { + + private class Inner1 implements Runnable { + private Inner1() { + } + private Runnable lambda1 = () -> { + }; + private void function() { + } + @Override + public void run() { + } + } + + private static class Inner2 { + private Runnable lambda1 = () -> { + }; + private static void staticFunction() { + } + } + + private class Inner3 { + public void function() { + } + } + + private class Inner4 extends Inner3 { + @Override + public void function() { + Runnable r = super::function; + } + } + + private static int function(Integer...vararg) { + return 0; + } + + { + Inner1 inner = new Inner1(); + Runnable l1 = inner::function; + Runnable l2 = Inner1::new; + inner.lambda1 = inner::function; + Comparator c = BridgeMethodsForLambdaTargetRelease14Test::function; + IntStream.of(2).mapToObj(int[]::new); + } + + static { + Inner2 inner = new Inner2(); + Runnable l1 = Inner2::staticFunction; + } +} diff --git a/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTest.java b/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTest.java --- a/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2020, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8044537 8200301 + * @bug 8044537 8200301 8238358 * @summary Checking ACC_SYNTHETIC flag is generated for bridge method * generated for lambda expressions and method references. * @modules jdk.compiler/com.sun.tools.javac.api @@ -44,18 +44,14 @@ * 1. inner class for Inner1. * 2. method for () -> {} in Inner1 * 3. method for () -> {} in Inner2 - * 4. method references to private methods. - * 5. method for super::function() - * 6. method references to private static methods. - * 7. access method for private method function(). - * 8. access method for private static method staticFunction(). - * 9. method reference to vararg method. - * 10. method reference to array's method. - * 11. constructors for Inner1 and Inner2. + * 4. method for super::function() + * 5. method reference to vararg method. + * 6. method reference to array's method. + * 7. constructors for Inner1 and Inner2. */ @ExpectedClass(className = "BridgeMethodsForLambdaTest", expectedMethods = {"()", "()", "function(java.lang.Integer[])"}, - expectedNumberOfSyntheticMethods = 6) + expectedNumberOfSyntheticMethods = 3) @ExpectedClass(className = "BridgeMethodsForLambdaTest$Inner1", expectedMethods = {"(BridgeMethodsForLambdaTest)", "function()", "run()"}, expectedFields = "lambda1", diff --git a/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecode.java b/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecode.java --- a/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecode.java +++ b/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2020, 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 @@ -23,8 +23,9 @@ /* * @test - * @bug 8009649 8129962 - * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods + * @bug 8009649 8129962 8238358 + * @summary Lambda back-end should generate invokevirtual for method handles referring to + * private instance methods as lambda proxy is a nestmate of the target clsas * @library /tools/javac/lib * @modules jdk.jdeps/com.sun.tools.classfile * jdk.compiler/com.sun.tools.javac.api @@ -269,14 +270,14 @@ boolean kindOK; switch (mh.reference_kind) { case REF_invokeStatic: kindOK = mk2.isStatic(); break; - case REF_invokeSpecial: kindOK = !mk2.isStatic(); break; + case REF_invokeVirtual: kindOK = !mk2.isStatic() && !mk2.inInterface(); break; case REF_invokeInterface: kindOK = mk2.inInterface(); break; default: kindOK = false; } if (!kindOK) { - fail("Bad invoke kind in implementation method handle"); + fail("Bad invoke kind in implementation method handle: " + mh.reference_kind); return; } diff --git a/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecodeTargetRelease14.java b/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecodeTargetRelease14.java new file mode 100644 --- /dev/null +++ b/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecodeTargetRelease14.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2020, 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 8238358 + * @summary Lambda back-end should generate invokespecial for method handles referring to + * private instance methods when compiling with --release 14 + * @library /tools/javac/lib + * @modules jdk.jdeps/com.sun.tools.classfile + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.util + * @build combo.ComboTestHelper + * @run main TestLambdaBytecodeTargetRelease14 + */ + +import com.sun.tools.classfile.Attribute; +import com.sun.tools.classfile.BootstrapMethods_attribute; +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.Code_attribute; +import com.sun.tools.classfile.ConstantPool.*; +import com.sun.tools.classfile.Instruction; +import com.sun.tools.classfile.Method; + +import java.io.IOException; +import java.io.InputStream; + +import combo.ComboInstance; +import combo.ComboParameter; +import combo.ComboTask.Result; +import combo.ComboTestHelper; + +import javax.tools.JavaFileObject; + +public class TestLambdaBytecodeTargetRelease14 extends ComboInstance { + + static final int MF_ARITY = 3; + static final String MH_SIG = "()V"; + + enum ClassKind implements ComboParameter { + CLASS("class"), + INTERFACE("interface"); + + String classStr; + + ClassKind(String classStr) { + this.classStr = classStr; + } + + @Override + public String expand(String optParameter) { + return classStr; + } + } + + enum AccessKind implements ComboParameter { + PUBLIC("public"), + PRIVATE("private"); + + String accessStr; + + AccessKind(String accessStr) { + this.accessStr = accessStr; + } + + @Override + public String expand(String optParameter) { + return accessStr; + } + } + + enum StaticKind implements ComboParameter { + STATIC("static"), + INSTANCE(""); + + String staticStr; + + StaticKind(String staticStr) { + this.staticStr = staticStr; + } + + @Override + public String expand(String optParameter) { + return staticStr; + } + } + + enum DefaultKind implements ComboParameter { + DEFAULT("default"), + NO_DEFAULT(""); + + String defaultStr; + + DefaultKind(String defaultStr) { + this.defaultStr = defaultStr; + } + + @Override + public String expand(String optParameter) { + return defaultStr; + } + } + + static class MethodKind { + ClassKind ck; + AccessKind ak; + StaticKind sk; + DefaultKind dk; + + MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) { + this.ck = ck; + this.ak = ak; + this.sk = sk; + this.dk = dk; + } + + boolean inInterface() { + return ck == ClassKind.INTERFACE; + } + + boolean isPrivate() { + return ak == AccessKind.PRIVATE; + } + + boolean isStatic() { + return sk == StaticKind.STATIC; + } + + boolean isDefault() { + return dk == DefaultKind.DEFAULT; + } + + boolean isOK() { + if (isDefault() && (!inInterface() || isStatic())) { + return false; + } else if (inInterface() && + ((!isStatic() && !isDefault()) || isPrivate())) { + return false; + } else { + return true; + } + } + } + + public static void main(String... args) throws Exception { + new ComboTestHelper() + .withDimension("CLASSKIND", (x, ck) -> x.ck = ck, ClassKind.values()) + .withArrayDimension("ACCESS", (x, acc, idx) -> x.accessKinds[idx] = acc, 2, AccessKind.values()) + .withArrayDimension("STATIC", (x, sk, idx) -> x.staticKinds[idx] = sk, 2, StaticKind.values()) + .withArrayDimension("DEFAULT", (x, dk, idx) -> x.defaultKinds[idx] = dk, 2, DefaultKind.values()) + .run(TestLambdaBytecodeTargetRelease14::new, TestLambdaBytecodeTargetRelease14::init); + } + + ClassKind ck; + AccessKind[] accessKinds = new AccessKind[2]; + StaticKind[] staticKinds = new StaticKind[2]; + DefaultKind[] defaultKinds = new DefaultKind[2]; + MethodKind mk1, mk2; + + void init() { + mk1 = new MethodKind(ck, accessKinds[0], staticKinds[0], defaultKinds[0]); + mk2 = new MethodKind(ck, accessKinds[1], staticKinds[1], defaultKinds[1]); + } + + String source_template = + "#{CLASSKIND} Test {\n" + + " #{ACCESS[0]} #{STATIC[0]} #{DEFAULT[0]} void test() { Runnable r = ()->{ target(); }; }\n" + + " #{ACCESS[1]} #{STATIC[1]} #{DEFAULT[1]} void target() { }\n" + + "}\n"; + + @Override + public void doWork() throws IOException { + newCompilationTask() + .withSourceFromTemplate(source_template) + .withOption("--release").withOption("14") + .generate(this::verifyBytecode); + } + + void verifyBytecode(Result> res) { + if (res.hasErrors()) { + boolean errorExpected = !mk1.isOK() || !mk2.isOK(); + errorExpected |= mk1.isStatic() && !mk2.isStatic(); + + if (!errorExpected) { + fail("Diags found when compiling instance; " + res.compilationInfo()); + } + return; + } + try (InputStream is = res.get().iterator().next().openInputStream()) { + ClassFile cf = ClassFile.read(is); + Method testMethod = null; + for (Method m : cf.methods) { + if (m.getName(cf.constant_pool).equals("test")) { + testMethod = m; + break; + } + } + if (testMethod == null) { + fail("Test method not found"); + return; + } + Code_attribute ea = + (Code_attribute)testMethod.attributes.get(Attribute.Code); + if (testMethod == null) { + fail("Code attribute for test() method not found"); + return; + } + + int bsmIdx = -1; + + for (Instruction i : ea.getInstructions()) { + if (i.getMnemonic().equals("invokedynamic")) { + CONSTANT_InvokeDynamic_info indyInfo = + (CONSTANT_InvokeDynamic_info)cf + .constant_pool.get(i.getShort(1)); + bsmIdx = indyInfo.bootstrap_method_attr_index; + if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType())) { + fail("type mismatch for CONSTANT_InvokeDynamic_info " + + res.compilationInfo() + "\n" + indyInfo.getNameAndTypeInfo().getType() + + "\n" + makeIndyType()); + return; + } + } + } + if (bsmIdx == -1) { + fail("Missing invokedynamic in generated code"); + return; + } + + BootstrapMethods_attribute bsm_attr = + (BootstrapMethods_attribute)cf + .getAttribute(Attribute.BootstrapMethods); + if (bsm_attr.bootstrap_method_specifiers.length != 1) { + fail("Bad number of method specifiers " + + "in BootstrapMethods attribute"); + return; + } + BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = + bsm_attr.bootstrap_method_specifiers[0]; + + if (bsm_spec.bootstrap_arguments.length != MF_ARITY) { + fail("Bad number of static invokedynamic args " + + "in BootstrapMethod attribute"); + return; + } + + CONSTANT_MethodHandle_info mh = + (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]); + + boolean kindOK; + switch (mh.reference_kind) { + case REF_invokeStatic: kindOK = mk2.isStatic(); break; + case REF_invokeSpecial: kindOK = !mk2.isStatic(); break; + case REF_invokeInterface: kindOK = mk2.inInterface(); break; + default: + kindOK = false; + } + + if (!kindOK) { + fail("Bad invoke kind in implementation method handle"); + return; + } + + if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) { + fail("Type mismatch in implementation method handle"); + return; + } + } catch (Exception e) { + e.printStackTrace(); + fail("error reading " + res.compilationInfo() + ": " + e); + } + } + + String makeIndyType() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + if (!mk2.isStatic()) { + buf.append("LTest;"); + } + buf.append(")Ljava/lang/Runnable;"); + return buf.toString(); + } +} diff --git a/test/langtools/tools/javac/lambda/lambdaExpression/LambdaTest6.java b/test/langtools/tools/javac/lambda/lambdaExpression/LambdaTest6.java --- a/test/langtools/tools/javac/lambda/lambdaExpression/LambdaTest6.java +++ b/test/langtools/tools/javac/lambda/lambdaExpression/LambdaTest6.java @@ -66,9 +66,6 @@ private static Set allowedMethods() { Set s = new HashSet<>(); s.add("m"); - if (Boolean.getBoolean("jdk.internal.lambda.disableEagerInitialization")) { - s.add("get$Lambda"); - } return s; } diff --git a/test/langtools/tools/javac/lambda/methodReference/BridgeMethod.java b/test/langtools/tools/javac/lambda/methodReference/BridgeMethod.java --- a/test/langtools/tools/javac/lambda/methodReference/BridgeMethod.java +++ b/test/langtools/tools/javac/lambda/methodReference/BridgeMethod.java @@ -75,9 +75,6 @@ private static Set allowedMethods() { Set s = new HashSet<>(); s.add("m"); - if (Boolean.getBoolean("jdk.internal.lambda.disableEagerInitialization")) { - s.add("get$Lambda"); - } return s; } diff --git a/test/lib/jdk/test/lib/util/ForceGC.java b/test/lib/jdk/test/lib/util/ForceGC.java new file mode 100644 --- /dev/null +++ b/test/lib/jdk/test/lib/util/ForceGC.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, 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 jdk.test.lib.util; + +import java.lang.ref.Cleaner; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; + +/** + * Utility class to invoke System.gc() + */ +public class ForceGC { + private final CountDownLatch cleanerInvoked = new CountDownLatch(1); + private final Cleaner cleaner = Cleaner.create(); + public ForceGC() { + cleaner.register(new Object(), () -> cleanerInvoked.countDown()); + } + + private void doit() { + try { + for (int i = 0; i < 10; i++) { + System.gc(); + System.out.println("gc " + i); + if (cleanerInvoked.await(1L, TimeUnit.SECONDS)) { + return; + } + } + } catch (InterruptedException unexpected) { + throw new AssertionError("unexpected InterruptedException"); + } + } + + /** + * Causes the current thread to wait until the {@code BooleanSupplier} returns true, + * unless the thread is interrupted or a predefined waiting time elapses. + * + * @param s boolean supplier + * @return true if the {@code BooleanSupplier} returns true and false if + * the predefined waiting time elapsed before the count reaches zero. + * @throws InterruptedException if the current thread is interrupted while waiting + */ + public boolean await(BooleanSupplier s) { + for (int i = 0; i < 10; i++) { + if (s.getAsBoolean()) return true; + doit(); + } + return false; + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/invoke/LookupDefineClass.java b/test/micro/org/openjdk/bench/java/lang/invoke/LookupDefineClass.java new file mode 100644 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/invoke/LookupDefineClass.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.lang.invoke; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.concurrent.TimeUnit; + +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; + +public class LookupDefineClass { + private static final byte[] X_BYTECODE = new byte[]{ + (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE, 0x00, 0x00, 0x00, 0x38, 0x00, 0x10, 0x0A, 0x00, + 0x03, 0x00, 0x0C, 0x07, 0x00, 0x0D, 0x07, 0x00, 0x0E, 0x07, 0x00, 0x0F, + 0x01, 0x00, 0x06, 0x3C, 0x69, 0x6E, 0x69, 0x74, 0x3E, 0x01, 0x00, 0x03, + 0x28, 0x29, 0x56, 0x01, 0x00, 0x04, 0x43, 0x6F, 0x64, 0x65, 0x01, 0x00, + 0x0F, 0x4C, 0x69, 0x6E, 0x65, 0x4E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x54, + 0x61, 0x62, 0x6C, 0x65, 0x01, 0x00, 0x03, 0x72, 0x75, 0x6E, 0x01, 0x00, + 0x0A, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6C, 0x65, 0x01, + 0x00, 0x08, 0x46, 0x6F, 0x6F, 0x2E, 0x6A, 0x61, 0x76, 0x61, 0x0C, 0x00, + 0x05, 0x00, 0x06, 0x01, 0x00, 0x07, 0x66, 0x6F, 0x6F, 0x2F, 0x46, 0x6F, + 0x6F, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, + 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x01, 0x00, 0x12, 0x6A, + 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x52, 0x75, 0x6E, + 0x6E, 0x61, 0x62, 0x6C, 0x65, 0x00, 0x21, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x05, 0x2A, (byte)0xB7, 0x00, 0x01, (byte)0xB1, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x09, 0x00, 0x06, 0x00, 0x01, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + (byte)0xB1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0B + }; + + /** + * Our own crippled classloader, that can only load a simple class over and over again. + */ + public static class XLoader extends URLClassLoader { + public XLoader() { + super("X-Loader", new URL[0], ClassLoader.getSystemClassLoader()); + } + + @Override + protected Class findClass(final String name) throws ClassNotFoundException { + return defineClass(name, X_BYTECODE, 0, X_BYTECODE.length); + } + + public Class install(final String name, byte[] bytes) { + return defineClass(name, bytes, 0, bytes.length); + } + } + + public static class Loader extends URLClassLoader { + public Loader(String name) { + super(name, new URL[0], ClassLoader.getSystemClassLoader()); + } + + public Class install(final String name, byte[] bytes) { + return defineClass(name, bytes, 0, bytes.length); + } + } + + @State(Scope.Thread) + @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Fork(3) + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public static class OneClassPerLoader { + ClassLoader loader; + @Benchmark + public Class load() throws ClassNotFoundException { + return Class.forName("foo.Foo", false, loader); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(LookupDefineClass.OneClassPerLoader.class.getSimpleName()) + // .addProfiler(ClassloaderProfiler.class) + // .addProfiler(CompilerProfiler.class) + .build(); + + new Runner(opt).run(); + } + + @Setup(Level.Invocation) + public void doSetup() { + loader = new XLoader(); + } + } + + private static final byte[] FOO_HOST_BYTES = new byte[]{ + (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE, 0x00, 0x00, 0x00, 0x33, 0x00, 0x12, 0x01, 0x00, + 0x11, 0x66, 0x6F, 0x6F, 0x2F, 0x41, 0x6E, 0x6F, 0x6E, 0x79, 0x6D, 0x6F, + 0x75, 0x73, 0x48, 0x6F, 0x73, 0x74, 0x07, 0x00, 0x01, 0x01, 0x00, 0x10, + 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x4F, 0x62, + 0x6A, 0x65, 0x63, 0x74, 0x07, 0x00, 0x03, 0x01, 0x00, 0x06, 0x4C, 0x4F, + 0x4F, 0x4B, 0x55, 0x50, 0x01, 0x00, 0x27, 0x4C, 0x6A, 0x61, 0x76, 0x61, + 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, + 0x2F, 0x4D, 0x65, 0x74, 0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, + 0x65, 0x73, 0x24, 0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70, 0x3B, 0x01, 0x00, + 0x08, 0x3C, 0x63, 0x6C, 0x69, 0x6E, 0x69, 0x74, 0x3E, 0x01, 0x00, 0x03, + 0x28, 0x29, 0x56, 0x01, 0x00, 0x1E, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, + 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, + 0x65, 0x74, 0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x73, + 0x07, 0x00, 0x09, 0x01, 0x00, 0x06, 0x6C, 0x6F, 0x6F, 0x6B, 0x75, 0x70, + 0x01, 0x00, 0x29, 0x28, 0x29, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, + 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, + 0x65, 0x74, 0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x73, + 0x24, 0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70, 0x3B, 0x0C, 0x00, 0x0B, 0x00, + 0x0C, 0x0A, 0x00, 0x0A, 0x00, 0x0D, 0x0C, 0x00, 0x05, 0x00, 0x06, 0x09, + 0x00, 0x02, 0x00, 0x0F, 0x01, 0x00, 0x04, 0x43, 0x6F, 0x64, 0x65, 0x06, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x19, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, (byte)0xB8, 0x00, 0x0E, (byte)0xB3, 0x00, 0x10, (byte)0xB1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static MethodHandles.Lookup defineHostClass(Loader loader, String name, byte[] bytes) { + try { + Class c = loader.install(name, bytes); + Field f = c.getDeclaredField("LOOKUP"); + return (MethodHandles.Lookup)f.get(null); + } catch (NoSuchFieldException|IllegalAccessException e) { + throw new InternalError(e); + } + } + + @State(Scope.Thread) + @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Fork(3) + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public static class TwoClassPerLoader { + XLoader loader; + @Benchmark + public Class load() throws ClassNotFoundException { + return Class.forName("foo.Foo", false, loader); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(LookupDefineClass.TwoClassPerLoader.class.getSimpleName()) + // .addProfiler(ClassloaderProfiler.class) + // .addProfiler(CompilerProfiler.class) + .build(); + + new Runner(opt).run(); + } + + @Setup(Level.Invocation) + public void doSetup() { + loader = new XLoader(); + loader.install("foo.AnonymousHost", FOO_HOST_BYTES); + } + } + + + @State(Scope.Thread) + @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Fork(3) + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public static class WeakClass { + private static final MethodHandles.Lookup HOST_LOOKUP = + defineHostClass(new Loader("weak-class-loader"), "foo.AnonymousHost", FOO_HOST_BYTES); + + @Benchmark + public Class load() throws ClassNotFoundException { + try { + return HOST_LOOKUP.defineHiddenClass(X_BYTECODE, false).lookupClass(); + } catch (IllegalAccessException e) { + throw new InternalError(e); + } + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(LookupDefineClass.WeakClass.class.getSimpleName()) + // .addProfiler(ClassloaderProfiler.class) + // .addProfiler(CompilerProfiler.class) + .build(); + + new Runner(opt).run(); + } + } + + @State(Scope.Thread) + @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Fork(3) + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public static class UnsafeAnonymousClass { + static final sun.misc.Unsafe unsafe = getUnsafe(); + + private static final MethodHandles.Lookup lookup = + defineHostClass(new Loader("anonymous-class-loader"),"foo.AnonymousHost", FOO_HOST_BYTES); + + @Benchmark + public Class load() throws ClassNotFoundException { + return unsafe.defineAnonymousClass(lookup.lookupClass(), X_BYTECODE, null); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(LookupDefineClass.UnsafeAnonymousClass.class.getSimpleName()) + // .addProfiler(ClassloaderProfiler.class) + // .addProfiler(CompilerProfiler.class) + .build(); + + new Runner(opt).run(); + } + } + + static sun.misc.Unsafe getUnsafe() { + try { + Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe)f.get(null); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @State(Scope.Thread) + @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Fork(3) + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public static class HiddenClass { + private static final MethodHandles.Lookup HOST_LOOKUP = + defineHostClass(new Loader("hidden-class-loader"),"foo.AnonymousHost", FOO_HOST_BYTES); + + @Benchmark + public Class load() throws ClassNotFoundException { + try { + return HOST_LOOKUP.defineHiddenClass(X_BYTECODE, false, STRONG).lookupClass(); + } catch (IllegalAccessException e) { + throw new InternalError(e); + } + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(LookupDefineClass.HiddenClass.class.getSimpleName()) + // .addProfiler(ClassloaderProfiler.class) + // .addProfiler(CompilerProfiler.class) + .build(); + + new Runner(opt).run(); + } + } +}