diff --git a/clang/include/clang/CrossTU/CrossTranslationUnit.h b/clang/include/clang/CrossTU/CrossTranslationUnit.h --- a/clang/include/clang/CrossTU/CrossTranslationUnit.h +++ b/clang/include/clang/CrossTU/CrossTranslationUnit.h @@ -38,6 +38,7 @@ namespace cross_tu { enum class index_error_code { + success = 0, unspecified = 1, missing_index_file, invalid_index_format, @@ -253,6 +254,7 @@ /// In case of on-demand parsing, the invocations for parsing the source /// files is stored. llvm::Optional InvocationList; + index_error_code PreviousParsingResult = index_error_code::success; }; /// Maintain number of AST loads and check for reaching the load limit. diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -92,6 +92,10 @@ std::string message(int Condition) const override { switch (static_cast(Condition)) { + case index_error_code::success: + // There should not be a success error. Jump to unreachable directly. + // Add this case to make the compiler stop complaining. + break; case index_error_code::unspecified: return "An unknown error has occurred."; case index_error_code::missing_index_file: @@ -667,12 +671,15 @@ /// 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(PreviousParsingResult); llvm::ErrorOr> FileContent = llvm::MemoryBuffer::getFile(InvocationListFilePath); - if (!FileContent) - return llvm::make_error( - index_error_code::invocation_list_file_not_found); + if (!FileContent) { + PreviousParsingResult = index_error_code::invocation_list_file_not_found; + return llvm::make_error(PreviousParsingResult); + } std::unique_ptr ContentBuffer = std::move(*FileContent); assert(ContentBuffer && "If no error was produced after loading, the pointer " "should not be nullptr."); @@ -680,8 +687,13 @@ llvm::Expected ExpectedInvocationList = parseInvocationList(ContentBuffer->getBuffer(), PathStyle); - if (!ExpectedInvocationList) - return ExpectedInvocationList.takeError(); + // 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(PreviousParsingResult); + } InvocationList = *ExpectedInvocationList; diff --git a/clang/test/Analysis/ctu-on-demand-parsing-multiple-invocation-list-parsing.cpp b/clang/test/Analysis/ctu-on-demand-parsing-multiple-invocation-list-parsing.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/ctu-on-demand-parsing-multiple-invocation-list-parsing.cpp @@ -0,0 +1,56 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// RUN: %host_cxx %s -fPIC -shared -o %t/mock_open.so + +// RUN: echo "void bar(); void foo() { bar(); bar(); }" > %t/trigger.c +// RUN: echo "void bar() {}" > %t/importee.c +// RUN: echo '[{"directory":"%t", "command":"cc -c %t/importee.c", "file": "%t/importee.c"}]' > %t/compile_commands.json +// RUN: %clang_extdef_map -p %t "%t/importee.c" > %t/externalDefMap.txt + +// Add an empty invocation list to make the on-demand parsing fail and load it again. +// RUN: echo '' > %t/invocations.yaml + +// RUN: cd %t && \ +// RUN: LD_PRELOAD=%t/mock_open.so \ +// RUN: %clang_cc1 -fsyntax-only -analyze \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=. \ +// RUN: -analyzer-config ctu-invocation-list=invocations.yaml \ +// RUN: %t/trigger.c | FileCheck %s + +// REQUIRES: shell, system-linux + +// CHECK: {{Opening file invocations.yaml: 1}} +// CHECK-NOT: {{Opening file invocations.yaml: 2}} + +#define _GNU_SOURCE 1 +#include +#include + +#include +#include +#include +using namespace std; + +extern "C" int open(const char *name, int flag, ...) { + // Log how many times the invocation list is opened. + if ("invocations.yaml" == string(name)) { + static unsigned N = 0; + cout << "Opening file invocations.yaml: " << ++N << endl; + } + + // The original open function will be called to open the files. + using open_t = int (*)(const char *, int, mode_t); + static open_t o_open = nullptr; + if (!o_open) + o_open = reinterpret_cast(dlsym(RTLD_NEXT, "open")); + assert(o_open && "Cannot find function `open'."); + + va_list vl; + va_start(vl, flag); + auto mode = va_arg(vl, mode_t); + va_end(vl); + return o_open(name, flag, mode); +}