Skip to content

Commit 4b2703a

Browse files
olivergillespieshipilev
authored andcommittedAug 14, 2023
8313678: SymbolTable can leak Symbols during cleanup
Reviewed-by: coleenp, dholmes, shade
1 parent f41c267 commit 4b2703a

File tree

11 files changed

+68
-29
lines changed

11 files changed

+68
-29
lines changed
 

‎src/hotspot/share/classfile/dictionary.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,13 @@ class DictionaryLookup : StackObj {
229229
uintx get_hash() const {
230230
return _name->identity_hash();
231231
}
232-
bool equals(DictionaryEntry** value, bool* is_dead) {
232+
bool equals(DictionaryEntry** value) {
233233
DictionaryEntry *entry = *value;
234-
*is_dead = false;
235234
return (entry->instance_klass()->name() == _name);
236235
}
236+
bool is_dead(DictionaryEntry** value) {
237+
return false;
238+
}
237239
};
238240

239241
// Add a loaded class to the dictionary.

‎src/hotspot/share/classfile/stringTable.cpp

+11-6
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,9 @@ class StringTableLookupJchar : StackObj {
176176
uintx get_hash() const {
177177
return _hash;
178178
}
179-
bool equals(WeakHandle* value, bool* is_dead) {
179+
bool equals(WeakHandle* value) {
180180
oop val_oop = value->peek();
181181
if (val_oop == nullptr) {
182-
// dead oop, mark this hash dead for cleaning
183-
*is_dead = true;
184182
return false;
185183
}
186184
bool equals = java_lang_String::equals(val_oop, _str, _len);
@@ -191,6 +189,10 @@ class StringTableLookupJchar : StackObj {
191189
_found = Handle(_thread, value->resolve());
192190
return true;
193191
}
192+
bool is_dead(WeakHandle* value) {
193+
oop val_oop = value->peek();
194+
return val_oop == nullptr;
195+
}
194196
};
195197

196198
class StringTableLookupOop : public StackObj {
@@ -208,11 +210,9 @@ class StringTableLookupOop : public StackObj {
208210
return _hash;
209211
}
210212

211-
bool equals(WeakHandle* value, bool* is_dead) {
213+
bool equals(WeakHandle* value) {
212214
oop val_oop = value->peek();
213215
if (val_oop == nullptr) {
214-
// dead oop, mark this hash dead for cleaning
215-
*is_dead = true;
216216
return false;
217217
}
218218
bool equals = java_lang_String::equals(_find(), val_oop);
@@ -223,6 +223,11 @@ class StringTableLookupOop : public StackObj {
223223
_found = Handle(_thread, value->resolve());
224224
return true;
225225
}
226+
227+
bool is_dead(WeakHandle* value) {
228+
oop val_oop = value->peek();
229+
return val_oop == nullptr;
230+
}
226231
};
227232

228233
void StringTable::create_table() {

‎src/hotspot/share/classfile/symbolTable.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,11 @@ class SymbolTableLookup : StackObj {
374374
uintx get_hash() const {
375375
return _hash;
376376
}
377-
bool equals(Symbol* value, bool* is_dead) {
377+
// Note: When equals() returns "true", the symbol's refcount is incremented. This is
378+
// needed to ensure that the symbol is kept alive before equals() returns to the caller,
379+
// so that another thread cannot clean the symbol up concurrently. The caller is
380+
// responsible for decrementing the refcount, when the symbol is no longer needed.
381+
bool equals(Symbol* value) {
378382
assert(value != nullptr, "expected valid value");
379383
Symbol *sym = value;
380384
if (sym->equals(_str, _len)) {
@@ -383,14 +387,15 @@ class SymbolTableLookup : StackObj {
383387
return true;
384388
} else {
385389
assert(sym->refcount() == 0, "expected dead symbol");
386-
*is_dead = true;
387390
return false;
388391
}
389392
} else {
390-
*is_dead = (sym->refcount() == 0);
391393
return false;
392394
}
393395
}
396+
bool is_dead(Symbol* value) {
397+
return value->refcount() == 0;
398+
}
394399
};
395400

396401
class SymbolTableGet : public StackObj {

‎src/hotspot/share/gc/g1/g1CardSet.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,13 @@ class G1CardSetHashTable : public CHeapObj<mtGCCardSet> {
258258

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

261-
bool equals(G1CardSetHashTableValue* value, bool* is_dead) {
262-
*is_dead = false;
261+
bool equals(G1CardSetHashTableValue* value) {
263262
return value->_region_idx == _region_idx;
264263
}
264+
265+
bool is_dead(G1CardSetHashTableValue*) {
266+
return false;
267+
}
265268
};
266269

267270
class G1CardSetHashTableFound : public StackObj {

‎src/hotspot/share/prims/resolvedMethodTable.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,9 @@ class ResolvedMethodTableLookup : StackObj {
126126
uintx get_hash() const {
127127
return _hash;
128128
}
129-
bool equals(WeakHandle* value, bool* is_dead) {
129+
bool equals(WeakHandle* value) {
130130
oop val_oop = value->peek();
131131
if (val_oop == nullptr) {
132-
// dead oop, mark this hash dead for cleaning
133-
*is_dead = true;
134132
return false;
135133
}
136134
bool equals = _method == java_lang_invoke_ResolvedMethodName::vmtarget(val_oop);
@@ -141,6 +139,10 @@ class ResolvedMethodTableLookup : StackObj {
141139
_found = Handle(_thread, value->resolve());
142140
return true;
143141
}
142+
bool is_dead(WeakHandle* value) {
143+
oop val_oop = value->peek();
144+
return val_oop == nullptr;
145+
}
144146
};
145147

146148

‎src/hotspot/share/services/finalizerService.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,14 @@ class FinalizerEntryLookup : StackObj {
137137
public:
138138
FinalizerEntryLookup(const InstanceKlass* ik) : _ik(ik) {}
139139
uintx get_hash() const { return hash_function(_ik); }
140-
bool equals(FinalizerEntry** value, bool* is_dead) {
140+
bool equals(FinalizerEntry** value) {
141141
assert(value != nullptr, "invariant");
142142
assert(*value != nullptr, "invariant");
143143
return (*value)->klass() == _ik;
144144
}
145+
bool is_dead(FinalizerEntry** value) {
146+
return false;
147+
}
145148
};
146149

147150
class FinalizerTableConfig : public AllStatic {

‎src/hotspot/share/services/threadIdTable.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,16 @@ class ThreadIdTableLookup : public StackObj {
187187
uintx get_hash() const {
188188
return _hash;
189189
}
190-
bool equals(ThreadIdTableEntry** value, bool* is_dead) {
190+
bool equals(ThreadIdTableEntry** value) {
191191
bool equals = primitive_equals(_tid, (*value)->tid());
192192
if (!equals) {
193193
return false;
194194
}
195195
return true;
196196
}
197+
bool is_dead(ThreadIdTableEntry** value) {
198+
return false;
199+
}
197200
};
198201

199202
class ThreadGet : public StackObj {

‎src/hotspot/share/utilities/concurrentHashTable.inline.hpp

+4-8
Original file line numberDiff line numberDiff line change
@@ -455,9 +455,8 @@ inline bool ConcurrentHashTable<CONFIG, F>::
455455
assert(bucket->is_locked(), "Must be locked.");
456456
Node* const volatile * rem_n_prev = bucket->first_ptr();
457457
Node* rem_n = bucket->first();
458-
bool have_dead = false;
459458
while (rem_n != nullptr) {
460-
if (lookup_f.equals(rem_n->value(), &have_dead)) {
459+
if (lookup_f.equals(rem_n->value())) {
461460
bucket->release_assign_node_ptr(rem_n_prev, rem_n->next());
462461
break;
463462
} else {
@@ -546,9 +545,7 @@ inline void ConcurrentHashTable<CONFIG, F>::
546545
Node* const volatile * rem_n_prev = bucket->first_ptr();
547546
Node* rem_n = bucket->first();
548547
while (rem_n != nullptr) {
549-
bool is_dead = false;
550-
lookup_f.equals(rem_n->value(), &is_dead);
551-
if (is_dead) {
548+
if (lookup_f.is_dead(rem_n->value())) {
552549
ndel[dels++] = rem_n;
553550
Node* next_node = rem_n->next();
554551
bucket->release_assign_node_ptr(rem_n_prev, next_node);
@@ -626,12 +623,11 @@ ConcurrentHashTable<CONFIG, F>::
626623
size_t loop_count = 0;
627624
Node* node = bucket->first();
628625
while (node != nullptr) {
629-
bool is_dead = false;
630626
++loop_count;
631-
if (lookup_f.equals(node->value(), &is_dead)) {
627+
if (lookup_f.equals(node->value())) {
632628
break;
633629
}
634-
if (is_dead && !(*have_dead)) {
630+
if (!(*have_dead) && lookup_f.is_dead(node->value())) {
635631
*have_dead = true;
636632
}
637633
node = node->next();

‎test/hotspot/gtest/classfile/test_symbolTable.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,17 @@ TEST_VM_FATAL_ERROR_MSG(SymbolTable, test_symbol_underflow, ".*refcount has gone
125125
my_symbol->decrement_refcount();
126126
my_symbol->increment_refcount(); // Should crash even in PRODUCT mode
127127
}
128+
129+
TEST_VM(SymbolTable, test_cleanup_leak) {
130+
// Check that dead entry cleanup doesn't increment refcount of live entry in same bucket.
131+
132+
// Create symbol and release ref, marking it available for cleanup.
133+
Symbol* entry1 = SymbolTable::new_symbol("hash_collision_123");
134+
entry1->decrement_refcount();
135+
136+
// Create a new symbol in the same bucket, which will notice the dead entry and trigger cleanup.
137+
// Note: relies on SymbolTable's use of String::hashCode which collides for these two values.
138+
Symbol* entry2 = SymbolTable::new_symbol("hash_collision_397476851");
139+
140+
ASSERT_EQ(entry2->refcount(), 1) << "Symbol refcount just created is 1";
141+
}

‎test/hotspot/gtest/utilities/test_concurrentHashtable.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,12 @@ struct SimpleTestLookup {
107107
uintx get_hash() {
108108
return Pointer::get_hash(_val, NULL);
109109
}
110-
bool equals(const uintptr_t* value, bool* is_dead) {
110+
bool equals(const uintptr_t* value) {
111111
return _val == *value;
112112
}
113+
bool is_dead(const uintptr_t* value) {
114+
return false;
115+
}
113116
};
114117

115118
struct ValueGet {
@@ -561,9 +564,12 @@ struct TestLookup {
561564
uintx get_hash() {
562565
return TestInterface::get_hash(_val, NULL);
563566
}
564-
bool equals(const uintptr_t* value, bool* is_dead) {
567+
bool equals(const uintptr_t* value) {
565568
return _val == *value;
566569
}
570+
bool is_dead(const uintptr_t* value) {
571+
return false;
572+
}
567573
};
568574

569575
static uintptr_t cht_get_copy(TestTable* cht, Thread* thr, TestLookup tl) {

‎test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicSharedSymbols.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private static void doTest(String topArchiveName) throws Exception {
9090
ProcessBuilder pb = new ProcessBuilder();
9191
pb.command(new String[] {JDKToolFinder.getJDKTool("jcmd"), Long.toString(pid), "VM.symboltable", "-verbose"});
9292
OutputAnalyzer output = CDSTestUtils.executeAndLog(pb, "jcmd-symboltable");
93-
output.shouldContain("17 3: jdk/test/lib/apps\n");
93+
output.shouldContain("17 2: jdk/test/lib/apps\n");
9494
output.shouldContain("Dynamic shared symbols:\n");
9595
output.shouldContain("5 65535: Hello\n");
9696

0 commit comments

Comments
 (0)
Failed to load comments.