Index: clang/include/clang/AST/ASTImporter.h =================================================================== --- clang/include/clang/AST/ASTImporter.h +++ clang/include/clang/AST/ASTImporter.h @@ -55,6 +55,7 @@ enum ErrorKind { NameConflict, /// Naming ambiguity (likely ODR violation). UnsupportedConstruct, /// Not supported node or case. + IncompatibleConstruct, /// Node not valid in target context. Unknown /// Other error. }; Index: clang/lib/AST/ASTImporter.cpp =================================================================== --- clang/lib/AST/ASTImporter.cpp +++ clang/lib/AST/ASTImporter.cpp @@ -90,6 +90,8 @@ return "NameConflict"; case UnsupportedConstruct: return "UnsupportedConstruct"; + case IncompatibleConstruct: + return "Language construct not compatible with target AST."; case Unknown: return "Unknown error"; } @@ -1754,6 +1756,27 @@ llvm_unreachable("Unknown name kind."); } +/// Returns true if the ASTImporter should skip this Decl when importing the +/// Decls inside a DeclContext. +static bool shouldSkipDeclInContext(ASTContext &ToCtx, Decl *D) { + // The To AST doesn't support C++ so filter out implicit C++ nodes that are + // generated when parsing C code as C++. + if (!ToCtx.getLangOpts().CPlusPlus) { + switch (D->getKind()) { + // Skip constructors/destructors which are generated by Clang. + case Decl::Kind::CXXConstructor: + case Decl::Kind::CXXDestructor: + return D->isImplicit(); + case Decl::Kind::CXXRecord: + // Injected class names are only used in C++. + return cast(D)->isInjectedClassName(); + default: + break; + } + } + return false; +} + Error ASTNodeImporter::ImportDeclContext(DeclContext *FromDC, bool ForceImport) { if (Importer.isMinimalImport() && !ForceImport) { @@ -1774,6 +1797,8 @@ Error ChildErrors = Error::success(); for (auto *From : FromDC->decls()) { + if (shouldSkipDeclInContext(Importer.getToContext(), From)) + continue; ExpectedDecl ImportedOrErr = import(From); // If we are in the process of ImportDefinition(...) for a RecordDecl we @@ -2772,6 +2797,9 @@ } else if (Importer.getToContext().getLangOpts().CPlusPlus) IDNS |= Decl::IDNS_Ordinary | Decl::IDNS_TagFriend; + /// Whether the to AST expects a RecordDecl (ant not to a CXXRecordDecl). + const bool ConvertToC = !Importer.getToContext().getLangOpts().CPlusPlus; + // We may already have a record of the same name; try to find and match it. RecordDecl *PrevDecl = nullptr; if (!DC->isFunctionOrMethod() && !D->isLambda()) { @@ -2819,14 +2847,16 @@ // user-defined methods. Importer.MapImported(D, FoundDef); if (const auto *DCXX = dyn_cast(D)) { - auto *FoundCXX = dyn_cast(FoundDef); - assert(FoundCXX && "Record type mismatch"); - - if (!Importer.isMinimalImport()) + // Only import any implicit method when the target AST supports + // C++. + if (!Importer.isMinimalImport() && !ConvertToC) { + auto *FoundCXX = dyn_cast(FoundDef); + assert(FoundCXX && "Record type mismatch"); // FoundDef may not have every implicit method that D has // because implicit methods are created only if they are used. if (Error Err = ImportImplicitMethods(DCXX, FoundCXX)) return std::move(Err); + } } } PrevDecl = FoundRecord->getMostRecentDecl(); @@ -2854,7 +2884,18 @@ // Create the record declaration. RecordDecl *D2 = nullptr; CXXRecordDecl *D2CXX = nullptr; - if (auto *DCXX = dyn_cast(D)) { + auto *DCXX = dyn_cast(D); + // If the target context doesn't support C++ but From is using C++-exclusive + // features then the Decl can't be imported. + if (ConvertToC && DCXX && !DCXX->isCLike()) + return make_error(ImportError::IncompatibleConstruct); + + if (DCXX && !ConvertToC) { + // If the target context doesn't support C++ but From is using C++-exclusive + // features then there is nothing that can be done. + if (ConvertToC && !DCXX->isCLike()) + return make_error(ImportError::IncompatibleConstruct); + if (DCXX->isLambda()) { auto TInfoOrErr = import(DCXX->getLambdaTypeInfo()); if (!TInfoOrErr) Index: clang/lib/AST/ASTStructuralEquivalence.cpp =================================================================== --- clang/lib/AST/ASTStructuralEquivalence.cpp +++ clang/lib/AST/ASTStructuralEquivalence.cpp @@ -2041,21 +2041,38 @@ return true; } +/// Returns the Decl::Kind that should be used when checking if Decls have a +/// similar enough kind to require a more in-depth comparison. +/// This usually returns the Kind that was passed in except in cases where +/// the same language construct is expressed by different Decl::Kinds in +/// different languages (CXXRecord vs Record). +static Decl::Kind getKindForComparison(Decl::Kind k) { + // For CXXRecords fall back to the parent class Record as Records and + // CXXRecords might be equivalent. The comparison code for Records handles + // both comparing CXXRecord and Records instances. + if (k == Decl::Kind::CXXRecord) + return Decl::Kind::Record; + return k; +} + bool StructuralEquivalenceContext::CheckKindSpecificEquivalence( Decl *D1, Decl *D2) { + const Decl::Kind Kind1 = getKindForComparison(D1->getKind()); + const Decl::Kind Kind2 = getKindForComparison(D2->getKind()); + // Kind mismatch. - if (D1->getKind() != D2->getKind()) + if (Kind1 != Kind2) return false; // Cast the Decls to their actual subclass so that the right overload of // IsStructurallyEquivalent is called. - switch (D1->getKind()) { + switch (Kind1) { #define ABSTRACT_DECL(DECL) #define DECL(DERIVED, BASE) \ case Decl::Kind::DERIVED: \ - return ::IsStructurallyEquivalent(*this, static_cast(D1), \ - static_cast(D2)); + return ::IsStructurallyEquivalent(*this, llvm::cast(D1), \ + llvm::cast(D2)); #include "clang/AST/DeclNodes.inc" } return true; Index: clang/unittests/AST/ASTImporterCrossLanguageTest.cpp =================================================================== --- /dev/null +++ clang/unittests/AST/ASTImporterCrossLanguageTest.cpp @@ -0,0 +1,123 @@ +//===- unittest/AST/ASTImporterCrossLanguageTest.cpp -===================--===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Tests for the correct import of AST nodes between translation units of +// different languages and language standards. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/DeclContextInternals.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +#include "ASTImporterFixtures.h" + +using namespace clang::ast_matchers; +using namespace clang; + +namespace { +/// Tests that import from C++ to C. +struct CXXToCImport : ASTImporterOptionSpecificTestBase {}; +} // namespace + +TEST_P(CXXToCImport, ConvertStruct) { + // Import a C struct parsed as C++ to a C AST. + Decl *FromTU = getTuDecl( + R"( + struct B {}; + )", + Lang_CXX11, "input0.cc"); + CXXRecordDecl *From = + FirstDeclMatcher().match(FromTU, recordDecl(hasName("B"))); + RecordDecl *To = Import(From, Lang_C99); + EXPECT_FALSE(isa(To)); + // The imported struct should contain none of the usual nested C++ Decls. + EXPECT_TRUE(To->decls_empty()); +} + +TEST_P(CXXToCImport, ConvertStructWithImplicitCtorDotr) { + // Import a C struct where Clang generated implicit dtors/ctors. + Decl *FromTU = getTuDecl( + R"( + struct B {}; + B b = B(); + )", + Lang_CXX11, "input0.cc"); + CXXRecordDecl *From = + FirstDeclMatcher().match(FromTU, recordDecl(hasName("B"))); + + // Sanity check that the imported AST contains dtors/ctors. + FirstDeclMatcher().match(FromTU, cxxConstructorDecl(hasName("B"))); + FirstDeclMatcher().match(FromTU, cxxDestructorDecl(hasName("~B"))); + + RecordDecl *To = Import(From, Lang_C99); + EXPECT_FALSE(isa(To)); + for (Decl *D : To->decls()) { + // Check that dtors/ctors were not imported. + EXPECT_FALSE(isa(D)); + EXPECT_FALSE(isa(D)); + // Check that the RecordDecl for the injected class name was not imported. + EXPECT_FALSE(isa(D)); + } +} + +TEST_P(CXXToCImport, MergeStruct) { + // Merge a C struct parsed as C into a C AST with the same struct. + std::string Source = "struct B{};"; + Decl *ToTU = getToTuDecl(Source, Lang_C99); + Decl *FromTU = getTuDecl(Source, Lang_CXX11, "input0.cc"); + + // Find the struct in both TUs. + RecordDecl *To = + FirstDeclMatcher().match(ToTU, recordDecl(hasName("B"))); + CXXRecordDecl *From = + FirstDeclMatcher().match(FromTU, recordDecl(hasName("B"))); + // Import it and check that it got merged into the existing struct. + RecordDecl *Imported = Import(From, Lang_C99); + EXPECT_EQ(To, Imported); + // The C struct should contain none of the usual nested C++ Decls. + EXPECT_TRUE(To->decls_empty()); +} + +TEST_P(CXXToCImport, InvalidStruct) { + // Try to import different kinds of records that are all only valid in C++. + std::vector InvalidInputs = { + "class B {};", + "struct B { public: };", + "struct B { private: };", + "struct B { using T = int; };", + "struct B { static_assert(true, \"\"); };", + "struct B { B() {} };", + "struct B { B() = default };", + "struct B { ~B() {} };", + "struct B { ~B() = default };", + "struct B { int i = 0; };", + "struct B { void func(); };", + "struct B { void func() {} };", + "struct F {}; struct B { friend class F; };", + "struct B { int &i; B(); };", + "template struct B {};", + }; + + unsigned InputIndex = 0; + for (const std::string &Input : InvalidInputs) { + SCOPED_TRACE("Input: " + Input); + Decl *FromTU = getTuDecl(Input, Lang_CXX11, + "input" + std::to_string(InputIndex++) + ".cc"); + + CXXRecordDecl *From = FirstDeclMatcher().match( + FromTU, recordDecl(hasName("B"))); + // Try import the C++ record which has to fail. + llvm::Expected Err = importOrError(From, Lang_C99); + ASSERT_THAT_EXPECTED(Err, llvm::Failed()); + } +} + +INSTANTIATE_TEST_CASE_P(ParameterizedTests, CXXToCImport, + ::testing::Values(std::vector()), ); Index: clang/unittests/AST/CMakeLists.txt =================================================================== --- clang/unittests/AST/CMakeLists.txt +++ clang/unittests/AST/CMakeLists.txt @@ -7,6 +7,7 @@ add_clang_unittest(ASTTests ASTContextParentMapTest.cpp ASTImporterFixtures.cpp + ASTImporterCrossLanguageTest.cpp ASTImporterTest.cpp ASTImporterObjCTest.cpp ASTImporterGenericRedeclTest.cpp