123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- //===--- CrossTranslationUnit.cpp - -----------------------------*- C++ -*-===//
- //
- // 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
- //
- //===----------------------------------------------------------------------===//
- //
- // This file implements the CrossTranslationUnit interface.
- //
- //===----------------------------------------------------------------------===//
- #include "clang/CrossTU/CrossTranslationUnit.h"
- #include "clang/AST/ASTImporter.h"
- #include "clang/AST/Decl.h"
- #include "clang/Basic/TargetInfo.h"
- #include "clang/CrossTU/CrossTUDiagnostic.h"
- #include "clang/Frontend/ASTUnit.h"
- #include "clang/Frontend/CompilerInstance.h"
- #include "clang/Frontend/TextDiagnosticPrinter.h"
- #include "clang/Index/USRGeneration.h"
- #include "llvm/ADT/Triple.h"
- #include "llvm/ADT/Statistic.h"
- #include "llvm/Support/ErrorHandling.h"
- #include "llvm/Support/ManagedStatic.h"
- #include "llvm/Support/Path.h"
- #include "llvm/Support/raw_ostream.h"
- #include <fstream>
- #include <sstream>
- namespace clang {
- namespace cross_tu {
- namespace {
- #define DEBUG_TYPE "CrossTranslationUnit"
- STATISTIC(NumGetCTUCalled, "The # of getCTUDefinition function called");
- STATISTIC(
- NumNotInOtherTU,
- "The # of getCTUDefinition called but the function is not in any other TU");
- STATISTIC(NumGetCTUSuccess,
- "The # of getCTUDefinition successfully returned the "
- "requested function's body");
- STATISTIC(NumUnsupportedNodeFound, "The # of imports when the ASTImporter "
- "encountered an unsupported AST Node");
- STATISTIC(NumNameConflicts, "The # of imports when the ASTImporter "
- "encountered an ODR error");
- STATISTIC(NumTripleMismatch, "The # of triple mismatches");
- STATISTIC(NumLangMismatch, "The # of language mismatches");
- STATISTIC(NumLangDialectMismatch, "The # of language dialect mismatches");
- STATISTIC(NumASTLoadThresholdReached,
- "The # of ASTs not loaded because of threshold");
- // Same as Triple's equality operator, but we check a field only if that is
- // known in both instances.
- bool hasEqualKnownFields(const llvm::Triple &Lhs, const llvm::Triple &Rhs) {
- using llvm::Triple;
- if (Lhs.getArch() != Triple::UnknownArch &&
- Rhs.getArch() != Triple::UnknownArch && Lhs.getArch() != Rhs.getArch())
- return false;
- if (Lhs.getSubArch() != Triple::NoSubArch &&
- Rhs.getSubArch() != Triple::NoSubArch &&
- Lhs.getSubArch() != Rhs.getSubArch())
- return false;
- if (Lhs.getVendor() != Triple::UnknownVendor &&
- Rhs.getVendor() != Triple::UnknownVendor &&
- Lhs.getVendor() != Rhs.getVendor())
- return false;
- if (!Lhs.isOSUnknown() && !Rhs.isOSUnknown() &&
- Lhs.getOS() != Rhs.getOS())
- return false;
- if (Lhs.getEnvironment() != Triple::UnknownEnvironment &&
- Rhs.getEnvironment() != Triple::UnknownEnvironment &&
- Lhs.getEnvironment() != Rhs.getEnvironment())
- return false;
- if (Lhs.getObjectFormat() != Triple::UnknownObjectFormat &&
- Rhs.getObjectFormat() != Triple::UnknownObjectFormat &&
- Lhs.getObjectFormat() != Rhs.getObjectFormat())
- return false;
- return true;
- }
- // FIXME: This class is will be removed after the transition to llvm::Error.
- class IndexErrorCategory : public std::error_category {
- public:
- const char *name() const noexcept override { return "clang.index"; }
- std::string message(int Condition) const override {
- switch (static_cast<index_error_code>(Condition)) {
- case index_error_code::unspecified:
- return "An unknown error has occurred.";
- case index_error_code::missing_index_file:
- return "The index file is missing.";
- case index_error_code::invalid_index_format:
- return "Invalid index file format.";
- case index_error_code::multiple_definitions:
- return "Multiple definitions in the index file.";
- case index_error_code::missing_definition:
- return "Missing definition from the index file.";
- case index_error_code::failed_import:
- return "Failed to import the definition.";
- case index_error_code::failed_to_get_external_ast:
- return "Failed to load external AST source.";
- case index_error_code::failed_to_generate_usr:
- return "Failed to generate USR.";
- case index_error_code::triple_mismatch:
- return "Triple mismatch";
- case index_error_code::lang_mismatch:
- return "Language mismatch";
- case index_error_code::lang_dialect_mismatch:
- return "Language dialect mismatch";
- case index_error_code::load_threshold_reached:
- return "Load threshold reached";
- }
- llvm_unreachable("Unrecognized index_error_code.");
- }
- };
- static llvm::ManagedStatic<IndexErrorCategory> Category;
- } // end anonymous namespace
- char IndexError::ID;
- void IndexError::log(raw_ostream &OS) const {
- OS << Category->message(static_cast<int>(Code)) << '\n';
- }
- std::error_code IndexError::convertToErrorCode() const {
- return std::error_code(static_cast<int>(Code), *Category);
- }
- llvm::Expected<llvm::StringMap<std::string>>
- parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir) {
- std::ifstream ExternalMapFile(IndexPath);
- if (!ExternalMapFile)
- return llvm::make_error<IndexError>(index_error_code::missing_index_file,
- IndexPath.str());
- llvm::StringMap<std::string> Result;
- std::string Line;
- unsigned LineNo = 1;
- while (std::getline(ExternalMapFile, Line)) {
- const size_t Pos = Line.find(" ");
- if (Pos > 0 && Pos != std::string::npos) {
- StringRef LineRef{Line};
- StringRef LookupName = LineRef.substr(0, Pos);
- if (Result.count(LookupName))
- return llvm::make_error<IndexError>(
- index_error_code::multiple_definitions, IndexPath.str(), LineNo);
- StringRef FileName = LineRef.substr(Pos + 1);
- SmallString<256> FilePath = CrossTUDir;
- llvm::sys::path::append(FilePath, FileName);
- Result[LookupName] = FilePath.str().str();
- } else
- return llvm::make_error<IndexError>(
- index_error_code::invalid_index_format, IndexPath.str(), LineNo);
- LineNo++;
- }
- return Result;
- }
- std::string
- createCrossTUIndexString(const llvm::StringMap<std::string> &Index) {
- std::ostringstream Result;
- for (const auto &E : Index)
- Result << E.getKey().str() << " " << E.getValue() << '\n';
- return Result.str();
- }
- bool containsConst(const VarDecl *VD, const ASTContext &ACtx) {
- CanQualType CT = ACtx.getCanonicalType(VD->getType());
- if (!CT.isConstQualified()) {
- const RecordType *RTy = CT->getAs<RecordType>();
- if (!RTy || !RTy->hasConstFields())
- return false;
- }
- return true;
- }
- static bool hasBodyOrInit(const FunctionDecl *D, const FunctionDecl *&DefD) {
- return D->hasBody(DefD);
- }
- static bool hasBodyOrInit(const VarDecl *D, const VarDecl *&DefD) {
- return D->getAnyInitializer(DefD);
- }
- template <typename T> static bool hasBodyOrInit(const T *D) {
- const T *Unused;
- return hasBodyOrInit(D, Unused);
- }
- CrossTranslationUnitContext::CrossTranslationUnitContext(CompilerInstance &CI)
- : Context(CI.getASTContext()), ASTStorage(CI) {}
- CrossTranslationUnitContext::~CrossTranslationUnitContext() {}
- llvm::Optional<std::string>
- CrossTranslationUnitContext::getLookupName(const NamedDecl *ND) {
- SmallString<128> DeclUSR;
- bool Ret = index::generateUSRForDecl(ND, DeclUSR);
- if (Ret)
- return {};
- return std::string(DeclUSR.str());
- }
- /// Recursively visits the decls of a DeclContext, and returns one with the
- /// given USR.
- template <typename T>
- const T *
- CrossTranslationUnitContext::findDefInDeclContext(const DeclContext *DC,
- StringRef LookupName) {
- assert(DC && "Declaration Context must not be null");
- for (const Decl *D : DC->decls()) {
- const auto *SubDC = dyn_cast<DeclContext>(D);
- if (SubDC)
- if (const auto *ND = findDefInDeclContext<T>(SubDC, LookupName))
- return ND;
- const auto *ND = dyn_cast<T>(D);
- const T *ResultDecl;
- if (!ND || !hasBodyOrInit(ND, ResultDecl))
- continue;
- llvm::Optional<std::string> ResultLookupName = getLookupName(ResultDecl);
- if (!ResultLookupName || *ResultLookupName != LookupName)
- continue;
- return ResultDecl;
- }
- return nullptr;
- }
- template <typename T>
- llvm::Expected<const T *> CrossTranslationUnitContext::getCrossTUDefinitionImpl(
- const T *D, StringRef CrossTUDir, StringRef IndexName,
- bool DisplayCTUProgress) {
- assert(D && "D is missing, bad call to this function!");
- assert(!hasBodyOrInit(D) &&
- "D has a body or init in current translation unit!");
- ++NumGetCTUCalled;
- const llvm::Optional<std::string> LookupName = getLookupName(D);
- if (!LookupName)
- return llvm::make_error<IndexError>(
- index_error_code::failed_to_generate_usr);
- llvm::Expected<ASTUnit *> ASTUnitOrError =
- loadExternalAST(*LookupName, CrossTUDir, IndexName, DisplayCTUProgress);
- if (!ASTUnitOrError)
- return ASTUnitOrError.takeError();
- ASTUnit *Unit = *ASTUnitOrError;
- assert(&Unit->getFileManager() ==
- &Unit->getASTContext().getSourceManager().getFileManager());
- const llvm::Triple &TripleTo = Context.getTargetInfo().getTriple();
- const llvm::Triple &TripleFrom =
- Unit->getASTContext().getTargetInfo().getTriple();
- // The imported AST had been generated for a different target.
- // Some parts of the triple in the loaded ASTContext can be unknown while the
- // very same parts in the target ASTContext are known. Thus we check for the
- // known parts only.
- if (!hasEqualKnownFields(TripleTo, TripleFrom)) {
- // TODO: Pass the SourceLocation of the CallExpression for more precise
- // diagnostics.
- ++NumTripleMismatch;
- return llvm::make_error<IndexError>(index_error_code::triple_mismatch,
- Unit->getMainFileName(), TripleTo.str(),
- TripleFrom.str());
- }
- const auto &LangTo = Context.getLangOpts();
- const auto &LangFrom = Unit->getASTContext().getLangOpts();
- // FIXME: Currenty we do not support CTU across C++ and C and across
- // different dialects of C++.
- if (LangTo.CPlusPlus != LangFrom.CPlusPlus) {
- ++NumLangMismatch;
- return llvm::make_error<IndexError>(index_error_code::lang_mismatch);
- }
- // If CPP dialects are different then return with error.
- //
- // Consider this STL code:
- // template<typename _Alloc>
- // struct __alloc_traits
- // #if __cplusplus >= 201103L
- // : std::allocator_traits<_Alloc>
- // #endif
- // { // ...
- // };
- // This class template would create ODR errors during merging the two units,
- // since in one translation unit the class template has a base class, however
- // in the other unit it has none.
- if (LangTo.CPlusPlus11 != LangFrom.CPlusPlus11 ||
- LangTo.CPlusPlus14 != LangFrom.CPlusPlus14 ||
- LangTo.CPlusPlus17 != LangFrom.CPlusPlus17 ||
- LangTo.CPlusPlus2a != LangFrom.CPlusPlus2a) {
- ++NumLangDialectMismatch;
- return llvm::make_error<IndexError>(
- index_error_code::lang_dialect_mismatch);
- }
- TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl();
- if (const T *ResultDecl = findDefInDeclContext<T>(TU, *LookupName))
- return importDefinition(ResultDecl, Unit);
- return llvm::make_error<IndexError>(index_error_code::failed_import);
- }
- llvm::Expected<const FunctionDecl *>
- CrossTranslationUnitContext::getCrossTUDefinition(const FunctionDecl *FD,
- StringRef CrossTUDir,
- StringRef IndexName,
- bool DisplayCTUProgress) {
- return getCrossTUDefinitionImpl(FD, CrossTUDir, IndexName,
- DisplayCTUProgress);
- }
- llvm::Expected<const VarDecl *>
- CrossTranslationUnitContext::getCrossTUDefinition(const VarDecl *VD,
- StringRef CrossTUDir,
- StringRef IndexName,
- bool DisplayCTUProgress) {
- return getCrossTUDefinitionImpl(VD, CrossTUDir, IndexName,
- DisplayCTUProgress);
- }
- void CrossTranslationUnitContext::emitCrossTUDiagnostics(const IndexError &IE) {
- switch (IE.getCode()) {
- case index_error_code::missing_index_file:
- Context.getDiagnostics().Report(diag::err_ctu_error_opening)
- << IE.getFileName();
- break;
- case index_error_code::invalid_index_format:
- Context.getDiagnostics().Report(diag::err_extdefmap_parsing)
- << IE.getFileName() << IE.getLineNum();
- break;
- case index_error_code::multiple_definitions:
- Context.getDiagnostics().Report(diag::err_multiple_def_index)
- << IE.getLineNum();
- break;
- case index_error_code::triple_mismatch:
- Context.getDiagnostics().Report(diag::warn_ctu_incompat_triple)
- << IE.getFileName() << IE.getTripleToName() << IE.getTripleFromName();
- break;
- default:
- break;
- }
- }
- CrossTranslationUnitContext::ASTFileLoader::ASTFileLoader(
- const CompilerInstance &CI)
- : CI(CI) {}
- std::unique_ptr<ASTUnit>
- CrossTranslationUnitContext::ASTFileLoader::operator()(StringRef ASTFilePath) {
- // Load AST from ast-dump.
- IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
- TextDiagnosticPrinter *DiagClient =
- new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
- IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
- IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
- new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
- return ASTUnit::LoadFromASTFile(
- ASTFilePath, CI.getPCHContainerOperations()->getRawReader(),
- ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts());
- }
- CrossTranslationUnitContext::ASTUnitStorage::ASTUnitStorage(
- const CompilerInstance &CI)
- : FileAccessor(CI), LoadGuard(const_cast<CompilerInstance &>(CI)
- .getAnalyzerOpts()
- ->CTUImportThreshold) {}
- llvm::Expected<ASTUnit *>
- CrossTranslationUnitContext::ASTUnitStorage::getASTUnitForFile(
- StringRef FileName, bool DisplayCTUProgress) {
- // Try the cache first.
- auto ASTCacheEntry = FileASTUnitMap.find(FileName);
- if (ASTCacheEntry == FileASTUnitMap.end()) {
- // Do not load if the limit is reached.
- if (!LoadGuard) {
- ++NumASTLoadThresholdReached;
- return llvm::make_error<IndexError>(
- index_error_code::load_threshold_reached);
- }
- // Load the ASTUnit from the pre-dumped AST file specified by ASTFileName.
- std::unique_ptr<ASTUnit> LoadedUnit = FileAccessor(FileName);
- // Need the raw pointer and the unique_ptr as well.
- ASTUnit *Unit = LoadedUnit.get();
- // Update the cache.
- FileASTUnitMap[FileName] = std::move(LoadedUnit);
- LoadGuard.indicateLoadSuccess();
- if (DisplayCTUProgress)
- llvm::errs() << "CTU loaded AST file: " << FileName << "\n";
- return Unit;
- } else {
- // Found in the cache.
- return ASTCacheEntry->second.get();
- }
- }
- llvm::Expected<ASTUnit *>
- CrossTranslationUnitContext::ASTUnitStorage::getASTUnitForFunction(
- StringRef FunctionName, StringRef CrossTUDir, StringRef IndexName,
- bool DisplayCTUProgress) {
- // Try the cache first.
- auto ASTCacheEntry = NameASTUnitMap.find(FunctionName);
- if (ASTCacheEntry == NameASTUnitMap.end()) {
- // Load the ASTUnit from the pre-dumped AST file specified by ASTFileName.
- // Ensure that the Index is loaded, as we need to search in it.
- if (llvm::Error IndexLoadError =
- ensureCTUIndexLoaded(CrossTUDir, IndexName))
- return std::move(IndexLoadError);
- // Check if there is and entry in the index for the function.
- if (!NameFileMap.count(FunctionName)) {
- ++NumNotInOtherTU;
- return llvm::make_error<IndexError>(index_error_code::missing_definition);
- }
- // Search in the index for the filename where the definition of FuncitonName
- // resides.
- if (llvm::Expected<ASTUnit *> FoundForFile =
- getASTUnitForFile(NameFileMap[FunctionName], DisplayCTUProgress)) {
- // Update the cache.
- NameASTUnitMap[FunctionName] = *FoundForFile;
- return *FoundForFile;
- } else {
- return FoundForFile.takeError();
- }
- } else {
- // Found in the cache.
- return ASTCacheEntry->second;
- }
- }
- llvm::Expected<std::string>
- CrossTranslationUnitContext::ASTUnitStorage::getFileForFunction(
- StringRef FunctionName, StringRef CrossTUDir, StringRef IndexName) {
- if (llvm::Error IndexLoadError = ensureCTUIndexLoaded(CrossTUDir, IndexName))
- return std::move(IndexLoadError);
- return NameFileMap[FunctionName];
- }
- llvm::Error CrossTranslationUnitContext::ASTUnitStorage::ensureCTUIndexLoaded(
- StringRef CrossTUDir, StringRef IndexName) {
- // Dont initialize if the map is filled.
- if (!NameFileMap.empty())
- return llvm::Error::success();
- // Get the absolute path to the index file.
- SmallString<256> IndexFile = CrossTUDir;
- if (llvm::sys::path::is_absolute(IndexName))
- IndexFile = IndexName;
- else
- llvm::sys::path::append(IndexFile, IndexName);
- if (auto IndexMapping = parseCrossTUIndex(IndexFile, CrossTUDir)) {
- // Initialize member map.
- NameFileMap = *IndexMapping;
- return llvm::Error::success();
- } else {
- // Error while parsing CrossTU index file.
- return IndexMapping.takeError();
- };
- }
- llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
- StringRef LookupName, StringRef CrossTUDir, StringRef IndexName,
- bool DisplayCTUProgress) {
- // FIXME: The current implementation only supports loading decls with
- // a lookup name from a single translation unit. If multiple
- // translation units contains decls with the same lookup name an
- // error will be returned.
- // Try to get the value from the heavily cached storage.
- llvm::Expected<ASTUnit *> Unit = ASTStorage.getASTUnitForFunction(
- LookupName, CrossTUDir, IndexName, DisplayCTUProgress);
- if (!Unit)
- return Unit.takeError();
- // Check whether the backing pointer of the Expected is a nullptr.
- if (!*Unit)
- return llvm::make_error<IndexError>(
- index_error_code::failed_to_get_external_ast);
- return Unit;
- }
- template <typename T>
- llvm::Expected<const T *>
- CrossTranslationUnitContext::importDefinitionImpl(const T *D, ASTUnit *Unit) {
- assert(hasBodyOrInit(D) && "Decls to be imported should have body or init.");
- assert(&D->getASTContext() == &Unit->getASTContext() &&
- "ASTContext of Decl and the unit should match.");
- ASTImporter &Importer = getOrCreateASTImporter(Unit);
- auto ToDeclOrError = Importer.Import(D);
- if (!ToDeclOrError) {
- handleAllErrors(ToDeclOrError.takeError(),
- [&](const ImportError &IE) {
- switch (IE.Error) {
- case ImportError::NameConflict:
- ++NumNameConflicts;
- break;
- case ImportError::UnsupportedConstruct:
- ++NumUnsupportedNodeFound;
- break;
- case ImportError::Unknown:
- llvm_unreachable("Unknown import error happened.");
- break;
- }
- });
- return llvm::make_error<IndexError>(index_error_code::failed_import);
- }
- auto *ToDecl = cast<T>(*ToDeclOrError);
- assert(hasBodyOrInit(ToDecl) && "Imported Decl should have body or init.");
- ++NumGetCTUSuccess;
- return ToDecl;
- }
- llvm::Expected<const FunctionDecl *>
- CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD,
- ASTUnit *Unit) {
- return importDefinitionImpl(FD, Unit);
- }
- llvm::Expected<const VarDecl *>
- CrossTranslationUnitContext::importDefinition(const VarDecl *VD,
- ASTUnit *Unit) {
- return importDefinitionImpl(VD, Unit);
- }
- void CrossTranslationUnitContext::lazyInitImporterSharedSt(
- TranslationUnitDecl *ToTU) {
- if (!ImporterSharedSt)
- ImporterSharedSt = std::make_shared<ASTImporterSharedState>(*ToTU);
- }
- ASTImporter &
- CrossTranslationUnitContext::getOrCreateASTImporter(ASTUnit *Unit) {
- ASTContext &From = Unit->getASTContext();
- auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl());
- if (I != ASTUnitImporterMap.end())
- return *I->second;
- lazyInitImporterSharedSt(Context.getTranslationUnitDecl());
- ASTImporter *NewImporter = new ASTImporter(
- Context, Context.getSourceManager().getFileManager(), From,
- From.getSourceManager().getFileManager(), false, ImporterSharedSt);
- NewImporter->setFileIDImportHandler([this, Unit](FileID ToID, FileID FromID) {
- assert(ImportedFileIDs.find(ToID) == ImportedFileIDs.end() &&
- "FileID already imported, should not happen.");
- ImportedFileIDs[ToID] = std::make_pair(FromID, Unit);
- });
- ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter);
- return *NewImporter;
- }
- llvm::Optional<std::pair<SourceLocation, ASTUnit *>>
- CrossTranslationUnitContext::getImportedFromSourceLocation(
- const clang::SourceLocation &ToLoc) const {
- const SourceManager &SM = Context.getSourceManager();
- auto DecToLoc = SM.getDecomposedLoc(ToLoc);
- auto I = ImportedFileIDs.find(DecToLoc.first);
- if (I == ImportedFileIDs.end())
- return {};
- FileID FromID = I->second.first;
- clang::ASTUnit *Unit = I->second.second;
- SourceLocation FromLoc =
- Unit->getSourceManager().getComposedLoc(FromID, DecToLoc.second);
- return std::make_pair(FromLoc, Unit);
- }
- } // namespace cross_tu
- } // namespace clang
|