[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
This commit is contained in:
parent
5e0a06b34d
commit
e3cfcf48d0
20 changed files with 248 additions and 73 deletions
|
|
@ -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 different 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 different language, "
|
||||
"current: %1, imported: %2">, InGroup<CrossTU>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,25 +64,25 @@ public:
|
|||
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 @@ private:
|
|||
/// 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 @@ private:
|
|||
};
|
||||
|
||||
ASTUnitStorage ASTStorage;
|
||||
|
||||
bool HasEmittedLoadThresholdRemark = false;
|
||||
};
|
||||
|
||||
} // namespace cross_tu
|
||||
|
|
|
|||
|
|
@ -142,6 +142,29 @@ public:
|
|||
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 differentiate 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(
|
|||
// different 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 different 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();
|
||||
<< IE.getFileName() << IE.getConfigToName() << IE.getConfigFromName();
|
||||
return;
|
||||
case index_error_code::success:
|
||||
llvm_unreachable("There should not be a success error. This case should "
|
||||
"have been handled by the caller.");
|
||||
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;
|
||||
|
|
|
|||
1
clang/test/Analysis/ctu/diag/Inputs/third.cpp
Normal file
1
clang/test/Analysis/ctu/diag/Inputs/third.cpp
Normal file
|
|
@ -0,0 +1 @@
|
|||
int third(int x) { return 3 * x; }
|
||||
|
|
@ -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}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
}
|
||||
|
|
|
|||
19
clang/test/Analysis/ctu/diag/invlist-wrong-format-late.cpp
Normal file
19
clang/test/Analysis/ctu/diag/invlist-wrong-format-late.cpp
Normal file
|
|
@ -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}}
|
||||
}
|
||||
|
|
@ -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}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 different language, current: C++14, imported: C++17}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 different language, current: non-C++, imported: C++14}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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.}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.}}
|
||||
|
|
|
|||
|
|
@ -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 different architecture) in
|
||||
// this test case. The disambiguation is the responsibility of the user.
|
||||
|
|
|
|||
Loading…
Reference in a new issue