| /* | |
| * Copyright (c) 2003, 2025, 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 "classfile/classLoaderData.inline.hpp" | |
| #include "classfile/classLoaderDataGraph.hpp" | |
| #include "classfile/dictionary.hpp" | |
| #include "classfile/loaderConstraints.hpp" | |
| #include "logging/log.hpp" | |
| #include "memory/resourceArea.hpp" | |
| #include "oops/klass.inline.hpp" | |
| #include "oops/symbolHandle.hpp" | |
| #include "runtime/mutexLocker.hpp" | |
| #include "utilities/resourceHash.hpp" | |
| // Overview | |
| // | |
| // The LoaderConstraintTable controls whether two ClassLoaders can resolve the same class name N | |
| // to different InstanceKlasses. | |
| // | |
| // The design of the algorithm can be found in the OOPSLA'98 paper "Dynamic Class Loading in | |
| // the Java Virtual Machine" by Sheng Liang and Gilad Bracha. | |
| // | |
| // To understand the implementation, start with LoaderConstraintTable::{add_entry, check_or_update} | |
| // | |
| // When a class name N is entered into the LoaderConstraintTable, it's mapped to a ConstraintSet which | |
| // contains one or more LoaderConstraints: | |
| // | |
| // LoaderConstraint_a = { _klass_a, loader_a1, loader_a2, ...} | |
| // LoaderConstraint_b = { _klass_b, loader_b1, loader_b2, ...} | |
| // LoaderConstraint_c = { _klass_c, loader_c1, loader_c2, ...} | |
| // ... | |
| // | |
| // If _klass_<m> is null, when the first loader_<m><n> resolves the name N to a class K, | |
| // we assign _klass_<m> = K. | |
| // | |
| // if _klass_<m> is non-null, when a loader loader_<m><n> tries to resolve the name N to a class K, | |
| // where _klass_<m> != K, a LinkageError is thrown, and the resolution fails. | |
| // | |
| // Management of LoaderConstraints | |
| // | |
| // When the SystemDictionary decides that loader_x and loader_y must resolve the name N to the same class: | |
| // For the name N, find two LoaderConstraints such that: | |
| // | |
| // - LoaderConstraint_x contains loader_x | |
| // - LoaderConstraint_y contains loader_y | |
| // | |
| // (Note that no class loader will appear in more than one LoaderConstraint for | |
| // each name N, as enforced by the following steps). | |
| // | |
| // If neither LoaderConstraint_x nor LoaderConstraint_y exist, add a new LoaderConstraint that contains | |
| // both loader_x and loader_y. | |
| // | |
| // Otherwise if LoaderConstraint_x exists but LoaderConstraint_y doesn't exist, add loader_y to LoaderConstraint_x, | |
| // or vice versa. | |
| // | |
| // Otherwise if both LoaderConstraints have different values for _klass, a LinkageError is thrown. | |
| // | |
| // Otherwise the two LoaderConstraints are merged into one. | |
| class LoaderConstraint : public CHeapObj<mtClass> { | |
| InstanceKlass* _klass; | |
| // Loader constraints enforce correct linking behavior. | |
| // Thus, it really operates on ClassLoaderData which represents linking domain, | |
| // not class loaders. | |
| GrowableArray<ClassLoaderData*>* _loaders; // initiating loaders | |
| public: | |
| LoaderConstraint(InstanceKlass* klass, ClassLoaderData* loader1, ClassLoaderData* loader2) : | |
| _klass(klass) { | |
| _loaders = new (mtClass) GrowableArray<ClassLoaderData*>(10, mtClass); | |
| add_loader_data(loader1); | |
| add_loader_data(loader2); | |
| } | |
| LoaderConstraint(const LoaderConstraint& src) = delete; | |
| LoaderConstraint& operator=(const LoaderConstraint&) = delete; | |
| ~LoaderConstraint() { delete _loaders; } | |
| InstanceKlass* klass() const { return _klass; } | |
| void set_klass(InstanceKlass* k) { _klass = k; } | |
| void extend_loader_constraint(Symbol* class_name, ClassLoaderData* loader, InstanceKlass* klass); | |
| int num_loaders() const { return _loaders->length(); } | |
| ClassLoaderData* loader_data(int i) { return _loaders->at(i); } | |
| void add_loader_data(ClassLoaderData* p) { _loaders->push(p); } | |
| void remove_loader_at(int n) { | |
| assert(_loaders->at(n)->is_unloading(), "should be unloading"); | |
| _loaders->remove_at(n); | |
| } | |
| }; | |
| // For this class name, these are the set of LoaderConstraints for classes loaded with this name. | |
| class ConstraintSet { // copied into hashtable as value | |
| private: | |
| GrowableArray<LoaderConstraint*>* _constraints; // loader constraints for this class name. | |
| public: | |
| ConstraintSet() : _constraints(nullptr) {} | |
| ConstraintSet(const ConstraintSet&) = delete; | |
| ConstraintSet& operator=(const ConstraintSet&) = delete; | |
| void initialize(LoaderConstraint* constraint) { | |
| _constraints = new (mtClass) GrowableArray<LoaderConstraint*>(5, mtClass); | |
| _constraints->push(constraint); | |
| } | |
| ~ConstraintSet() { | |
| while (!_constraints->is_empty()) { | |
| delete _constraints->pop(); | |
| } | |
| delete _constraints; | |
| } | |
| int num_constraints() const { return _constraints->length(); } | |
| LoaderConstraint* constraint_at(int i) const { return _constraints->at(i); } | |
| void add_constraint(LoaderConstraint* new_constraint) { | |
| _constraints->push(new_constraint); | |
| } | |
| void remove_constraint(LoaderConstraint* constraint) { | |
| _constraints->remove(constraint); | |
| delete constraint; | |
| } | |
| }; | |
| using InternalLoaderConstraintTable = ResourceHashtable<SymbolHandle, ConstraintSet, 107, AnyObj::C_HEAP, mtClass, SymbolHandle::compute_hash>; | |
| static InternalLoaderConstraintTable* _loader_constraint_table; | |
| void LoaderConstraint::extend_loader_constraint(Symbol* class_name, | |
| ClassLoaderData* loader, | |
| InstanceKlass* klass) { | |
| add_loader_data(loader); | |
| LogTarget(Info, class, loader, constraints) lt; | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("extending constraint for name %s by adding loader: %s %s", | |
| class_name->as_C_string(), | |
| loader->loader_name_and_id(), | |
| _klass == nullptr ? " and setting class object" : ""); | |
| } | |
| if (_klass == nullptr) { | |
| set_klass(klass); | |
| } else { | |
| assert(klass == nullptr || _klass == klass, "constraints corrupted"); | |
| } | |
| } | |
| // The loaderConstraintTable must always be accessed with the | |
| // SystemDictionary lock held. This is true even for readers as | |
| // entries in the table could be being dynamically resized. | |
| void LoaderConstraintTable::initialize() { | |
| _loader_constraint_table = new (mtClass) InternalLoaderConstraintTable(); | |
| } | |
| LoaderConstraint* LoaderConstraintTable::find_loader_constraint( | |
| Symbol* name, ClassLoaderData* loader_data) { | |
| assert_lock_strong(SystemDictionary_lock); | |
| ConstraintSet* set = _loader_constraint_table->get(name); | |
| if (set == nullptr) { | |
| return nullptr; | |
| } | |
| for (int i = 0; i < set->num_constraints(); i++) { | |
| LoaderConstraint* p = set->constraint_at(i); | |
| for (int i = p->num_loaders() - 1; i >= 0; i--) { | |
| if (p->loader_data(i) == loader_data && | |
| // skip unloaded klasses | |
| (p->klass() == nullptr || | |
| p->klass()->is_loader_alive())) { | |
| return p; | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| // Either add it to an existing entry in the table or make a new one. | |
| void LoaderConstraintTable::add_loader_constraint(Symbol* name, InstanceKlass* klass, | |
| ClassLoaderData* loader1, ClassLoaderData* loader2) { | |
| assert_lock_strong(SystemDictionary_lock); | |
| LoaderConstraint* constraint = new LoaderConstraint(klass, loader1, loader2); | |
| // The klass may be null if it hasn't been loaded yet, for instance while checking | |
| // a parameter name to a method call. We impose this constraint that the | |
| // class that is eventually loaded must match between these two loaders. | |
| bool created; | |
| ConstraintSet* set = _loader_constraint_table->put_if_absent(name, &created); | |
| if (created) { | |
| set->initialize(constraint); | |
| } else { | |
| set->add_constraint(constraint); | |
| } | |
| } | |
| class PurgeUnloadedConstraints : public StackObj { | |
| public: | |
| bool do_entry(SymbolHandle& name, ConstraintSet& set) { | |
| LogTarget(Info, class, loader, constraints) lt; | |
| int len = set.num_constraints(); | |
| for (int i = len - 1; i >= 0; i--) { | |
| LoaderConstraint* probe = set.constraint_at(i); | |
| InstanceKlass* klass = probe->klass(); | |
| // Remove klass that is no longer alive | |
| if (klass != nullptr && | |
| !klass->is_loader_alive()) { | |
| probe->set_klass(nullptr); | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("purging class object from constraint for name %s," | |
| " loader list:", | |
| name->as_C_string()); | |
| for (int i = 0; i < probe->num_loaders(); i++) { | |
| lt.print(" [%d]: %s", i, | |
| probe->loader_data(i)->loader_name_and_id()); | |
| } | |
| } | |
| } | |
| // Remove entries no longer alive from loader array | |
| for (int n = probe->num_loaders() - 1; n >= 0; n--) { | |
| if (probe->loader_data(n)->is_unloading()) { | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("purging loader %s from constraint for name %s", | |
| probe->loader_data(n)->loader_name_and_id(), | |
| name->as_C_string()); | |
| } | |
| probe->remove_loader_at(n); | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("new loader list:"); | |
| for (int i = 0; i < probe->num_loaders(); i++) { | |
| lt.print(" [%d]: %s", i, | |
| probe->loader_data(i)->loader_name_and_id()); | |
| } | |
| } | |
| } | |
| } | |
| // Check whether the set should be purged | |
| if (probe->num_loaders() < 2) { | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("purging complete constraint for name %s", | |
| name->as_C_string()); | |
| } | |
| set.remove_constraint(probe); | |
| } else { | |
| #ifdef ASSERT | |
| if (probe->klass() != nullptr) { | |
| assert(probe->klass()->is_loader_alive(), "klass should be live"); | |
| } | |
| #endif | |
| } | |
| } | |
| if (set.num_constraints() == 0) { | |
| return true; | |
| } | |
| // Don't unlink this set | |
| return false; | |
| } | |
| }; | |
| void LoaderConstraintTable::purge_loader_constraints() { | |
| assert_locked_or_safepoint(SystemDictionary_lock); | |
| // Remove unloaded entries from constraint table | |
| PurgeUnloadedConstraints purge; | |
| _loader_constraint_table->unlink(&purge); | |
| } | |
| static void log_ldr_constraint_msg(Symbol* class_name, const char* reason, | |
| ClassLoaderData* loader1, ClassLoaderData* loader2) { | |
| LogTarget(Info, class, loader, constraints) lt; | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("Failed to add constraint for name: %s, loader[0]: %s," | |
| " loader[1]: %s, Reason: %s", | |
| class_name->as_C_string(), | |
| loader1->loader_name_and_id(), | |
| loader2->loader_name_and_id(), | |
| reason); | |
| } | |
| } | |
| bool LoaderConstraintTable::add_entry(Symbol* class_name, | |
| InstanceKlass* klass1, ClassLoaderData* loader1, | |
| InstanceKlass* klass2, ClassLoaderData* loader2) { | |
| LogTarget(Info, class, loader, constraints) lt; | |
| if (klass1 != nullptr && klass2 != nullptr) { | |
| if (klass1 == klass2) { | |
| // Same type already loaded in both places. There is no need for any constraint. | |
| return true; | |
| } else { | |
| log_ldr_constraint_msg(class_name, | |
| "The class objects presented by loader[0] and loader[1] " | |
| "are different", | |
| loader1, loader2); | |
| return false; | |
| } | |
| } | |
| InstanceKlass* klass = klass1 != nullptr ? klass1 : klass2; | |
| LoaderConstraint* pp1 = find_loader_constraint(class_name, loader1); | |
| if (pp1 != nullptr && pp1->klass() != nullptr) { | |
| if (klass != nullptr) { | |
| if (klass != pp1->klass()) { | |
| log_ldr_constraint_msg(class_name, | |
| "The class object presented by loader[0] does not match " | |
| "the stored class object in the constraint", | |
| loader1, loader2); | |
| return false; | |
| } | |
| } else { | |
| klass = pp1->klass(); | |
| } | |
| } | |
| LoaderConstraint* pp2 = find_loader_constraint(class_name, loader2); | |
| if (pp2 != nullptr && pp2->klass() != nullptr) { | |
| if (klass != nullptr) { | |
| if (klass != pp2->klass()) { | |
| log_ldr_constraint_msg(class_name, | |
| "The class object presented by loader[1] does not match " | |
| "the stored class object in the constraint", | |
| loader1, loader2); | |
| return false; | |
| } | |
| } else { | |
| klass = pp2->klass(); | |
| } | |
| } | |
| if (pp1 == nullptr && pp2 == nullptr) { | |
| add_loader_constraint(class_name, klass, loader1, loader2); | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("adding new constraint for name: %s, loader[0]: %s," | |
| " loader[1]: %s", | |
| class_name->as_C_string(), | |
| loader1->loader_name_and_id(), | |
| loader2->loader_name_and_id()); | |
| } | |
| } else if (pp1 == pp2) { | |
| /* constraint already imposed */ | |
| if (pp1->klass() == nullptr) { | |
| pp1->set_klass(klass); | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("setting class object in existing constraint for" | |
| " name: %s and loader %s", | |
| class_name->as_C_string(), | |
| loader1->loader_name_and_id()); | |
| } | |
| } else { | |
| assert(pp1->klass() == klass, "loader constraints corrupted"); | |
| } | |
| } else if (pp1 == nullptr) { | |
| pp2->extend_loader_constraint(class_name, loader1, klass); | |
| } else if (pp2 == nullptr) { | |
| pp1->extend_loader_constraint(class_name, loader2, klass); | |
| } else { | |
| merge_loader_constraints(class_name, pp1, pp2, klass); | |
| } | |
| return true; | |
| } | |
| // return true if the constraint was updated, false if the constraint is | |
| // violated | |
| bool LoaderConstraintTable::check_or_update(InstanceKlass* k, | |
| ClassLoaderData* loader, | |
| Symbol* name) { | |
| LogTarget(Info, class, loader, constraints) lt; | |
| LoaderConstraint* p = find_loader_constraint(name, loader); | |
| if (p && p->klass() != nullptr && p->klass() != k) { | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("constraint check failed for name %s, loader %s: " | |
| "the presented class object differs from that stored", | |
| name->as_C_string(), | |
| loader->loader_name_and_id()); | |
| } | |
| return false; | |
| } else { | |
| if (p && p->klass() == nullptr) { | |
| p->set_klass(k); | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("updating constraint for name %s, loader %s, " | |
| "by setting class object", | |
| name->as_C_string(), | |
| loader->loader_name_and_id()); | |
| } | |
| } | |
| return true; | |
| } | |
| } | |
| InstanceKlass* LoaderConstraintTable::find_constrained_klass(Symbol* name, | |
| ClassLoaderData* loader) { | |
| LoaderConstraint *p = find_loader_constraint(name, loader); | |
| if (p != nullptr && p->klass() != nullptr) { | |
| assert(p->klass()->is_instance_klass(), "sanity"); | |
| if (!p->klass()->is_loaded()) { | |
| // Only return fully loaded classes. Classes found through the | |
| // constraints might still be in the process of loading. | |
| return nullptr; | |
| } | |
| return p->klass(); | |
| } | |
| // No constraints, or else no klass loaded yet. | |
| return nullptr; | |
| } | |
| // Removes a class that was added to the table then class loading subsequently failed for this class, | |
| // so we don't have a dangling pointer to InstanceKlass in the LoaderConstraintTable. | |
| void LoaderConstraintTable::remove_failed_loaded_klass(InstanceKlass* klass, | |
| ClassLoaderData* loader) { | |
| MutexLocker ml(SystemDictionary_lock); | |
| Symbol* name = klass->name(); | |
| LoaderConstraint *p = find_loader_constraint(name, loader); | |
| if (p != nullptr && p->klass() != nullptr && p->klass() == klass) { | |
| // If this is the klass in the constraint, the error was OOM from the ClassLoader.addClass() call. | |
| // Other errors during loading (eg. constraint violations) will not have added this klass. | |
| log_info(class, loader, constraints)("removing klass %s: failed to load", name->as_C_string()); | |
| // We only null out the class, since the constraint for the class name for this loader is still valid as | |
| // it was added when checking signature loaders for a method or field resolution. | |
| p->set_klass(nullptr); | |
| } | |
| } | |
| void LoaderConstraintTable::merge_loader_constraints(Symbol* class_name, | |
| LoaderConstraint* p1, | |
| LoaderConstraint* p2, | |
| InstanceKlass* klass) { | |
| // Copy into the longer of the constraints. | |
| LoaderConstraint* dest = p1->num_loaders() <= p2->num_loaders() ? p2 : p1; | |
| LoaderConstraint* src = dest == p1 ? p2 : p1; | |
| for (int i = 0; i < src->num_loaders(); i++) { | |
| // We don't seem to care about duplicates. | |
| dest->add_loader_data(src->loader_data(i)); | |
| } | |
| LogTarget(Info, class, loader, constraints) lt; | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| lt.print("merged constraints for name %s, new loader list:", class_name->as_C_string()); | |
| for (int i = 0; i < dest->num_loaders(); i++) { | |
| lt.print(" [%d]: %s", i, dest->loader_data(i)->loader_name_and_id()); | |
| } | |
| if (dest->klass() == nullptr) { | |
| lt.print("... and setting class object"); | |
| } | |
| } | |
| // dest->klass() will hold null if klass, src->klass(), and old | |
| // dest->klass() are all null. In addition, all three must have | |
| // matching non-null values, otherwise either the constraints would | |
| // have been violated, or the constraints had been corrupted (and an | |
| // assertion would fail). | |
| if (src->klass() != nullptr) { | |
| assert(src->klass() == klass, "constraints corrupted"); | |
| } | |
| if (dest->klass() == nullptr) { | |
| dest->set_klass(klass); | |
| } else { | |
| assert(dest->klass() == klass, "constraints corrupted"); | |
| } | |
| // Remove src from set | |
| ConstraintSet* set = _loader_constraint_table->get(class_name); | |
| set->remove_constraint(src); | |
| } | |
| void LoaderConstraintTable::verify() { | |
| Thread* thread = Thread::current(); | |
| auto check = [&] (SymbolHandle& key, ConstraintSet& set) { | |
| // foreach constraint in the set, check the klass is in the dictionary or placeholder table. | |
| int len = set.num_constraints(); | |
| for (int i = 0; i < len; i++) { | |
| LoaderConstraint* probe = set.constraint_at(i); | |
| if (probe->klass() != nullptr) { | |
| InstanceKlass* ik = probe->klass(); | |
| guarantee(key == ik->name(), "name should match"); | |
| Symbol* name = ik->name(); | |
| ClassLoaderData* loader_data = ik->class_loader_data(); | |
| Dictionary* dictionary = loader_data->dictionary(); | |
| InstanceKlass* k = dictionary->find_class(thread, name); | |
| if (k != nullptr) { | |
| // We found the class in the dictionary, so we should | |
| // make sure that the Klass* matches what we already have. | |
| guarantee(k == probe->klass(), "klass should be in dictionary"); | |
| // If we don't find the class in the dictionary, it is | |
| // in the process of loading and may or may not be in the placeholder table. | |
| } | |
| } | |
| for (int n = 0; n< probe->num_loaders(); n++) { | |
| assert(ClassLoaderDataGraph::contains_loader_data(probe->loader_data(n)), "The loader is missing"); | |
| } | |
| } | |
| }; | |
| assert_locked_or_safepoint(SystemDictionary_lock); | |
| _loader_constraint_table->iterate_all(check); | |
| } | |
| void LoaderConstraintTable::print_table_statistics(outputStream* st) { | |
| auto size = [&] (SymbolHandle& key, ConstraintSet& set) { | |
| // sizeof set is included in the size of the hashtable node | |
| int sum = 0; | |
| int len = set.num_constraints(); | |
| for (int i = 0; i < len; i++) { | |
| LoaderConstraint* probe = set.constraint_at(i); | |
| sum += (int)(sizeof(*probe) + (probe->num_loaders() * sizeof(ClassLoaderData*))); | |
| } | |
| return sum; | |
| }; | |
| TableStatistics ts = _loader_constraint_table->statistics_calculate(size); | |
| ts.print(st, "LoaderConstraintTable"); | |
| } | |
| // Called with the system dictionary lock held | |
| void LoaderConstraintTable::print_on(outputStream* st) { | |
| auto printer = [&] (SymbolHandle& key, ConstraintSet& set) { | |
| int len = set.num_constraints(); | |
| for (int i = 0; i < len; i++) { | |
| LoaderConstraint* probe = set.constraint_at(i); | |
| st->print("Symbol: %s loaders:", key->as_C_string()); | |
| for (int n = 0; n < probe->num_loaders(); n++) { | |
| st->cr(); | |
| st->print(" "); | |
| probe->loader_data(n)->print_value_on(st); | |
| } | |
| st->cr(); | |
| } | |
| }; | |
| assert_locked_or_safepoint(SystemDictionary_lock); | |
| ResourceMark rm; | |
| st->print_cr("Java loader constraints (table_size=%d, constraints=%d)", | |
| _loader_constraint_table->table_size(), _loader_constraint_table->number_of_entries()); | |
| _loader_constraint_table->iterate_all(printer); | |
| } | |
| void LoaderConstraintTable::print() { print_on(tty); } |