[C++20][Modules] Prevent premature calls to PassInterestingDeclsToConsumer() within FinishedDeserializing(). (#129982)
`ASTReader::FinishedDeserializing` uses `NumCurrentElementsDeserializing` to keep track of nested `Deserializing` RAII actions. The `FinishedDeserializing` only performs actions if it is the top-level `Deserializing` layer. This works fine in general, but there is a problematic edge case. If a call to `redecls()` in `FinishedDeserializing` performs deserialization, we re-enter `FinishedDeserializing` while in the middle of the previous `FinishedDeserializing` call. The known problematic part of this is that this inner `FinishedDeserializing` can go all the way to `PassInterestingDeclsToConsumer`, which operates on `PotentiallyInterestingDecls` data structure which contain decls that should be handled by the previous `FinishedDeserializing` stage. The other shared data structures are also somewhat concerning at a high-level in that the inner `FinishedDeserializing` would be handling pending actions that are not "within its scope", but this part is not known to be problematic. We already have a guard within `PassInterestingDeclsToConsumer` because we can end up with recursive deserialization within `PassInterestingDeclsToConsumer`. The implemented solution is to apply this guard to the portion of `FinishedDeserializing` that performs further deserialization as well. This ensures that recursive deserialization does not trigger `PassInterestingDeclsToConsumer` which may operate on entries that are not ready to be passed.
This commit is contained in:
parent
508db53d1a
commit
0689d23ab3
@ -278,6 +278,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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -1176,11 +1176,11 @@ private:
|
||||
/// 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.
|
||||
|
@ -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 =
|
||||
@ -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,
|
||||
@ -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() {
|
||||
@ -10792,48 +10822,55 @@ 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);
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
|
||||
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();
|
||||
diagnoseOdrViolations();
|
||||
}
|
||||
|
||||
if (ReadTimer)
|
||||
ReadTimer->stopTimer();
|
||||
|
||||
diagnoseOdrViolations();
|
||||
|
||||
// We are not in recursive loading, so it's safe to pass the "interesting"
|
||||
// decls to the consumer.
|
||||
if (Consumer)
|
||||
|
@ -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.
|
||||
|
93
clang/test/Modules/pr121245.cpp
Normal file
93
clang/test/Modules/pr121245.cpp
Normal file
@ -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();
|
||||
}
|
107
clang/test/Modules/pr129982.cpp
Normal file
107
clang/test/Modules/pr129982.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
// If this test fails, it should be investigated under Debug builds.
|
||||
// Before the PR, this test was violating an assertion.
|
||||
|
||||
// RUN: rm -rf %t
|
||||
// RUN: mkdir -p %t
|
||||
// RUN: split-file %s %t
|
||||
|
||||
// RUN: %clang_cc1 -std=c++20 -emit-obj -fmodules \
|
||||
// RUN: -fmodule-map-file=%t/module.modulemap \
|
||||
// RUN: -fmodules-cache-path=%t %t/a.cpp
|
||||
|
||||
//--- module.modulemap
|
||||
module ebo {
|
||||
header "ebo.h"
|
||||
}
|
||||
|
||||
module fwd {
|
||||
header "fwd.h"
|
||||
}
|
||||
|
||||
module s {
|
||||
header "s.h"
|
||||
export *
|
||||
}
|
||||
|
||||
module mod {
|
||||
header "a.h"
|
||||
header "b.h"
|
||||
}
|
||||
|
||||
//--- ebo.h
|
||||
#pragma once
|
||||
|
||||
namespace N { inline namespace __1 {
|
||||
|
||||
template <typename T>
|
||||
struct EBO : T {
|
||||
EBO() = default;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
//--- fwd.h
|
||||
#pragma once
|
||||
|
||||
namespace N { inline namespace __1 {
|
||||
|
||||
template <typename T>
|
||||
struct Empty;
|
||||
|
||||
template <typename T>
|
||||
struct BS;
|
||||
|
||||
using S = BS<Empty<char>>;
|
||||
|
||||
}}
|
||||
|
||||
//--- s.h
|
||||
#pragma once
|
||||
|
||||
#include "fwd.h"
|
||||
#include "ebo.h"
|
||||
|
||||
namespace N { inline namespace __1 {
|
||||
|
||||
template <typename T>
|
||||
struct Empty {};
|
||||
|
||||
template <typename T>
|
||||
struct BS {
|
||||
EBO<T> _;
|
||||
void f();
|
||||
};
|
||||
|
||||
extern template void BS<Empty<char>>::f();
|
||||
|
||||
}}
|
||||
|
||||
//--- b.h
|
||||
#pragma once
|
||||
|
||||
#include "s.h"
|
||||
|
||||
struct B {
|
||||
void f() {
|
||||
N::S{}.f();
|
||||
}
|
||||
};
|
||||
|
||||
//--- a.h
|
||||
#pragma once
|
||||
|
||||
#include "s.h"
|
||||
|
||||
struct A {
|
||||
void f(int) {}
|
||||
void f(const N::S &) {}
|
||||
|
||||
void g();
|
||||
};
|
||||
|
||||
//--- a.cpp
|
||||
#include "a.h"
|
||||
|
||||
void A::g() { f(0); }
|
||||
|
||||
// expected-no-diagnostics
|
Loading…
x
Reference in New Issue
Block a user