Skip to content

[C++20][Modules] Prevent premature calls to PassInterestingDeclsToConsumer() within FinishedDeserializing(). #129982

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

Merged
merged 4 commits into from
Mar 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ Bug Fixes in This Version
considered an error in C23 mode and are allowed as an extension in earlier language modes.

- Remove the ``static`` specifier for the value of ``_FUNCTION_`` for static functions, in MSVC compatibility mode.
- Fixed a modules crash where exception specifications were not propagated properly (#GH121245, relanded in #GH129982)
- Fixed a problematic case with recursive deserialization within ``FinishedDeserializing()`` where
``PassInterestingDeclsToConsumer()`` was called before the declarations were safe to be passed. (#GH129982)

Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
8 changes: 4 additions & 4 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -1176,11 +1176,11 @@ class ASTReader
/// Number of Decl/types that are currently deserializing.
unsigned NumCurrentElementsDeserializing = 0;

/// Set true while we are in the process of passing deserialized
/// "interesting" decls to consumer inside FinishedDeserializing().
/// This is used as a guard to avoid recursively repeating the process of
/// Set false while we are in a state where we cannot safely pass deserialized
/// "interesting" decls to the consumer inside FinishedDeserializing().
/// This is used as a guard to avoid recursively entering the process of
/// passing decls to consumer.
bool PassingDeclsToConsumer = false;
bool CanPassDeclsToConsumer = true;

/// The set of identifiers that were read while the AST reader was
/// (recursively) loading declarations.
Expand Down
137 changes: 87 additions & 50 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10182,12 +10182,12 @@ void ASTReader::visitTopLevelModuleMaps(
}

void ASTReader::finishPendingActions() {
while (
!PendingIdentifierInfos.empty() || !PendingDeducedFunctionTypes.empty() ||
!PendingDeducedVarTypes.empty() || !PendingIncompleteDeclChains.empty() ||
!PendingDeclChains.empty() || !PendingMacroIDs.empty() ||
!PendingDeclContextInfos.empty() || !PendingUpdateRecords.empty() ||
!PendingObjCExtensionIvarRedeclarations.empty()) {
while (!PendingIdentifierInfos.empty() ||
!PendingDeducedFunctionTypes.empty() ||
!PendingDeducedVarTypes.empty() || !PendingDeclChains.empty() ||
!PendingMacroIDs.empty() || !PendingDeclContextInfos.empty() ||
!PendingUpdateRecords.empty() ||
!PendingObjCExtensionIvarRedeclarations.empty()) {
// If any identifiers with corresponding top-level declarations have
// been loaded, load those declarations now.
using TopLevelDeclsMap =
Expand Down Expand Up @@ -10235,13 +10235,6 @@ void ASTReader::finishPendingActions() {
}
PendingDeducedVarTypes.clear();

// For each decl chain that we wanted to complete while deserializing, mark
// it as "still needs to be completed".
for (unsigned I = 0; I != PendingIncompleteDeclChains.size(); ++I) {
markIncompleteDeclChain(PendingIncompleteDeclChains[I]);
}
PendingIncompleteDeclChains.clear();

// Load pending declaration chains.
for (unsigned I = 0; I != PendingDeclChains.size(); ++I)
loadPendingDeclChain(PendingDeclChains[I].first,
Expand Down Expand Up @@ -10479,6 +10472,43 @@ void ASTReader::finishPendingActions() {
for (auto *ND : PendingMergedDefinitionsToDeduplicate)
getContext().deduplicateMergedDefinitonsFor(ND);
PendingMergedDefinitionsToDeduplicate.clear();

// For each decl chain that we wanted to complete while deserializing, mark
// it as "still needs to be completed".
for (Decl *D : PendingIncompleteDeclChains)
markIncompleteDeclChain(D);
PendingIncompleteDeclChains.clear();

assert(PendingIdentifierInfos.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingDeducedFunctionTypes.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingDeducedVarTypes.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingDeclChains.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingMacroIDs.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingDeclContextInfos.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingUpdateRecords.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingObjCExtensionIvarRedeclarations.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingFakeDefinitionData.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingDefinitions.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingWarningForDuplicatedDefsInModuleUnits.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingBodies.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingAddedClassMembers.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingMergedDefinitionsToDeduplicate.empty() &&
"Should be empty at the end of finishPendingActions");
assert(PendingIncompleteDeclChains.empty() &&
"Should be empty at the end of finishPendingActions");
}

void ASTReader::diagnoseOdrViolations() {
Expand Down Expand Up @@ -10792,47 +10822,54 @@ void ASTReader::FinishedDeserializing() {
--NumCurrentElementsDeserializing;

if (NumCurrentElementsDeserializing == 0) {
// Propagate exception specification and deduced type updates along
// redeclaration chains.
//
// We do this now rather than in finishPendingActions because we want to
// be able to walk the complete redeclaration chains of the updated decls.
while (!PendingExceptionSpecUpdates.empty() ||
!PendingDeducedTypeUpdates.empty() ||
!PendingUndeducedFunctionDecls.empty()) {
auto ESUpdates = std::move(PendingExceptionSpecUpdates);
PendingExceptionSpecUpdates.clear();
for (auto Update : ESUpdates) {
ProcessingUpdatesRAIIObj ProcessingUpdates(*this);
auto *FPT = Update.second->getType()->castAs<FunctionProtoType>();
auto ESI = FPT->getExtProtoInfo().ExceptionSpec;
if (auto *Listener = getContext().getASTMutationListener())
Listener->ResolvedExceptionSpec(cast<FunctionDecl>(Update.second));
for (auto *Redecl : Update.second->redecls())
getContext().adjustExceptionSpec(cast<FunctionDecl>(Redecl), ESI);
}
{
// Guard variable to avoid recursively entering the process of passing
// decls to consumer.
SaveAndRestore GuardPassingDeclsToConsumer(CanPassDeclsToConsumer, false);

auto DTUpdates = std::move(PendingDeducedTypeUpdates);
PendingDeducedTypeUpdates.clear();
for (auto Update : DTUpdates) {
ProcessingUpdatesRAIIObj ProcessingUpdates(*this);
// FIXME: If the return type is already deduced, check that it matches.
getContext().adjustDeducedFunctionResultType(Update.first,
Update.second);
}
// Propagate exception specification and deduced type updates along
// redeclaration chains.
//
// We do this now rather than in finishPendingActions because we want to
// be able to walk the complete redeclaration chains of the updated decls.
while (!PendingExceptionSpecUpdates.empty() ||
!PendingDeducedTypeUpdates.empty() ||
!PendingUndeducedFunctionDecls.empty()) {
auto ESUpdates = std::move(PendingExceptionSpecUpdates);
PendingExceptionSpecUpdates.clear();
for (auto Update : ESUpdates) {
ProcessingUpdatesRAIIObj ProcessingUpdates(*this);
auto *FPT = Update.second->getType()->castAs<FunctionProtoType>();
auto ESI = FPT->getExtProtoInfo().ExceptionSpec;
if (auto *Listener = getContext().getASTMutationListener())
Listener->ResolvedExceptionSpec(cast<FunctionDecl>(Update.second));
for (auto *Redecl : Update.second->redecls())
getContext().adjustExceptionSpec(cast<FunctionDecl>(Redecl), ESI);
}

auto UDTUpdates = std::move(PendingUndeducedFunctionDecls);
PendingUndeducedFunctionDecls.clear();
// We hope we can find the deduced type for the functions by iterating
// redeclarations in other modules.
for (FunctionDecl *UndeducedFD : UDTUpdates)
(void)UndeducedFD->getMostRecentDecl();
}
auto DTUpdates = std::move(PendingDeducedTypeUpdates);
PendingDeducedTypeUpdates.clear();
for (auto Update : DTUpdates) {
ProcessingUpdatesRAIIObj ProcessingUpdates(*this);
// FIXME: If the return type is already deduced, check that it
// matches.
getContext().adjustDeducedFunctionResultType(Update.first,
Update.second);
}

if (ReadTimer)
ReadTimer->stopTimer();
auto UDTUpdates = std::move(PendingUndeducedFunctionDecls);
PendingUndeducedFunctionDecls.clear();
// We hope we can find the deduced type for the functions by iterating
// redeclarations in other modules.
for (FunctionDecl *UndeducedFD : UDTUpdates)
(void)UndeducedFD->getMostRecentDecl();
}

if (ReadTimer)
ReadTimer->stopTimer();

diagnoseOdrViolations();
diagnoseOdrViolations();
}

// We are not in recursive loading, so it's safe to pass the "interesting"
// decls to the consumer.
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4309,12 +4309,12 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) {
void ASTReader::PassInterestingDeclsToConsumer() {
assert(Consumer);

if (PassingDeclsToConsumer)
if (!CanPassDeclsToConsumer)
return;

// Guard variable to avoid recursively redoing the process of passing
// decls to consumer.
SaveAndRestore GuardPassingDeclsToConsumer(PassingDeclsToConsumer, true);
SaveAndRestore GuardPassingDeclsToConsumer(CanPassDeclsToConsumer, false);

// Ensure that we've loaded all potentially-interesting declarations
// that need to be eagerly loaded.
Expand Down
93 changes: 93 additions & 0 deletions clang/test/Modules/pr121245.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// If this test fails, it should be investigated under Debug builds.
// Before the PR, this test was encountering an `llvm_unreachable()`.

// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
// RUN: cd %t

// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header %t/hu-01.h \
// RUN: -fcxx-exceptions -o %t/hu-01.pcm

// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header %t/hu-02.h \
// RUN: -Wno-experimental-header-units -fcxx-exceptions \
// RUN: -fmodule-file=%t/hu-01.pcm -o %t/hu-02.pcm

// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header %t/hu-03.h \
// RUN: -Wno-experimental-header-units -fcxx-exceptions \
// RUN: -fmodule-file=%t/hu-01.pcm -o %t/hu-03.pcm

// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header %t/hu-04.h \
// RUN: -Wno-experimental-header-units -fcxx-exceptions \
// RUN: -fmodule-file=%t/hu-01.pcm -o %t/hu-04.pcm

// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header %t/hu-05.h \
// RUN: -Wno-experimental-header-units -fcxx-exceptions \
// RUN: -fmodule-file=%t/hu-03.pcm -fmodule-file=%t/hu-04.pcm \
// RUN: -fmodule-file=%t/hu-01.pcm -o %t/hu-05.pcm

// RUN: %clang_cc1 -std=c++20 -emit-obj %t/main.cpp \
// RUN: -Wno-experimental-header-units -fcxx-exceptions \
// RUN: -fmodule-file=%t/hu-02.pcm -fmodule-file=%t/hu-05.pcm \
// RUN: -fmodule-file=%t/hu-04.pcm -fmodule-file=%t/hu-03.pcm \
// RUN: -fmodule-file=%t/hu-01.pcm

//--- hu-01.h
template <typename T>
struct A {
A() {}
~A() {}
};

template <typename T>
struct EBO : T {
EBO() = default;
};

template <typename T>
struct HT : EBO<A<T>> {};

//--- hu-02.h
import "hu-01.h";

inline void f() {
HT<int>();
}

//--- hu-03.h
import "hu-01.h";

struct C {
C();

HT<long> _;
};

//--- hu-04.h
import "hu-01.h";

void g(HT<long> = {});

//--- hu-05.h
import "hu-03.h";
import "hu-04.h";
import "hu-01.h";

struct B {
virtual ~B() = default;

virtual void f() {
HT<long>();
}
};

//--- main.cpp
import "hu-02.h";
import "hu-05.h";
import "hu-03.h";

int main() {
f();
C();
B();
}
Loading