Skip to content

Commit

Permalink
8313678: SymbolTable can leak Symbols during cleanup
Browse files Browse the repository at this point in the history
Reviewed-by: coleenp, dholmes, shade
  • Loading branch information
olivergillespie authored and shipilev committed Aug 14, 2023
1 parent f41c267 commit 4b2703a
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 29 deletions.
6 changes: 4 additions & 2 deletions src/hotspot/share/classfile/dictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,13 @@ class DictionaryLookup : StackObj {
uintx get_hash() const {
return _name->identity_hash();
}
bool equals(DictionaryEntry** value, bool* is_dead) {
bool equals(DictionaryEntry** value) {
DictionaryEntry *entry = *value;
*is_dead = false;
return (entry->instance_klass()->name() == _name);
}
bool is_dead(DictionaryEntry** value) {
return false;
}
};

// Add a loaded class to the dictionary.
Expand Down
17 changes: 11 additions & 6 deletions src/hotspot/share/classfile/stringTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,9 @@ class StringTableLookupJchar : StackObj {
uintx get_hash() const {
return _hash;
}
bool equals(WeakHandle* value, bool* is_dead) {
bool equals(WeakHandle* value) {
oop val_oop = value->peek();
if (val_oop == nullptr) {
// dead oop, mark this hash dead for cleaning
*is_dead = true;
return false;
}
bool equals = java_lang_String::equals(val_oop, _str, _len);
Expand All @@ -191,6 +189,10 @@ class StringTableLookupJchar : StackObj {
_found = Handle(_thread, value->resolve());
return true;
}
bool is_dead(WeakHandle* value) {
oop val_oop = value->peek();
return val_oop == nullptr;
}
};

class StringTableLookupOop : public StackObj {
Expand All @@ -208,11 +210,9 @@ class StringTableLookupOop : public StackObj {
return _hash;
}

bool equals(WeakHandle* value, bool* is_dead) {
bool equals(WeakHandle* value) {
oop val_oop = value->peek();
if (val_oop == nullptr) {
// dead oop, mark this hash dead for cleaning
*is_dead = true;
return false;
}
bool equals = java_lang_String::equals(_find(), val_oop);
Expand All @@ -223,6 +223,11 @@ class StringTableLookupOop : public StackObj {
_found = Handle(_thread, value->resolve());
return true;
}

bool is_dead(WeakHandle* value) {
oop val_oop = value->peek();
return val_oop == nullptr;
}
};

void StringTable::create_table() {
Expand Down
11 changes: 8 additions & 3 deletions src/hotspot/share/classfile/symbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,11 @@ class SymbolTableLookup : StackObj {
uintx get_hash() const {
return _hash;
}
bool equals(Symbol* value, bool* is_dead) {
// Note: When equals() returns "true", the symbol's refcount is incremented. This is
// needed to ensure that the symbol is kept alive before equals() returns to the caller,
// so that another thread cannot clean the symbol up concurrently. The caller is
// responsible for decrementing the refcount, when the symbol is no longer needed.
bool equals(Symbol* value) {
assert(value != nullptr, "expected valid value");
Symbol *sym = value;
if (sym->equals(_str, _len)) {
Expand All @@ -383,14 +387,15 @@ class SymbolTableLookup : StackObj {
return true;
} else {
assert(sym->refcount() == 0, "expected dead symbol");
*is_dead = true;
return false;
}
} else {
*is_dead = (sym->refcount() == 0);
return false;
}
}
bool is_dead(Symbol* value) {
return value->refcount() == 0;
}
};

class SymbolTableGet : public StackObj {
Expand Down
7 changes: 5 additions & 2 deletions src/hotspot/share/gc/g1/g1CardSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,13 @@ class G1CardSetHashTable : public CHeapObj<mtGCCardSet> {

uintx get_hash() const { return G1CardSetHashTable::get_hash(_region_idx); }

bool equals(G1CardSetHashTableValue* value, bool* is_dead) {
*is_dead = false;
bool equals(G1CardSetHashTableValue* value) {
return value->_region_idx == _region_idx;
}

bool is_dead(G1CardSetHashTableValue*) {
return false;
}
};

class G1CardSetHashTableFound : public StackObj {
Expand Down
8 changes: 5 additions & 3 deletions src/hotspot/share/prims/resolvedMethodTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,9 @@ class ResolvedMethodTableLookup : StackObj {
uintx get_hash() const {
return _hash;
}
bool equals(WeakHandle* value, bool* is_dead) {
bool equals(WeakHandle* value) {
oop val_oop = value->peek();
if (val_oop == nullptr) {
// dead oop, mark this hash dead for cleaning
*is_dead = true;
return false;
}
bool equals = _method == java_lang_invoke_ResolvedMethodName::vmtarget(val_oop);
Expand All @@ -141,6 +139,10 @@ class ResolvedMethodTableLookup : StackObj {
_found = Handle(_thread, value->resolve());
return true;
}
bool is_dead(WeakHandle* value) {
oop val_oop = value->peek();
return val_oop == nullptr;
}
};


Expand Down
5 changes: 4 additions & 1 deletion src/hotspot/share/services/finalizerService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,14 @@ class FinalizerEntryLookup : StackObj {
public:
FinalizerEntryLookup(const InstanceKlass* ik) : _ik(ik) {}
uintx get_hash() const { return hash_function(_ik); }
bool equals(FinalizerEntry** value, bool* is_dead) {
bool equals(FinalizerEntry** value) {
assert(value != nullptr, "invariant");
assert(*value != nullptr, "invariant");
return (*value)->klass() == _ik;
}
bool is_dead(FinalizerEntry** value) {
return false;
}
};

class FinalizerTableConfig : public AllStatic {
Expand Down
5 changes: 4 additions & 1 deletion src/hotspot/share/services/threadIdTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,16 @@ class ThreadIdTableLookup : public StackObj {
uintx get_hash() const {
return _hash;
}
bool equals(ThreadIdTableEntry** value, bool* is_dead) {
bool equals(ThreadIdTableEntry** value) {
bool equals = primitive_equals(_tid, (*value)->tid());
if (!equals) {
return false;
}
return true;
}
bool is_dead(ThreadIdTableEntry** value) {
return false;
}
};

class ThreadGet : public StackObj {
Expand Down
12 changes: 4 additions & 8 deletions src/hotspot/share/utilities/concurrentHashTable.inline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,8 @@ inline bool ConcurrentHashTable<CONFIG, F>::
assert(bucket->is_locked(), "Must be locked.");
Node* const volatile * rem_n_prev = bucket->first_ptr();
Node* rem_n = bucket->first();
bool have_dead = false;
while (rem_n != nullptr) {
if (lookup_f.equals(rem_n->value(), &have_dead)) {
if (lookup_f.equals(rem_n->value())) {
bucket->release_assign_node_ptr(rem_n_prev, rem_n->next());
break;
} else {
Expand Down Expand Up @@ -546,9 +545,7 @@ inline void ConcurrentHashTable<CONFIG, F>::
Node* const volatile * rem_n_prev = bucket->first_ptr();
Node* rem_n = bucket->first();
while (rem_n != nullptr) {
bool is_dead = false;
lookup_f.equals(rem_n->value(), &is_dead);
if (is_dead) {
if (lookup_f.is_dead(rem_n->value())) {
ndel[dels++] = rem_n;
Node* next_node = rem_n->next();
bucket->release_assign_node_ptr(rem_n_prev, next_node);
Expand Down Expand Up @@ -626,12 +623,11 @@ ConcurrentHashTable<CONFIG, F>::
size_t loop_count = 0;
Node* node = bucket->first();
while (node != nullptr) {
bool is_dead = false;
++loop_count;
if (lookup_f.equals(node->value(), &is_dead)) {
if (lookup_f.equals(node->value())) {
break;
}
if (is_dead && !(*have_dead)) {
if (!(*have_dead) && lookup_f.is_dead(node->value())) {
*have_dead = true;
}
node = node->next();
Expand Down
14 changes: 14 additions & 0 deletions test/hotspot/gtest/classfile/test_symbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,17 @@ TEST_VM_FATAL_ERROR_MSG(SymbolTable, test_symbol_underflow, ".*refcount has gone
my_symbol->decrement_refcount();
my_symbol->increment_refcount(); // Should crash even in PRODUCT mode
}

TEST_VM(SymbolTable, test_cleanup_leak) {
// Check that dead entry cleanup doesn't increment refcount of live entry in same bucket.

// Create symbol and release ref, marking it available for cleanup.
Symbol* entry1 = SymbolTable::new_symbol("hash_collision_123");
entry1->decrement_refcount();

// Create a new symbol in the same bucket, which will notice the dead entry and trigger cleanup.
// Note: relies on SymbolTable's use of String::hashCode which collides for these two values.
Symbol* entry2 = SymbolTable::new_symbol("hash_collision_397476851");

ASSERT_EQ(entry2->refcount(), 1) << "Symbol refcount just created is 1";
}
10 changes: 8 additions & 2 deletions test/hotspot/gtest/utilities/test_concurrentHashtable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,12 @@ struct SimpleTestLookup {
uintx get_hash() {
return Pointer::get_hash(_val, NULL);
}
bool equals(const uintptr_t* value, bool* is_dead) {
bool equals(const uintptr_t* value) {
return _val == *value;
}
bool is_dead(const uintptr_t* value) {
return false;
}
};

struct ValueGet {
Expand Down Expand Up @@ -561,9 +564,12 @@ struct TestLookup {
uintx get_hash() {
return TestInterface::get_hash(_val, NULL);
}
bool equals(const uintptr_t* value, bool* is_dead) {
bool equals(const uintptr_t* value) {
return _val == *value;
}
bool is_dead(const uintptr_t* value) {
return false;
}
};

static uintptr_t cht_get_copy(TestTable* cht, Thread* thr, TestLookup tl) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private static void doTest(String topArchiveName) throws Exception {
ProcessBuilder pb = new ProcessBuilder();
pb.command(new String[] {JDKToolFinder.getJDKTool("jcmd"), Long.toString(pid), "VM.symboltable", "-verbose"});
OutputAnalyzer output = CDSTestUtils.executeAndLog(pb, "jcmd-symboltable");
output.shouldContain("17 3: jdk/test/lib/apps\n");
output.shouldContain("17 2: jdk/test/lib/apps\n");
output.shouldContain("Dynamic shared symbols:\n");
output.shouldContain("5 65535: Hello\n");

Expand Down

1 comment on commit 4b2703a

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.