Skip to content

Commit 1ad4b99

Browse files
author
Gabor Marton
committed
[ASTImporter] Mark erroneous nodes in from ctx
Summary: During import of a specific Decl D, it may happen that some AST nodes had already been created before we recognize an error. In this case we signal back the error to the caller, but the "to" context remains polluted with those nodes which had been created. Ideally, those nodes should not had been created, but that time we did not know about the error, the error happened later. Since the AST is immutable (most of the cases we can't remove existing nodes) we choose to mark these nodes as erroneous. Here are the steps of the algorithm: 1) We keep track of the nodes which we visit during the import of D: See ImportPathTy. 2) If a Decl is already imported and it is already on the import path (we have a cycle) then we copy/store the relevant part of the import path. We store these cycles for each Decl. 3) When we recognize an error during the import of D then we set up this error to all Decls in the stored cycles for D and we clear the stored cycles. Reviewers: a_sidorin, a.sidorin, shafik Subscribers: rnkovacs, dkrupp, Szelethus, gamesh411, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D62375 llvm-svn: 364771
1 parent 511ad50 commit 1ad4b99

File tree

3 files changed

+351
-22
lines changed

3 files changed

+351
-22
lines changed

clang/include/clang/AST/ASTImporter.h

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,127 @@ class TypeSourceInfo;
8787
using ImportedCXXBaseSpecifierMap =
8888
llvm::DenseMap<const CXXBaseSpecifier *, CXXBaseSpecifier *>;
8989

90+
// An ImportPath is the list of the AST nodes which we visit during an
91+
// Import call.
92+
// If node `A` depends on node `B` then the path contains an `A`->`B` edge.
93+
// From the call stack of the import functions we can read the very same
94+
// path.
95+
//
96+
// Now imagine the following AST, where the `->` represents dependency in
97+
// therms of the import.
98+
// ```
99+
// A->B->C->D
100+
// `->E
101+
// ```
102+
// We would like to import A.
103+
// The import behaves like a DFS, so we will visit the nodes in this order:
104+
// ABCDE.
105+
// During the visitation we will have the following ImportPaths:
106+
// ```
107+
// A
108+
// AB
109+
// ABC
110+
// ABCD
111+
// ABC
112+
// AB
113+
// ABE
114+
// AB
115+
// A
116+
// ```
117+
// If during the visit of E there is an error then we set an error for E,
118+
// then as the call stack shrinks for B, then for A:
119+
// ```
120+
// A
121+
// AB
122+
// ABC
123+
// ABCD
124+
// ABC
125+
// AB
126+
// ABE // Error! Set an error to E
127+
// AB // Set an error to B
128+
// A // Set an error to A
129+
// ```
130+
// However, during the import we could import C and D without any error and
131+
// they are independent from A,B and E.
132+
// We must not set up an error for C and D.
133+
// So, at the end of the import we have an entry in `ImportDeclErrors` for
134+
// A,B,E but not for C,D.
135+
//
136+
// Now what happens if there is a cycle in the import path?
137+
// Let's consider this AST:
138+
// ```
139+
// A->B->C->A
140+
// `->E
141+
// ```
142+
// During the visitation we will have the below ImportPaths and if during
143+
// the visit of E there is an error then we will set up an error for E,B,A.
144+
// But what's up with C?
145+
// ```
146+
// A
147+
// AB
148+
// ABC
149+
// ABCA
150+
// ABC
151+
// AB
152+
// ABE // Error! Set an error to E
153+
// AB // Set an error to B
154+
// A // Set an error to A
155+
// ```
156+
// This time we know that both B and C are dependent on A.
157+
// This means we must set up an error for C too.
158+
// As the call stack reverses back we get to A and we must set up an error
159+
// to all nodes which depend on A (this includes C).
160+
// But C is no longer on the import path, it just had been previously.
161+
// Such situation can happen only if during the visitation we had a cycle.
162+
// If we didn't have any cycle, then the normal way of passing an Error
163+
// object through the call stack could handle the situation.
164+
// This is why we must track cycles during the import process for each
165+
// visited declaration.
166+
class ImportPathTy {
167+
public:
168+
using VecTy = llvm::SmallVector<Decl *, 32>;
169+
170+
void push(Decl *D) {
171+
Nodes.push_back(D);
172+
++Aux[D];
173+
}
174+
175+
void pop() {
176+
if (Nodes.empty())
177+
return;
178+
--Aux[Nodes.back()];
179+
Nodes.pop_back();
180+
}
181+
182+
/// Returns true if the last element can be found earlier in the path.
183+
bool hasCycleAtBack() const {
184+
auto Pos = Aux.find(Nodes.back());
185+
return Pos != Aux.end() && Pos->second > 1;
186+
}
187+
188+
using Cycle = llvm::iterator_range<VecTy::const_reverse_iterator>;
189+
Cycle getCycleAtBack() const {
190+
assert(Nodes.size() >= 2);
191+
return Cycle(Nodes.rbegin(),
192+
std::find(Nodes.rbegin() + 1, Nodes.rend(), Nodes.back()) +
193+
1);
194+
}
195+
196+
/// Returns the copy of the cycle.
197+
VecTy copyCycleAtBack() const {
198+
auto R = getCycleAtBack();
199+
return VecTy(R.begin(), R.end());
200+
}
201+
202+
private:
203+
// All the nodes of the path.
204+
VecTy Nodes;
205+
// Auxiliary container to be able to answer "Do we have a cycle ending
206+
// at last element?" as fast as possible.
207+
// We count each Decl's occurrence over the path.
208+
llvm::SmallDenseMap<Decl *, int, 32> Aux;
209+
};
210+
90211
private:
91212

92213
/// Pointer to the import specific lookup table, which may be shared
@@ -96,6 +217,17 @@ class TypeSourceInfo;
96217
/// If not set then the original C/C++ lookup is used.
97218
ASTImporterLookupTable *LookupTable = nullptr;
98219

220+
/// The path which we go through during the import of a given AST node.
221+
ImportPathTy ImportPath;
222+
/// Sometimes we have to save some part of an import path, so later we can
223+
/// set up properties to the saved nodes.
224+
/// We may have several of these import paths associated to one Decl.
225+
using SavedImportPathsForOneDecl =
226+
llvm::SmallVector<ImportPathTy::VecTy, 32>;
227+
using SavedImportPathsTy =
228+
llvm::SmallDenseMap<Decl *, SavedImportPathsForOneDecl, 32>;
229+
SavedImportPathsTy SavedImportPaths;
230+
99231
/// The contexts we're importing to and from.
100232
ASTContext &ToContext, &FromContext;
101233

clang/lib/AST/ASTImporter.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7842,6 +7842,10 @@ Expected<Decl *> ASTImporter::Import(Decl *FromD) {
78427842
if (!FromD)
78437843
return nullptr;
78447844

7845+
// Push FromD to the stack, and remove that when we return.
7846+
ImportPath.push(FromD);
7847+
auto ImportPathBuilder =
7848+
llvm::make_scope_exit([this]() { ImportPath.pop(); });
78457849

78467850
// Check whether there was a previous failed import.
78477851
// If yes return the existing error.
@@ -7853,6 +7857,10 @@ Expected<Decl *> ASTImporter::Import(Decl *FromD) {
78537857
if (ToD) {
78547858
// If FromD has some updated flags after last import, apply it
78557859
updateFlags(FromD, ToD);
7860+
// If we encounter a cycle during an import then we save the relevant part
7861+
// of the import path associated to the Decl.
7862+
if (ImportPath.hasCycleAtBack())
7863+
SavedImportPaths[FromD].push_back(ImportPath.copyCycleAtBack());
78567864
return ToD;
78577865
}
78587866

@@ -7889,16 +7897,20 @@ Expected<Decl *> ASTImporter::Import(Decl *FromD) {
78897897
// FIXME: AST may contain remaining references to the failed object.
78907898
}
78917899

7892-
// Error encountered for the first time.
7893-
assert(!getImportDeclErrorIfAny(FromD) &&
7894-
"Import error already set for Decl.");
7895-
7896-
// After takeError the error is not usable any more in ToDOrErr.
7900+
// After takeError the error is not usable anymore in ToDOrErr.
78977901
// Get a copy of the error object (any more simple solution for this?).
78987902
ImportError ErrOut;
78997903
handleAllErrors(ToDOrErr.takeError(),
79007904
[&ErrOut](const ImportError &E) { ErrOut = E; });
79017905
setImportDeclError(FromD, ErrOut);
7906+
7907+
// Set the error for all nodes which have been created before we
7908+
// recognized the error.
7909+
for (const auto &Path : SavedImportPaths[FromD])
7910+
for (Decl *Di : Path)
7911+
setImportDeclError(Di, ErrOut);
7912+
SavedImportPaths[FromD].clear();
7913+
79027914
// Do not return ToDOrErr, error was taken out of it.
79037915
return make_error<ImportError>(ErrOut);
79047916
}
@@ -7921,6 +7933,7 @@ Expected<Decl *> ASTImporter::Import(Decl *FromD) {
79217933
Imported(FromD, ToD);
79227934

79237935
updateFlags(FromD, ToD);
7936+
SavedImportPaths[FromD].clear();
79247937
return ToDOrErr;
79257938
}
79267939

@@ -8641,9 +8654,10 @@ ASTImporter::getImportDeclErrorIfAny(Decl *FromD) const {
86418654
}
86428655

86438656
void ASTImporter::setImportDeclError(Decl *From, ImportError Error) {
8644-
assert(ImportDeclErrors.find(From) == ImportDeclErrors.end() &&
8645-
"Setting import error allowed only once for a Decl.");
8646-
ImportDeclErrors[From] = Error;
8657+
auto InsertRes = ImportDeclErrors.insert({From, Error});
8658+
// Either we set the error for the first time, or we already had set one and
8659+
// now we want to set the same error.
8660+
assert(InsertRes.second || InsertRes.first->second.Error == Error.Error);
86478661
}
86488662

86498663
bool ASTImporter::IsStructurallyEquivalent(QualType From, QualType To,

0 commit comments

Comments
 (0)