-
Notifications
You must be signed in to change notification settings - Fork 6k
8278602: CDS dynamic dump may access unloaded classes #6859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…n goes through EligibleClassIterationHelper
👋 Welcome back iklam! A progress list of the required criteria for merging this PR into |
/label add hotspot |
@iklam |
@iklam |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reviewed the interaction of the klasses in the _dumptime_table with the new is_loader_alive() check. I don't know the reset of the CDS code to know if the other changes are correct or not. I spotted something that looks weird:
void SystemDictionaryShared::stop_dumping() { | ||
assert_lock_strong(DumpTimeTable_lock); | ||
_dump_in_progress = true; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you really intend to set _dump_in_progress to true in stop_dumping()? start_dumping() also sets it to true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it should be _dump_in_progress = false;
. I'll fix that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also add your unloads a lot test even though it doesn't reproduce this particular error without the ZGC change? It might find a similar bug under stress conditions.
assert(SafepointSynchronize::is_at_safepoint(), "invariant"); | ||
assert_lock_strong(DumpTimeTable_lock); | ||
if (k->is_loader_alive()) { | ||
assert(k->is_loader_alive(), "must be"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does seem a bit paranoid and redundant here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, that's was left over code. I'll remove it.
assert(k->is_loader_alive(), "must not change"); | ||
return result; | ||
} else { | ||
if (!SystemDictionaryShared::is_excluded_class(k)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this was the original bug? is_excluded_class() looks at mirror->signers() which if the class isn't alive, mirror->signers() will crash. This has to be in the k->is_loader_alive() too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_excluded_class() only checks the DumpTimeClassInfo::_is_excluded field. It doesn't examine the mirror->signers(). The crash happened with SystemDictionaryShared::check_excluded_classes(), which does examine the signers.
bool SystemDictionaryShared::is_excluded_class(InstanceKlass* k) {
assert(_no_class_loading_should_happen, "sanity");
assert_lock_strong(DumpTimeTable_lock);
Arguments::assert_is_dumping_archive();
DumpTimeClassInfo* p = find_or_allocate_info_for_locked(k);
return (p == NULL) ? true : p->is_excluded();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sorry I got the names confused.
OK, I'll add the test case. |
static double d = 123; | ||
static float f = 456; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the above declarations needed? They are not being used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are left overs. I've remove them.
public void doit(Runnable r) { | ||
r.run(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the above method being called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Just couple of questions on the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Thank you for adding the test.
assert(k->is_loader_alive(), "must not change"); | ||
return result; | ||
} else { | ||
if (!SystemDictionaryShared::is_excluded_class(k)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sorry I got the names confused.
@iklam This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 4 new commits pushed to the
Please see this link for an up-to-date comparison between the source branch of this pull request and the ➡️ To integrate this PR with the above commit message to the |
Thanks @stefank @calvinccheung @coleenp for the review. Latest version passed Mach5 CI tiers 1-4. |
Going to push as commit 09cf5f1.
Your commit was automatically rebased without conflicts. |
Cause of crash:
When dumping a CDS archive, while iterating over entries of the
SystemDictionaryShared::_dumptime_table
, we do not check whether the classes are already unloaded. In the crash, we are trying to callInstanceKlass::signer()
but the class has already been unloaded.Fix:
Override the template function
DumpTimeSharedClassTable::iterate
to ensure iteration safety. Do not iterate over a class if itsclass_loader_data
is no longer alive.The assert in
DumpTimeSharedClassTable::IterationHelper
found another existing bug -- we were callingSystemDictionaryShared::is_dumptime_table_empty()
without holding theDumpTimeTable_lock
. I delayed the call until we have grabbed the lock.Testing:
I have attached a test case into the bug report. Without the fix, it would reproduce the same crash in less than a minute. With the fix, the crash is no longer reproducible.
Unfortunately, the test case requires a ZGC patch (thanks to @stefank) that adds delays to increase the likelihood of seeing unloaded classes inside the
_dumptime_table
. Therefore, I cannot integrate the test as a jtreg test. I'll mark the bug as noreg-hardProgress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/6859/head:pull/6859
$ git checkout pull/6859
Update a local copy of the PR:
$ git checkout pull/6859
$ git pull https://git.openjdk.java.net/jdk pull/6859/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 6859
View PR using the GUI difftool:
$ git pr show -t 6859
Using diff file
Download this PR as a diff file:
https://git.openjdk.java.net/jdk/pull/6859.diff