[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
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index 215dc10..d517d63 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -358,6 +358,106 @@
EXPECT_EQ(0U, count);
}
+struct ImportPath : ASTImporterOptionSpecificTestBase {
+ Decl *FromTU;
+ FunctionDecl *D0, *D1, *D2;
+ ImportPath() {
+ FromTU = getTuDecl("void f(); void f(); void f();", Lang_CXX);
+ auto Pattern = functionDecl(hasName("f"));
+ D0 = FirstDeclMatcher<FunctionDecl>().match(FromTU, Pattern);
+ D2 = LastDeclMatcher<FunctionDecl>().match(FromTU, Pattern);
+ D1 = D2->getPreviousDecl();
+ }
+};
+
+TEST_P(ImportPath, Push) {
+ ASTImporter::ImportPathTy path;
+ path.push(D0);
+ EXPECT_FALSE(path.hasCycleAtBack());
+}
+
+TEST_P(ImportPath, SmallCycle) {
+ ASTImporter::ImportPathTy path;
+ path.push(D0);
+ path.push(D0);
+ EXPECT_TRUE(path.hasCycleAtBack());
+ path.pop();
+ EXPECT_FALSE(path.hasCycleAtBack());
+ path.push(D0);
+ EXPECT_TRUE(path.hasCycleAtBack());
+}
+
+TEST_P(ImportPath, GetSmallCycle) {
+ ASTImporter::ImportPathTy path;
+ path.push(D0);
+ path.push(D0);
+ EXPECT_TRUE(path.hasCycleAtBack());
+ std::array<Decl* ,2> Res;
+ int i = 0;
+ for (Decl *Di : path.getCycleAtBack()) {
+ Res[i++] = Di;
+ }
+ ASSERT_EQ(i, 2);
+ EXPECT_EQ(Res[0], D0);
+ EXPECT_EQ(Res[1], D0);
+}
+
+TEST_P(ImportPath, GetCycle) {
+ ASTImporter::ImportPathTy path;
+ path.push(D0);
+ path.push(D1);
+ path.push(D2);
+ path.push(D0);
+ EXPECT_TRUE(path.hasCycleAtBack());
+ std::array<Decl* ,4> Res;
+ int i = 0;
+ for (Decl *Di : path.getCycleAtBack()) {
+ Res[i++] = Di;
+ }
+ ASSERT_EQ(i, 4);
+ EXPECT_EQ(Res[0], D0);
+ EXPECT_EQ(Res[1], D2);
+ EXPECT_EQ(Res[2], D1);
+ EXPECT_EQ(Res[3], D0);
+}
+
+TEST_P(ImportPath, CycleAfterCycle) {
+ ASTImporter::ImportPathTy path;
+ path.push(D0);
+ path.push(D1);
+ path.push(D0);
+ path.push(D1);
+ path.push(D2);
+ path.push(D0);
+ EXPECT_TRUE(path.hasCycleAtBack());
+ std::array<Decl* ,4> Res;
+ int i = 0;
+ for (Decl *Di : path.getCycleAtBack()) {
+ Res[i++] = Di;
+ }
+ ASSERT_EQ(i, 4);
+ EXPECT_EQ(Res[0], D0);
+ EXPECT_EQ(Res[1], D2);
+ EXPECT_EQ(Res[2], D1);
+ EXPECT_EQ(Res[3], D0);
+
+ path.pop();
+ path.pop();
+ path.pop();
+ EXPECT_TRUE(path.hasCycleAtBack());
+ i = 0;
+ for (Decl *Di : path.getCycleAtBack()) {
+ Res[i++] = Di;
+ }
+ ASSERT_EQ(i, 3);
+ EXPECT_EQ(Res[0], D0);
+ EXPECT_EQ(Res[1], D1);
+ EXPECT_EQ(Res[2], D0);
+
+ path.pop();
+ EXPECT_FALSE(path.hasCycleAtBack());
+}
+
TEST_P(ImportExpr, ImportStringLiteral) {
MatchVerifier<Decl> Verifier;
testImport(
@@ -4547,12 +4647,6 @@
EXPECT_EQ(*Res.begin(), A);
}
-INSTANTIATE_TEST_CASE_P(ParameterizedTests, DeclContextTest,
- ::testing::Values(ArgVector()), );
-
-INSTANTIATE_TEST_CASE_P(
- ParameterizedTests, CanonicalRedeclChain,
- ::testing::Values(ArgVector()),);
// FIXME This test is disabled currently, upcoming patches will make it
// possible to enable.
@@ -4770,19 +4864,99 @@
CXXMethodDecl *ImportedF = Import(FromF, Lang_CXX);
EXPECT_FALSE(ImportedF);
- // There is no error set for ok().
+ // There is an error set for the other member too.
auto *FromOK = FirstDeclMatcher<CXXMethodDecl>().match(
FromTU, cxxMethodDecl(hasName("ok")));
OptErr = Importer->getImportDeclErrorIfAny(FromOK);
- EXPECT_FALSE(OptErr);
- // And we should be able to import.
+ EXPECT_TRUE(OptErr);
+ // Cannot import the other member.
CXXMethodDecl *ImportedOK = Import(FromOK, Lang_CXX);
- EXPECT_TRUE(ImportedOK);
+ EXPECT_FALSE(ImportedOK);
+}
- // Unwary clients may access X even if the error is set, so, at least make
- // sure the class is set to be complete.
- CXXRecordDecl *ToX = cast<CXXRecordDecl>(ImportedOK->getDeclContext());
- EXPECT_TRUE(ToX->isCompleteDefinition());
+// Check that an error propagates to the dependent AST nodes.
+// In the below code it means that an error in X should propagate to A.
+// And even to F since the containing A is erroneous.
+// And to all AST nodes which we visit during the import process which finally
+// ends up in a failure (in the error() function).
+TEST_P(ErrorHandlingTest, ErrorPropagatesThroughImportCycles) {
+ Decl *FromTU = getTuDecl(
+ std::string(R"(
+ namespace NS {
+ class A {
+ template <int I> class F {};
+ class X {
+ template <int I> friend class F;
+ void error() { )") + ErroneousStmt + R"( }
+ };
+ };
+
+ class B {};
+ } // NS
+ )",
+ Lang_CXX, "input0.cc");
+
+ auto *FromFRD = FirstDeclMatcher<CXXRecordDecl>().match(
+ FromTU, cxxRecordDecl(hasName("F"), isDefinition()));
+ auto *FromA = FirstDeclMatcher<CXXRecordDecl>().match(
+ FromTU, cxxRecordDecl(hasName("A"), isDefinition()));
+ auto *FromB = FirstDeclMatcher<CXXRecordDecl>().match(
+ FromTU, cxxRecordDecl(hasName("B"), isDefinition()));
+ auto *FromNS = FirstDeclMatcher<NamespaceDecl>().match(
+ FromTU, namespaceDecl(hasName("NS")));
+
+ // Start by importing the templated CXXRecordDecl of F.
+ // Import fails for that.
+ EXPECT_FALSE(Import(FromFRD, Lang_CXX));
+ // Import fails for A.
+ EXPECT_FALSE(Import(FromA, Lang_CXX));
+ // But we should be able to import the independent B.
+ EXPECT_TRUE(Import(FromB, Lang_CXX));
+ // And the namespace.
+ EXPECT_TRUE(Import(FromNS, Lang_CXX));
+
+ // An error is set to the templated CXXRecordDecl of F.
+ ASTImporter *Importer = findFromTU(FromFRD)->Importer.get();
+ Optional<ImportError> OptErr = Importer->getImportDeclErrorIfAny(FromFRD);
+ EXPECT_TRUE(OptErr);
+
+ // An error is set to A.
+ OptErr = Importer->getImportDeclErrorIfAny(FromA);
+ EXPECT_TRUE(OptErr);
+
+ // There is no error set to B.
+ OptErr = Importer->getImportDeclErrorIfAny(FromB);
+ EXPECT_FALSE(OptErr);
+
+ // There is no error set to NS.
+ OptErr = Importer->getImportDeclErrorIfAny(FromNS);
+ EXPECT_FALSE(OptErr);
+
+ // Check some of those decls whose ancestor is X, they all should have an
+ // error set if we visited them during an import process which finally failed.
+ // These decls are part of a cycle in an ImportPath.
+ // There would not be any error set for these decls if we hadn't follow the
+ // ImportPaths and the cycles.
+ OptErr = Importer->getImportDeclErrorIfAny(
+ FirstDeclMatcher<ClassTemplateDecl>().match(
+ FromTU, classTemplateDecl(hasName("F"))));
+ // An error is set to the 'F' ClassTemplateDecl.
+ EXPECT_TRUE(OptErr);
+ // An error is set to the FriendDecl.
+ OptErr = Importer->getImportDeclErrorIfAny(
+ FirstDeclMatcher<FriendDecl>().match(
+ FromTU, friendDecl()));
+ EXPECT_TRUE(OptErr);
+ // An error is set to the implicit class of A.
+ OptErr =
+ Importer->getImportDeclErrorIfAny(FirstDeclMatcher<CXXRecordDecl>().match(
+ FromTU, cxxRecordDecl(hasName("A"), isImplicit())));
+ EXPECT_TRUE(OptErr);
+ // An error is set to the implicit class of X.
+ OptErr =
+ Importer->getImportDeclErrorIfAny(FirstDeclMatcher<CXXRecordDecl>().match(
+ FromTU, cxxRecordDecl(hasName("X"), isImplicit())));
+ EXPECT_TRUE(OptErr);
}
TEST_P(ErrorHandlingTest, ErrorIsNotPropagatedFromMemberToNamespace) {
@@ -4828,9 +5002,18 @@
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ErrorHandlingTest,
DefaultTestValuesForRunOptions, );
+INSTANTIATE_TEST_CASE_P(ParameterizedTests, DeclContextTest,
+ ::testing::Values(ArgVector()), );
+
+INSTANTIATE_TEST_CASE_P(ParameterizedTests, CanonicalRedeclChain,
+ ::testing::Values(ArgVector()), );
+
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ASTImporterLookupTableTest,
DefaultTestValuesForRunOptions, );
+INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportPath,
+ ::testing::Values(ArgVector()), );
+
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportExpr,
DefaultTestValuesForRunOptions, );