blob: 3a34800c05172eed2ab23beb196195d9b365f86a [file] [log] [blame]
/*
* Copyright (c) 2003, 2024, 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 "precompiled.hpp"
#include "classfile/classLoaderData.inline.hpp"
#include "classfile/classLoaderDataGraph.hpp"
#include "classfile/dictionary.hpp"
#include "classfile/loaderConstraints.hpp"
#include "classfile/placeholders.hpp"
#include "logging/log.hpp"
#include "memory/resourceArea.hpp"
#include "oops/klass.inline.hpp"
#include "oops/oop.inline.hpp"
#include "oops/symbolHandle.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/safepoint.hpp"
#include "utilities/resourceHash.hpp"
// Implementation Classes for Loader Constraints
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);
}
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;
}
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");
} else {
// If we don't find the class in the dictionary, it
// has to be in the placeholders table.
PlaceholderEntry* entry = PlaceholderTable::get_entry(name, loader_data);
// The InstanceKlass might not be on the entry, so the only
// thing we can check here is whether we were successful in
// finding the class in the placeholders table.
guarantee(entry != nullptr, "klass should be in the placeholders");
}
}
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 += 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); }