Author: Arseniy Zaostrovnykh Date: 2026-04-02T07:59:52Z New Revision: e3cfcf48d0c966f163c9807839a900936af0e759
URL: https://github.com/llvm/llvm-project/commit/e3cfcf48d0c966f163c9807839a900936af0e759 DIFF: https://github.com/llvm/llvm-project/commit/e3cfcf48d0c966f163c9807839a900936af0e759.diff LOG: [clang][analyzer] Forward CTU-import failure conditions Forward all CTU-import failures as diagnostics (remarks, warnings, errors), except for `index_error_code::missing_definition` which has the potential of generating too many diagnostics. -- CPP-7804 Added: clang/test/Analysis/ctu/diag/Inputs/third.cpp clang/test/Analysis/ctu/diag/invlist-wrong-format-late.cpp Modified: clang/include/clang/Basic/DiagnosticCrossTUKinds.td clang/include/clang/CrossTU/CrossTranslationUnit.h clang/lib/CrossTU/CrossTranslationUnit.cpp clang/test/Analysis/ctu/diag/invlist-ambiguous.cpp clang/test/Analysis/ctu/diag/invlist-empty.cpp clang/test/Analysis/ctu/diag/invlist-lookup-miss.cpp clang/test/Analysis/ctu/diag/invlist-missing.cpp clang/test/Analysis/ctu/diag/invlist-wrong-format.cpp clang/test/Analysis/ctu/diag/lang-dialect-mismatch.cpp clang/test/Analysis/ctu/diag/lang-mismatch.c clang/test/Analysis/ctu/diag/load-threshold.cpp clang/test/Analysis/ctu/import-type-decl-definition.c clang/test/Analysis/ctu/invalid-ast.cpp clang/test/Analysis/ctu/main.c clang/test/Analysis/ctu/missing-ast.cpp clang/test/Analysis/ctu/on-demand-parsing.c clang/test/Analysis/ctu/test-import-failure.cpp clang/unittests/CrossTU/CrossTranslationUnitTest.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticCrossTUKinds.td b/clang/include/clang/Basic/DiagnosticCrossTUKinds.td index e6ea1956f98a6..7785751651dfb 100644 --- a/clang/include/clang/Basic/DiagnosticCrossTUKinds.td +++ b/clang/include/clang/Basic/DiagnosticCrossTUKinds.td @@ -15,10 +15,35 @@ def err_extdefmap_parsing : Error< "error parsing index file: '%0' line: %1 '<USR-Length>:<USR> <File-Path>' " "format expected">; +def err_invlist_parsing : Error< + "error parsing invocation list file: '%0' line: %1 " + "'<source-file>: [<compiler>, <arg1>, ...]' YAML mapping format expected">; + def err_multiple_def_index : Error< - "multiple definitions are found for the same key in index ">; + "multiple definitions are found for the same key in index">; + +def warn_multiple_entries_invlist : Warning< + "multiple invocations for '%0' are found in the invocation list">, + InGroup<CrossTU>; + +def warn_invlist_missing_file : Warning< + "invocation for '%0' is missing in the invocation list">, InGroup<CrossTU>; def warn_ctu_incompat_triple : Warning< "imported AST from '%0' had been generated for a diff erent target, " "current: %1, imported: %2">, InGroup<CrossTU>; + +def warn_ctu_import_failure : Warning< + "import of an external symbol for CTU failed: %0">, InGroup<CrossTU>; + +def err_ctu_import_failure: Error< + "import of an external symbol for CTU failed: %0">; + +def remark_ctu_import_threshold_reached: Remark< + "reached a the CTU-import threshold before trying to import definition">, + InGroup<CrossTU>; + +def warn_ctu_incompat_lang : Warning< + "imported AST from '%0' had been generated for a diff erent language, " + "current: %1, imported: %2">, InGroup<CrossTU>; } diff --git a/clang/include/clang/CrossTU/CrossTranslationUnit.h b/clang/include/clang/CrossTU/CrossTranslationUnit.h index 145bc8df27de6..dd7eceef35b7f 100644 --- a/clang/include/clang/CrossTU/CrossTranslationUnit.h +++ b/clang/include/clang/CrossTU/CrossTranslationUnit.h @@ -64,25 +64,25 @@ class IndexError : public llvm::ErrorInfo<IndexError> { IndexError(index_error_code C) : Code(C), LineNo(0) {} IndexError(index_error_code C, std::string FileName, int LineNo = 0) : Code(C), FileName(std::move(FileName)), LineNo(LineNo) {} - IndexError(index_error_code C, std::string FileName, std::string TripleToName, - std::string TripleFromName) + IndexError(index_error_code C, std::string FileName, std::string ConfigToName, + std::string ConfigFromName) : Code(C), FileName(std::move(FileName)), - TripleToName(std::move(TripleToName)), - TripleFromName(std::move(TripleFromName)) {} + ConfigToName(std::move(ConfigToName)), + ConfigFromName(std::move(ConfigFromName)) {} void log(raw_ostream &OS) const override; std::error_code convertToErrorCode() const override; index_error_code getCode() const { return Code; } int getLineNum() const { return LineNo; } std::string getFileName() const { return FileName; } - std::string getTripleToName() const { return TripleToName; } - std::string getTripleFromName() const { return TripleFromName; } + std::string getConfigToName() const { return ConfigToName; } + std::string getConfigFromName() const { return ConfigFromName; } private: index_error_code Code; std::string FileName; int LineNo; - std::string TripleToName; - std::string TripleFromName; + std::string ConfigToName; + std::string ConfigFromName; }; /// This function parses an index file that determines which @@ -107,7 +107,8 @@ using InvocationListTy = llvm::StringMap<llvm::SmallVector<std::string, 32>>; /// will be used to produce the AST of the TU. llvm::Expected<InvocationListTy> parseInvocationList( StringRef FileContent, - llvm::sys::path::Style PathStyle = llvm::sys::path::Style::posix); + llvm::sys::path::Style PathStyle = llvm::sys::path::Style::posix, + StringRef FilePath = ""); /// Returns true if it makes sense to import a foreign variable definition. /// For instance, we don't want to import variables that have non-trivial types @@ -264,7 +265,7 @@ class CrossTranslationUnitContext { /// In case of on-demand parsing, the invocations for parsing the source /// files is stored. std::optional<InvocationListTy> InvocationList; - index_error_code PreviousParsingResult = index_error_code::success; + std::optional<IndexError> PreviousError; }; /// Maintain number of AST loads and check for reaching the load limit. @@ -346,6 +347,8 @@ class CrossTranslationUnitContext { }; ASTUnitStorage ASTStorage; + + bool HasEmittedLoadThresholdRemark = false; }; } // namespace cross_tu diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp index 8dd0ef13123d1..a911fa4e1813f 100644 --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -142,6 +142,29 @@ class IndexErrorCategory : public std::error_category { static llvm::ManagedStatic<IndexErrorCategory> Category; } // end anonymous namespace +/// Returns a human-readable language/dialect description for diagnostics. +/// Checks flags from highest to lowest standard since they are cumulative +/// (e.g. CPlusPlus20 implies CPlusPlus17). +/// This does not cover all possible languages (e.g. Obj-C or flavors of C), +/// because CTU currently does not diff erentiate between them. +static std::string getLangDescription(const LangOptions &LO) { + if (!LO.CPlusPlus) + return "non-C++"; + if (LO.CPlusPlus26) + return "C++26"; + if (LO.CPlusPlus23) + return "C++23"; + if (LO.CPlusPlus20) + return "C++20"; + if (LO.CPlusPlus17) + return "C++17"; + if (LO.CPlusPlus14) + return "C++14"; + if (LO.CPlusPlus11) + return "C++11"; + return "C++98"; +} + char IndexError::ID; void IndexError::log(raw_ostream &OS) const { @@ -330,7 +353,9 @@ llvm::Expected<const T *> CrossTranslationUnitContext::getCrossTUDefinitionImpl( // diff erent dialects of C++. if (LangTo.CPlusPlus != LangFrom.CPlusPlus) { ++NumLangMismatch; - return llvm::make_error<IndexError>(index_error_code::lang_mismatch); + return llvm::make_error<IndexError>( + index_error_code::lang_mismatch, std::string(Unit->getMainFileName()), + getLangDescription(LangTo), getLangDescription(LangFrom)); } // If CPP dialects are diff erent then return with error. @@ -351,8 +376,10 @@ llvm::Expected<const T *> CrossTranslationUnitContext::getCrossTUDefinitionImpl( LangTo.CPlusPlus17 != LangFrom.CPlusPlus17 || LangTo.CPlusPlus20 != LangFrom.CPlusPlus20) { ++NumLangDialectMismatch; - return llvm::make_error<IndexError>( - index_error_code::lang_dialect_mismatch); + return llvm::make_error<IndexError>(index_error_code::lang_dialect_mismatch, + std::string(Unit->getMainFileName()), + getLangDescription(LangTo), + getLangDescription(LangFrom)); } TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl(); @@ -383,39 +410,96 @@ void CrossTranslationUnitContext::emitCrossTUDiagnostics(const IndexError &IE, SourceLocation Loc) { switch (IE.getCode()) { case index_error_code::missing_index_file: + case index_error_code::invocation_list_file_not_found: + // If the external def-map refers to source files, you must provide an + // invocation list file. Otherwise, CTU does not work at all, so you should + // check your build and analysis configuration. Context.getDiagnostics().Report(Loc, diag::err_ctu_error_opening) << IE.getFileName(); return; + case index_error_code::invalid_index_format: Context.getDiagnostics().Report(Loc, diag::err_extdefmap_parsing) << IE.getFileName() << IE.getLineNum(); return; + case index_error_code::multiple_definitions: Context.getDiagnostics().Report(Loc, diag::err_multiple_def_index) << IE.getLineNum(); return; + case index_error_code::triple_mismatch: Context.getDiagnostics().Report(Loc, diag::warn_ctu_incompat_triple) - << IE.getFileName() << IE.getTripleToName() << IE.getTripleFromName(); - return; - case index_error_code::success: - llvm_unreachable("There should not be a success error. This case should " - "have been handled by the caller."); + << IE.getFileName() << IE.getConfigToName() << IE.getConfigFromName(); return; - case index_error_code::unspecified: + case index_error_code::missing_definition: + // Ignore missing definitions because it is very common to have some symbols + // defined outside of the analysis scope: they may be defined in 3-rd party + // and standard libraries, generated code, and files excluded from the + // analysis. + // Even ignoring it with Ignored diagnostic might generate too much traffic. + return; + case index_error_code::failed_import: - case index_error_code::failed_to_get_external_ast: + case index_error_code::unspecified: + // Not clear what happened exactly, but the outcome is a missing definition + // This is not a big deal, and is expected since ASTImporter is incomplete. + Context.getDiagnostics().Report(Loc, diag::warn_ctu_import_failure) + << Category->message(static_cast<int>(IE.getCode())); + return; + case index_error_code::failed_to_generate_usr: + // This is unlikely, so it is worth looking into, hence an error. + case index_error_code::failed_to_get_external_ast: + // This is suspicious, since the external AST is mentioned in the external + // defmap, so it should exist. + Context.getDiagnostics().Report(Loc, diag::err_ctu_import_failure) + << Category->message(static_cast<int>(IE.getCode())); + return; + + case index_error_code::load_threshold_reached: + // This is expected. It is still useful to be aware of, but it is normal + // operation. Emit the remark only once to avoid noise. + if (!HasEmittedLoadThresholdRemark) { + HasEmittedLoadThresholdRemark = true; + Context.getDiagnostics().Report( + Loc, diag::remark_ctu_import_threshold_reached); + } + return; + case index_error_code::lang_mismatch: case index_error_code::lang_dialect_mismatch: - case index_error_code::load_threshold_reached: - case index_error_code::invocation_list_ambiguous: - case index_error_code::invocation_list_file_not_found: - case index_error_code::invocation_list_empty: + // Similar to target triple mismatch. + Context.getDiagnostics().Report(Loc, diag::warn_ctu_incompat_lang) + << IE.getFileName() << IE.getConfigToName() << IE.getConfigFromName(); + return; + case index_error_code::invocation_list_wrong_format: + case index_error_code::invocation_list_empty: + // Without parsable invocation list, CTU cannot function. + Context.getDiagnostics().Report(Loc, diag::err_invlist_parsing) + << IE.getFileName() << IE.getLineNum(); + return; + + case index_error_code::invocation_list_ambiguous: + // For automatically generated invocation lists, it is common to list + // multiple invocations, if a file is compiled in multiple contexts. No need + // to block CTU because of this. + Context.getDiagnostics().Report(Loc, diag::warn_multiple_entries_invlist) + << IE.getFileName(); + return; + case index_error_code::invocation_list_lookup_unsuccessful: - // FIXME: Silently dropping these errors + // Some files might be missing in the invocation list. It is sad but not + // fatal, and CTU can take advantage of the definitions in files with known + // invocations. + Context.getDiagnostics().Report(Loc, diag::warn_invlist_missing_file) + << IE.getFileName(); + return; + + case index_error_code::success: + llvm_unreachable("Success is not an error."); return; } llvm_unreachable("Unrecognized index_error_code."); @@ -624,7 +708,8 @@ CrossTranslationUnitContext::ASTLoader::loadFromSource( auto Invocation = InvocationList->find(SourceFilePath); if (Invocation == InvocationList->end()) return llvm::make_error<IndexError>( - index_error_code::invocation_list_lookup_unsuccessful); + index_error_code::invocation_list_lookup_unsuccessful, + SourceFilePath.str()); const InvocationListTy::mapped_type &InvocationCommand = Invocation->second; @@ -649,13 +734,23 @@ CrossTranslationUnitContext::ASTLoader::loadFromSource( } llvm::Expected<InvocationListTy> -parseInvocationList(StringRef FileContent, llvm::sys::path::Style PathStyle) { +parseInvocationList(StringRef FileContent, llvm::sys::path::Style PathStyle, + StringRef FilePath) { InvocationListTy InvocationList; /// LLVM YAML parser is used to extract information from invocation list file. llvm::SourceMgr SM; llvm::yaml::Stream InvocationFile(FileContent, SM); + auto GetLine = [&SM](const llvm::yaml::Node *N) -> int { + return N ? SM.FindLineNumber(N->getSourceRange().Start) : 0; + }; + auto WrongFormatError = [&](const llvm::yaml::Node *N) { + return llvm::make_error<IndexError>( + index_error_code::invocation_list_wrong_format, FilePath.str(), + GetLine(N)); + }; + /// Only the first document is processed. llvm::yaml::document_iterator FirstInvocationFile = InvocationFile.begin(); @@ -674,15 +769,13 @@ parseInvocationList(StringRef FileContent, llvm::sys::path::Style PathStyle) { /// parts. auto *Mappings = dyn_cast<llvm::yaml::MappingNode>(DocumentRoot); if (!Mappings) - return llvm::make_error<IndexError>( - index_error_code::invocation_list_wrong_format); + return WrongFormatError(DocumentRoot); for (auto &NextMapping : *Mappings) { /// The keys should be strings, which represent a source-file path. auto *Key = dyn_cast<llvm::yaml::ScalarNode>(NextMapping.getKey()); if (!Key) - return llvm::make_error<IndexError>( - index_error_code::invocation_list_wrong_format); + return WrongFormatError(NextMapping.getKey()); SmallString<32> ValueStorage; StringRef SourcePath = Key->getValue(ValueStorage); @@ -695,20 +788,18 @@ parseInvocationList(StringRef FileContent, llvm::sys::path::Style PathStyle) { if (InvocationList.contains(InvocationKey)) return llvm::make_error<IndexError>( - index_error_code::invocation_list_ambiguous); + index_error_code::invocation_list_ambiguous, InvocationKey.str()); /// The values should be sequences of strings, each representing a part of /// the invocation. auto *Args = dyn_cast<llvm::yaml::SequenceNode>(NextMapping.getValue()); if (!Args) - return llvm::make_error<IndexError>( - index_error_code::invocation_list_wrong_format); + return WrongFormatError(NextMapping.getValue()); for (auto &Arg : *Args) { auto *CmdString = dyn_cast<llvm::yaml::ScalarNode>(&Arg); if (!CmdString) - return llvm::make_error<IndexError>( - index_error_code::invocation_list_wrong_format); + return WrongFormatError(&Arg); /// Every conversion starts with an empty working storage, as it is not /// clear if this is a requirement of the YAML parser. ValueStorage.clear(); @@ -717,8 +808,7 @@ parseInvocationList(StringRef FileContent, llvm::sys::path::Style PathStyle) { } if (InvocationList[InvocationKey].empty()) - return llvm::make_error<IndexError>( - index_error_code::invocation_list_wrong_format); + return WrongFormatError(Key); } return InvocationList; @@ -728,28 +818,28 @@ llvm::Error CrossTranslationUnitContext::ASTLoader::lazyInitInvocationList() { /// Lazily initialize the invocation list member used for on-demand parsing. if (InvocationList) return llvm::Error::success(); - if (index_error_code::success != PreviousParsingResult) - return llvm::make_error<IndexError>(PreviousParsingResult); + if (PreviousError) + return llvm::make_error<IndexError>(*PreviousError); llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileContent = CI.getVirtualFileSystem().getBufferForFile(InvocationListFilePath); if (!FileContent) { - PreviousParsingResult = index_error_code::invocation_list_file_not_found; - return llvm::make_error<IndexError>(PreviousParsingResult); + PreviousError = IndexError(index_error_code::invocation_list_file_not_found, + InvocationListFilePath.str()); + return llvm::make_error<IndexError>(*PreviousError); } std::unique_ptr<llvm::MemoryBuffer> ContentBuffer = std::move(*FileContent); assert(ContentBuffer && "If no error was produced after loading, the pointer " "should not be nullptr."); - llvm::Expected<InvocationListTy> ExpectedInvocationList = - parseInvocationList(ContentBuffer->getBuffer(), PathStyle); + llvm::Expected<InvocationListTy> ExpectedInvocationList = parseInvocationList( + ContentBuffer->getBuffer(), PathStyle, InvocationListFilePath); - // Handle the error to store the code for next call to this function. if (!ExpectedInvocationList) { llvm::handleAllErrors( ExpectedInvocationList.takeError(), - [&](const IndexError &E) { PreviousParsingResult = E.getCode(); }); - return llvm::make_error<IndexError>(PreviousParsingResult); + [this](const IndexError &E) { this->PreviousError = E; }); + return llvm::make_error<IndexError>(*PreviousError); } InvocationList = *ExpectedInvocationList; diff --git a/clang/test/Analysis/ctu/diag/Inputs/third.cpp b/clang/test/Analysis/ctu/diag/Inputs/third.cpp new file mode 100644 index 0000000000000..92464d5a46804 --- /dev/null +++ b/clang/test/Analysis/ctu/diag/Inputs/third.cpp @@ -0,0 +1 @@ +int third(int x) { return 3 * x; } diff --git a/clang/test/Analysis/ctu/diag/invlist-ambiguous.cpp b/clang/test/Analysis/ctu/diag/invlist-ambiguous.cpp index 570c6c7fe2585..3c106cee78a56 100644 --- a/clang/test/Analysis/ctu/diag/invlist-ambiguous.cpp +++ b/clang/test/Analysis/ctu/diag/invlist-ambiguous.cpp @@ -14,6 +14,5 @@ int foo(int); void test() { - // expected-no-diagnostics - foo(1); // no-warning. Ignoring "Invocation list file contains multiple references to the same source file." + foo(1); // expected-warning{{multiple invocations for '/some/path.cpp' are found in the invocation list}} } diff --git a/clang/test/Analysis/ctu/diag/invlist-empty.cpp b/clang/test/Analysis/ctu/diag/invlist-empty.cpp index b0504960b98b2..53fab169da78d 100644 --- a/clang/test/Analysis/ctu/diag/invlist-empty.cpp +++ b/clang/test/Analysis/ctu/diag/invlist-empty.cpp @@ -3,7 +3,8 @@ // Note: invocation_list_empty (index_error_code::invocation_list_empty) is // dead code: llvm::yaml::Stream::begin() always creates at least one Document, // so FirstInvocationFile == InvocationFile.end() is never true. An empty file -// reaches the !DocumentRoot branch instead, producing invocation_list_wrong_format. +// produces a NullNode as the document root, which fails the dyn_cast to +// MappingNode, producing invocation_list_wrong_format at line 1. // // RUN: rm -rf %t && mkdir %t // RUN: echo '11:c:@F@foo#I# simple.cpp' > %t/externalDefMap.txt @@ -18,6 +19,5 @@ int foo(int); void test() { - // expected-no-diagnostics - foo(1); // no-warning. Ignoring "Invocation list file is in wrong format." + foo(1); // expected-error-re{{error parsing invocation list file: '{{.+}}invocations.yaml' line: 1 '<source-file>: [<compiler>, <arg1>, ...]' YAML mapping format expected}} } diff --git a/clang/test/Analysis/ctu/diag/invlist-lookup-miss.cpp b/clang/test/Analysis/ctu/diag/invlist-lookup-miss.cpp index 3adc035dff681..8a384e73fb005 100644 --- a/clang/test/Analysis/ctu/diag/invlist-lookup-miss.cpp +++ b/clang/test/Analysis/ctu/diag/invlist-lookup-miss.cpp @@ -16,6 +16,5 @@ int foo(int); void test() { - // expected-no-diagnostics - foo(1); // no-warning. Ignoring "Invocation list file does not contain the requested source file." + foo(1); // expected-warning-re{{invocation for '{{.+}}diag-simple.cpp' is missing in the invocation list}} } diff --git a/clang/test/Analysis/ctu/diag/invlist-missing.cpp b/clang/test/Analysis/ctu/diag/invlist-missing.cpp index bac264ab10b4e..e4edab66ef2a1 100644 --- a/clang/test/Analysis/ctu/diag/invlist-missing.cpp +++ b/clang/test/Analysis/ctu/diag/invlist-missing.cpp @@ -15,6 +15,5 @@ int foo(int); void test() { - // expected-no-diagnostics - foo(1); // no-warning. Ignoring "Invocation list file is not found." + foo(1); // expected-error-re{{error opening '{{.+}}nonexistent.yaml': required by the CrossTU functionality}} } diff --git a/clang/test/Analysis/ctu/diag/invlist-wrong-format-late.cpp b/clang/test/Analysis/ctu/diag/invlist-wrong-format-late.cpp new file mode 100644 index 0000000000000..3472c9a552c3b --- /dev/null +++ b/clang/test/Analysis/ctu/diag/invlist-wrong-format-late.cpp @@ -0,0 +1,19 @@ +// Test that a malformed invocation list entry on a non-first line reports the +// correct line number. The first mapping entry is valid; the second has a +// scalar value instead of a sequence, triggering invocation_list_wrong_format. +// +// RUN: rm -rf %t && mkdir %t +// RUN: echo '11:c:@F@foo#I# simple.cpp' > %t/externalDefMap.txt +// RUN: printf '/tmp/valid.cpp:\n - clang++\n/tmp/bad.cpp: not_a_sequence\n' > %t/invocations.yaml +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t \ +// RUN: -analyzer-config ctu-invocation-list=%t/invocations.yaml \ +// RUN: -verify %s + +int foo(int); + +void test() { + foo(1); // expected-error-re{{error parsing invocation list file: '{{.+}}invocations.yaml' line: 3 '<source-file>: [<compiler>, <arg1>, ...]' YAML mapping format expected}} +} diff --git a/clang/test/Analysis/ctu/diag/invlist-wrong-format.cpp b/clang/test/Analysis/ctu/diag/invlist-wrong-format.cpp index cfa3cc65e8d06..58ac139543823 100644 --- a/clang/test/Analysis/ctu/diag/invlist-wrong-format.cpp +++ b/clang/test/Analysis/ctu/diag/invlist-wrong-format.cpp @@ -16,6 +16,5 @@ int foo(int); void test() { - // expected-no-diagnostics - foo(1); // no-warning. Ignoring "Invocation list file is in wrong format." + foo(1); // expected-error-re{{error parsing invocation list file: '{{.+}}invocations.yaml' line: 1 '<source-file>: [<compiler>, <arg1>, ...]' YAML mapping format expected}} } diff --git a/clang/test/Analysis/ctu/diag/lang-dialect-mismatch.cpp b/clang/test/Analysis/ctu/diag/lang-dialect-mismatch.cpp index c41072d46df02..d08f69df756b8 100644 --- a/clang/test/Analysis/ctu/diag/lang-dialect-mismatch.cpp +++ b/clang/test/Analysis/ctu/diag/lang-dialect-mismatch.cpp @@ -16,6 +16,5 @@ int foo(int); void test() { - // expected-no-diagnostics - foo(1); // no-warning. Ignoring "Invocation list file contains multiple references to the same source file." + foo(1); // expected-warning-re{{imported AST from '{{.+}}simple.cpp' had been generated for a diff erent language, current: C++14, imported: C++17}} } diff --git a/clang/test/Analysis/ctu/diag/lang-mismatch.c b/clang/test/Analysis/ctu/diag/lang-mismatch.c index deee7077ec495..58cc659cadea7 100644 --- a/clang/test/Analysis/ctu/diag/lang-mismatch.c +++ b/clang/test/Analysis/ctu/diag/lang-mismatch.c @@ -18,6 +18,5 @@ int foo(int); void test(void) { - // expected-no-diagnostics - foo(1); // no-warning. Ignoring "Language mismatch." + foo(1); // expected-warning-re{{imported AST from '{{.+}}simple-extern-c.cpp' had been generated for a diff erent language, current: non-C++, imported: C++14}} } diff --git a/clang/test/Analysis/ctu/diag/load-threshold.cpp b/clang/test/Analysis/ctu/diag/load-threshold.cpp index 9e40b8a42958f..4353dab60d0cd 100644 --- a/clang/test/Analysis/ctu/diag/load-threshold.cpp +++ b/clang/test/Analysis/ctu/diag/load-threshold.cpp @@ -1,8 +1,11 @@ // Test that exceeding ctu-import-cpp-threshold produces load_threshold_reached, -// which is silently fails AST import. +// which silently fails AST import. // // With threshold=1, the first external AST (foo) is loaded successfully. -// The second lookup (bar) finds the threshold exhausted and reports the error. +// The second lookup (bar) finds the threshold exhausted and emits a remark once. +// All subsequent threshold-blocked lookups fail silently, including those in a +// second analysis entry point (test2): the remark is not repeated, and the +// already-cached AST for foo remains accessible. // // RUN: rm -rf %t && mkdir %t // RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ @@ -11,23 +14,43 @@ // RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/bar.cpp.ast \ // RUN: %S/Inputs/bar.cpp +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -emit-pch -o %t/third.cpp.ast \ +// RUN: %S/Inputs/third.cpp // RUN: echo '11:c:@F@foo#I# simple.cpp.ast' > %t/externalDefMap.txt // RUN: echo '11:c:@F@bar#I# bar.cpp.ast' >> %t/externalDefMap.txt +// RUN: echo '13:c:@F@third#I# third.cpp.ast' >> %t/externalDefMap.txt // RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -analyzer-checker=core \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t \ // RUN: -analyzer-config ctu-import-cpp-threshold=1 \ +// RUN: -Rctu \ // RUN: -verify %s // foo is loaded successfully (first load, within threshold). -// bar hits the threshold (no telemetry emitted for it). +// bar is the first to hit the threshold; the remark is emitted once. +// Subsequent threshold-blocked lookups (bar(2), third) fail silently. +// +// test2() is a second entry point analyzed after test(). It is defined first +// to be analyzed last. int foo(int); int bar(int); +int third(int); + +// In a second entry point the threshold state persists from test(): the remark +// is not repeated, foo's AST is still accessible from cache, and new +// threshold-blocked lookups (bar, third) fail silently. +void test2() { + foo(1); // no remark: foo's AST was cached during test()'s analysis + bar(1); // no remark: threshold already reported in test() + third(1); // no remark: threshold already reported in test() +} void test() { foo(1); - // expected-no-diagnostics - bar(1); // no-warning. Ignoring "Load threshold reached." + bar(1); // expected-remark {{reached a the CTU-import threshold before trying to import definition}} + bar(2); // no remark: threshold already reported + third(1); // no remark: threshold already reported } diff --git a/clang/test/Analysis/ctu/import-type-decl-definition.c b/clang/test/Analysis/ctu/import-type-decl-definition.c index 10910e0812f3a..60fabb50f83e1 100644 --- a/clang/test/Analysis/ctu/import-type-decl-definition.c +++ b/clang/test/Analysis/ctu/import-type-decl-definition.c @@ -5,7 +5,9 @@ // RUN: %clang_cc1 -x c -emit-pch -o %t/import.c.ast %t/import.c // RUN: %clang_extdef_map %t/import.c -- -c -x c > %t/externalDefMap.tmp.txt -// RUN: sed 's/$/.ast/' %t/externalDefMap.tmp.txt > %t/externalDefMap.txt +// FIXME On windows, absolute path generated by extdef_map is not recognized, +// so CSA prepends the workdir path to it. Force relative path to workaround this issue. +// RUN: sed 's| .*import\.c| import.c.ast|' %t/externalDefMap.tmp.txt > %t/externalDefMap.txt // RUN: %clang_cc1 -analyze \ // RUN: -analyzer-checker=core \ diff --git a/clang/test/Analysis/ctu/invalid-ast.cpp b/clang/test/Analysis/ctu/invalid-ast.cpp index 9d0b850233dbe..2026f88ab6a74 100644 --- a/clang/test/Analysis/ctu/invalid-ast.cpp +++ b/clang/test/Analysis/ctu/invalid-ast.cpp @@ -21,6 +21,5 @@ void external(); void trigger() { - // expected-no-diagnostics - external(); // no-warning + external(); // expected-error{{import of an external symbol for CTU failed: Failed to load external AST source.}} } diff --git a/clang/test/Analysis/ctu/main.c b/clang/test/Analysis/ctu/main.c index 928da3cbea038..b36ca4a1faa0e 100644 --- a/clang/test/Analysis/ctu/main.c +++ b/clang/test/Analysis/ctu/main.c @@ -101,6 +101,8 @@ void testStructDefInArgument(void) { // Not imported, thus remains unknown both in stu and ctu. clang_analyzer_eval(structInProto(&d) == 0); // newctu-warning{{UNKNOWN}} // oldctu-warning@-1{{UNKNOWN}} + // newctu-warning@-2{{import of an external symbol for CTU failed: Failed to import the definition.}} + // oldctu-warning@-3{{import of an external symbol for CTU failed: Failed to import the definition.}} } int switchWithoutCases(int); diff --git a/clang/test/Analysis/ctu/missing-ast.cpp b/clang/test/Analysis/ctu/missing-ast.cpp index d39d5d8f05bf6..190b88d3146ba 100644 --- a/clang/test/Analysis/ctu/missing-ast.cpp +++ b/clang/test/Analysis/ctu/missing-ast.cpp @@ -19,6 +19,5 @@ void external(); void trigger() { - // expected-no-diagnostics - external(); // no-warning + external(); // expected-error{{import of an external symbol for CTU failed: Failed to load external AST source.}} } diff --git a/clang/test/Analysis/ctu/on-demand-parsing.c b/clang/test/Analysis/ctu/on-demand-parsing.c index d1d490bcf9a6d..4a94690fde05c 100644 --- a/clang/test/Analysis/ctu/on-demand-parsing.c +++ b/clang/test/Analysis/ctu/on-demand-parsing.c @@ -85,4 +85,5 @@ void testStructDefInArgument() { d.a = 1; d.b = 0; clang_analyzer_eval(structInProto(&d) == 0); // expected-warning{{TRUE}} expected-warning{{FALSE}} + // expected-warning@-1{{import of an external symbol for CTU failed: Failed to import the definition.}} } diff --git a/clang/test/Analysis/ctu/test-import-failure.cpp b/clang/test/Analysis/ctu/test-import-failure.cpp index 89ccd297ac3a7..3b5f9e71c2570 100644 --- a/clang/test/Analysis/ctu/test-import-failure.cpp +++ b/clang/test/Analysis/ctu/test-import-failure.cpp @@ -32,3 +32,4 @@ extern const int RootExamples[]; // expected-warning@Inputs/test-import-failure-import.cpp:14{{incompatible definitions}} // expected-note@Inputs/test-import-failure-import.cpp:14{{no corresponding field here}} // expected-note@Inputs/test-import-failure-import.cpp:14{{no corresponding field here}} +// expected-warning@Inputs/test-import-failure-import.cpp:44{{import of an external symbol for CTU failed: Failed to import the definition.}} diff --git a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp index 28f71d61e2c57..96d0c1954505c 100644 --- a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp +++ b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp @@ -191,6 +191,22 @@ TEST(CrossTranslationUnit, EmptyInvocationListIsNotValid) { EXPECT_TRUE(IsWrongFromatError); } +TEST(CrossTranslationUnit, WrongFormatInvocationListHasLineNumber) { + // The first entry is valid; the second has a scalar value instead of a + // sequence. The error should report the line of the malformed value. + auto Input = R"(/tmp/valid.cpp: + - clang++ +/tmp/bad.cpp: not_a_sequence +)"; + + llvm::Expected<InvocationListTy> Result = parseInvocationList(Input); + EXPECT_FALSE(static_cast<bool>(Result)); + llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) { + EXPECT_EQ(Err.getCode(), index_error_code::invocation_list_wrong_format); + EXPECT_EQ(Err.getLineNum(), 3); + }); +} + TEST(CrossTranslationUnit, AmbiguousInvocationListIsDetected) { // The same source file occurs twice (for two diff erent architecture) in // this test case. The disambiguation is the responsibility of the user. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
