From 76f57b28554f0c281b1e5e3dc2749fe8c4893193 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 4 Dec 2017 21:56:36 +0000 Subject: [PATCH] [libclang] Record parsing invocation to a temporary file when requested by client This patch extends libclang by allowing it to record parsing operations to a temporary JSON file. The file is deleted after parsing succeeds. When a crash happens during parsing, the file is preserved and the client will be able to use it to generate a reproducer for the crash. These files are not emitted by default, and the client has to specify the invocation emission path first. rdar://35322543 Differential Revision: https://reviews.llvm.org/D40527 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@319702 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang-c/Index.h | 10 +++++ test/Index/record-parsing-invocation.c | 21 ++++++++++ tools/c-index-test/c-index-test.c | 4 ++ tools/libclang/CIndex.cpp | 11 ++++++ tools/libclang/CIndexer.cpp | 55 ++++++++++++++++++++++++++ tools/libclang/CIndexer.h | 28 +++++++++++++ tools/libclang/libclang.exports | 1 + 7 files changed, 130 insertions(+) create mode 100644 test/Index/record-parsing-invocation.c diff --git a/include/clang-c/Index.h b/include/clang-c/Index.h index c72be56fdc..fe0e70b4f4 100644 --- a/include/clang-c/Index.h +++ b/include/clang-c/Index.h @@ -333,6 +333,16 @@ CINDEX_LINKAGE void clang_CXIndex_setGlobalOptions(CXIndex, unsigned options); */ CINDEX_LINKAGE unsigned clang_CXIndex_getGlobalOptions(CXIndex); +/** + * \brief Sets the invocation emission path option in a CXIndex. + * + * The invocation emission path specifies a path which will contain log + * files for certain libclang invocations. A null value (default) implies that + * libclang invocations are not logged.. + */ +CINDEX_LINKAGE void +clang_CXIndex_setInvocationEmissionPathOption(CXIndex, const char *Path); + /** * \defgroup CINDEX_FILES File manipulation routines * diff --git a/test/Index/record-parsing-invocation.c b/test/Index/record-parsing-invocation.c new file mode 100644 index 0000000000..7baf68a733 --- /dev/null +++ b/test/Index/record-parsing-invocation.c @@ -0,0 +1,21 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: not env CINDEXTEST_INVOCATION_EMISSION_PATH=%t c-index-test -test-load-source all %s +// RUN: cat %t/libclang-* | FileCheck %s + +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: env LIBCLANG_DISABLE_CRASH_RECOVERY=1 CINDEXTEST_INVOCATION_EMISSION_PATH=%t not --crash c-index-test -test-load-source all %s +// RUN: cat %t/libclang-* | FileCheck %s + +// Verify that the file is removed for successful operation: +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: env CINDEXTEST_INVOCATION_EMISSION_PATH=%t c-index-test -test-load-source all %s -DAVOID_CRASH +// RUN: ls %t | count 0 + +#ifndef AVOID_CRASH +# pragma clang __debug parser_crash +#endif + +// CHECK: {"toolchain":"{{.*}}","libclang.operation":"parse","libclang.opts":1,"args":["clang","-fno-spell-checking","{{.*}}/record-parsing-invocation.c","-Xclang","-detailed-preprocessing-record","-fallow-editor-placeholders"]} diff --git a/tools/c-index-test/c-index-test.c b/tools/c-index-test/c-index-test.c index a11e03c846..6cb67e1030 100644 --- a/tools/c-index-test/c-index-test.c +++ b/tools/c-index-test/c-index-test.c @@ -1747,11 +1747,15 @@ int perform_test_load_source(int argc, const char **argv, int result; unsigned Repeats = 0; unsigned I; + const char *InvocationPath; Idx = clang_createIndex(/* excludeDeclsFromPCH */ (!strcmp(filter, "local") || !strcmp(filter, "local-display"))? 1 : 0, /* displayDiagnostics=*/1); + InvocationPath = getenv("CINDEXTEST_INVOCATION_EMISSION_PATH"); + if (InvocationPath) + clang_CXIndex_setInvocationEmissionPathOption(Idx, InvocationPath); if ((CommentSchemaFile = parse_comments_schema(argc, argv))) { argc--; diff --git a/tools/libclang/CIndex.cpp b/tools/libclang/CIndex.cpp index 455151f0fc..b642014c43 100644 --- a/tools/libclang/CIndex.cpp +++ b/tools/libclang/CIndex.cpp @@ -3255,6 +3255,12 @@ unsigned clang_CXIndex_getGlobalOptions(CXIndex CIdx) { return 0; } +void clang_CXIndex_setInvocationEmissionPathOption(CXIndex CIdx, + const char *Path) { + if (CIdx) + static_cast(CIdx)->setInvocationEmissionPath(Path ? Path : ""); +} + void clang_toggleCrashRecovery(unsigned isEnabled) { if (isEnabled) llvm::CrashRecoveryContext::Enable(); @@ -3431,6 +3437,11 @@ clang_parseTranslationUnit_Impl(CXIndex CIdx, const char *source_filename, // faster, trading for a slower (first) reparse. unsigned PrecompilePreambleAfterNParses = !PrecompilePreamble ? 0 : 2 - CreatePreambleOnFirstParse; + + // FIXME: Record the hash of the unsaved files. + LibclangInvocationReporter InvocationReporter( + *CXXIdx, LibclangInvocationReporter::OperationKind::ParseOperation, + options, llvm::makeArrayRef(*Args)); std::unique_ptr Unit(ASTUnit::LoadFromCommandLine( Args->data(), Args->data() + Args->size(), CXXIdx->getPCHContainerOperations(), Diags, diff --git a/tools/libclang/CIndexer.cpp b/tools/libclang/CIndexer.cpp index 694ed60630..13774bd8b7 100644 --- a/tools/libclang/CIndexer.cpp +++ b/tools/libclang/CIndexer.cpp @@ -14,8 +14,10 @@ #include "CIndexer.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/Version.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/Config/llvm-config.h" +#include "llvm/Support/MutexGuard.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include @@ -76,3 +78,56 @@ const std::string &CIndexer::getClangResourcesPath() { ResourcesPath = LibClangPath.str(); return ResourcesPath; } + +StringRef CIndexer::getClangToolchainPath() { + if (!ToolchainPath.empty()) + return ToolchainPath; + StringRef ResourcePath = getClangResourcesPath(); + ToolchainPath = llvm::sys::path::parent_path( + llvm::sys::path::parent_path(llvm::sys::path::parent_path(ResourcePath))); + return ToolchainPath; +} + +LibclangInvocationReporter::LibclangInvocationReporter( + CIndexer &Idx, OperationKind Op, unsigned ParseOptions, + llvm::ArrayRef Args) { + StringRef Path = Idx.getInvocationEmissionPath(); + if (Path.empty()) + return; + + // Create a temporary file for the invocation log. + SmallString<256> TempPath; + TempPath = Path; + llvm::sys::path::append(TempPath, "libclang-%%%%%%%%%%%%"); + int FD; + if (llvm::sys::fs::createUniqueFile(TempPath, FD, TempPath)) + return; + File = std::string(TempPath.begin(), TempPath.end()); + llvm::raw_fd_ostream OS(FD, /*ShouldClose=*/true); + + // Write out the information about the invocation to it. + auto WriteStringKey = [&OS](StringRef Key, StringRef Value) { + OS << R"(")" << Key << R"(":")"; + OS << Value << '"'; + }; + OS << '{'; + WriteStringKey("toolchain", Idx.getClangToolchainPath()); + OS << ','; + WriteStringKey("libclang.operation", + Op == OperationKind::ParseOperation ? "parse" : "complete"); + OS << ','; + OS << R"("libclang.opts":)" << ParseOptions; + OS << ','; + OS << R"("args":[)"; + for (const auto &I : llvm::enumerate(Args)) { + if (I.index()) + OS << ','; + OS << '"' << I.value() << '"'; + } + OS << "]}"; +} + +LibclangInvocationReporter::~LibclangInvocationReporter() { + if (!File.empty()) + llvm::sys::fs::remove(File); +} diff --git a/tools/libclang/CIndexer.h b/tools/libclang/CIndexer.h index b227f943f7..b3346cd955 100644 --- a/tools/libclang/CIndexer.h +++ b/tools/libclang/CIndexer.h @@ -18,6 +18,7 @@ #include "clang-c/Index.h" #include "clang/Frontend/PCHContainerOperations.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Mutex.h" #include namespace llvm { @@ -40,6 +41,10 @@ class CIndexer { std::string ResourcesPath; std::shared_ptr PCHContainerOps; + std::string ToolchainPath; + + std::string InvocationEmissionPath; + public: CIndexer(std::shared_ptr PCHContainerOps = std::make_shared()) @@ -71,6 +76,29 @@ public: /// \brief Get the path of the clang resource files. const std::string &getClangResourcesPath(); + + StringRef getClangToolchainPath(); + + void setInvocationEmissionPath(StringRef Str) { + InvocationEmissionPath = Str; + } + + StringRef getInvocationEmissionPath() const { return InvocationEmissionPath; } +}; + +/// Logs information about a particular libclang operation like parsing to +/// a new file in the invocation emission path. +class LibclangInvocationReporter { +public: + enum class OperationKind { ParseOperation, CompletionOperation }; + + LibclangInvocationReporter(CIndexer &Idx, OperationKind Op, + unsigned ParseOptions, + llvm::ArrayRef Args); + ~LibclangInvocationReporter(); + +private: + std::string File; }; /// \brief Return the current size to request for "safety". diff --git a/tools/libclang/libclang.exports b/tools/libclang/libclang.exports index fb4547a867..bd262ee1a0 100644 --- a/tools/libclang/libclang.exports +++ b/tools/libclang/libclang.exports @@ -2,6 +2,7 @@ clang_CXCursorSet_contains clang_CXCursorSet_insert clang_CXIndex_getGlobalOptions clang_CXIndex_setGlobalOptions +clang_CXIndex_setInvocationEmissionPathOption clang_CXXConstructor_isConvertingConstructor clang_CXXConstructor_isCopyConstructor clang_CXXConstructor_isDefaultConstructor -- 2.40.0