diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -641,7 +641,7 @@ auto BuiltPreamble = PrecompiledPreamble::Build( CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, Stats ? TimedFS : StatCacheFS, std::make_shared(), - StoreInMemory, CapturedInfo); + /*StoragePath=*/StringRef(), StoreInMemory, CapturedInfo); PreambleTimer.stopTimer(); // When building the AST for the main file, we do want the function diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -260,6 +260,9 @@ which identifies whether a constructor or conversion function cursor was marked with the explicit identifier. +- Introduced the new function ``clang_createIndexWithOptions``, + which allows overriding precompiled preamble storage path. + Static Analyzer --------------- diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -34,7 +34,7 @@ * compatible, thus CINDEX_VERSION_MAJOR is expected to remain stable. */ #define CINDEX_VERSION_MAJOR 0 -#define CINDEX_VERSION_MINOR 63 +#define CINDEX_VERSION_MINOR 64 #define CINDEX_VERSION_ENCODE(major, minor) (((major)*10000) + ((minor)*1)) @@ -309,9 +309,104 @@ } CXGlobalOptFlags; +typedef struct CXIndexOptions { + /** + * The size of struct CXIndexOptions used for option versioning. + * + * Always assign sizeof(CXIndexOptions) to this member right after + * creating a CXIndexOptions object. + */ + size_t Size; + /** + * A bitmask of options, a bitwise OR of CXGlobalOpt_XXX flags. + */ + unsigned GlobalOptions; + /** + * \see clang_createIndex() + */ + int ExcludeDeclarationsFromPCH : 1; + int DisplayDiagnostics : 1; + /** + * The path to a directory, in which to store temporary PCH files. If null or + * empty, the default system temporary directory is used. These PCH files are + * deleted on clean exit but stay on disk if the program crashes or is killed. + * + * Libclang does not create the directory at the specified path in the file + * system. Therefore it must exist, or storing PCH files will fail. + */ + const char *PreambleStoragePath; + /** + * Specifies a path which will contain log files for certain libclang + * invocations. A null value implies that libclang invocations are not logged. + */ + const char *InvocationEmissionPath; +} CXIndexOptions; + +/** + * Gets the default value of general options associated with a CXIndex. + * + * This default value is used by a CXIndex created via clang_createIndex(). + * + * \returns A bitmask of options, a bitwise OR of CXGlobalOpt_XXX flags that + * depends on the process environment. + */ +CINDEX_LINKAGE unsigned clang_getDefaultGlobalOptions(); + +/** + * Provides a shared context for creating translation units. + * + * Call this function instead of clang_createIndex() if you need to configure + * the additional options in CXIndexOptions. + * + * \returns The created index or null in case of error, such as an unsupported + * value of options->Size. + * + * For example: + * \code + * CXIndex createIndex(const char *ApplicationTemporaryPath) { + * const unsigned ForcedGlobalOptions = + * CXGlobalOpt_ThreadBackgroundPriorityForIndexing; + * const int ExcludeDeclarationsFromPCH = 1; + * const int DisplayDiagnostics = 1; + * #if CINDEX_VERSION_MINOR >= 64 + * const unsigned DefaultGlobalOptions = clang_getDefaultGlobalOptions(); + * CXIndexOptions Opts; + * Opts.Size = sizeof(CXIndexOptions); + * Opts.GlobalOptions = DefaultGlobalOptions | ForcedGlobalOptions; + * Opts.ExcludeDeclarationsFromPCH = ExcludeDeclarationsFromPCH; + * Opts.DisplayDiagnostics = DisplayDiagnostics; + * Opts.PreambleStoragePath = ApplicationTemporaryPath; + * Opts.InvocationEmissionPath = NULL; + * CXIndex Idx = clang_createIndexWithOptions(&Opts); + * if (Idx) + * return Idx; + * fprintf(stderr, "clang_createIndexWithOptions() failed. " + * "CINDEX_VERSION_MINOR = %d, sizeof(CXIndexOptions) = %u\n", + * CINDEX_VERSION_MINOR, (unsigned)Opts.Size); + * Idx = clang_createIndex(ExcludeDeclarationsFromPCH, DisplayDiagnostics); + * #else + * (void)ApplicationTemporaryPath; + * CXIndex Idx = clang_createIndex(ExcludeDeclarationsFromPCH, + * DisplayDiagnostics); + * const unsigned DefaultGlobalOptions = clang_CXIndex_getGlobalOptions(Idx); + * #endif + * clang_CXIndex_setGlobalOptions(Idx, + * DefaultGlobalOptions | ForcedGlobalOptions); + * return Idx; + * } + * \endcode + * + * \sa clang_createIndex() + */ +CINDEX_LINKAGE CXIndex +clang_createIndexWithOptions(const CXIndexOptions *options); + /** * Sets general options associated with a CXIndex. * + * This function is DEPRECATED. Set CXIndexOptions::GlobalOptions and call + * clang_createIndexWithOptions() instead. + * * For example: * \code * CXIndex idx = ...; @@ -335,6 +430,9 @@ /** * Sets the invocation emission path option in a CXIndex. * + * This function is DEPRECATED. Set CXIndexOptions::InvocationEmissionPath and + * call clang_createIndexWithOptions() instead. + * * 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.. diff --git a/clang/include/clang/Frontend/ASTUnit.h b/clang/include/clang/Frontend/ASTUnit.h --- a/clang/include/clang/Frontend/ASTUnit.h +++ b/clang/include/clang/Frontend/ASTUnit.h @@ -124,6 +124,7 @@ std::unique_ptr WriterData; FileSystemOptions FileSystemOpts; + std::string PreambleStoragePath; /// The AST consumer that received information about the translation /// unit as it was parsed or loaded. @@ -802,6 +803,10 @@ /// /// \param ResourceFilesPath - The path to the compiler resource files. /// + /// \param PreambleStoragePath - The path to a directory, in which to create + /// temporary PCH files. If empty, the default system temporary directory is + /// used. + /// /// \param ModuleFormat - If provided, uses the specific module format. /// /// \param ErrAST - If non-null and parsing failed without any AST to return @@ -820,7 +825,7 @@ const char **ArgBegin, const char **ArgEnd, std::shared_ptr PCHContainerOps, IntrusiveRefCntPtr Diags, StringRef ResourceFilesPath, - bool OnlyLocalDecls = false, + StringRef PreambleStoragePath = StringRef(), bool OnlyLocalDecls = false, CaptureDiagsKind CaptureDiagnostics = CaptureDiagsKind::None, ArrayRef RemappedFiles = std::nullopt, bool RemappedFilesKeepOriginalName = true, diff --git a/clang/include/clang/Frontend/PrecompiledPreamble.h b/clang/include/clang/Frontend/PrecompiledPreamble.h --- a/clang/include/clang/Frontend/PrecompiledPreamble.h +++ b/clang/include/clang/Frontend/PrecompiledPreamble.h @@ -72,6 +72,10 @@ /// /// \param PCHContainerOps An instance of PCHContainerOperations. /// + /// \param StoragePath The path to a directory, in which to create a temporary + /// file to store PCH in. If empty, the default system temporary directory is + /// used. This parameter is ignored if \p StoreInMemory is true. + /// /// \param StoreInMemory Store PCH in memory. If false, PCH will be stored in /// a temporary file. /// @@ -83,7 +87,8 @@ DiagnosticsEngine &Diagnostics, IntrusiveRefCntPtr VFS, std::shared_ptr PCHContainerOps, - bool StoreInMemory, PreambleCallbacks &Callbacks); + StringRef StoragePath, bool StoreInMemory, + PreambleCallbacks &Callbacks); PrecompiledPreamble(PrecompiledPreamble &&); PrecompiledPreamble &operator=(PrecompiledPreamble &&); diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp --- a/clang/lib/Frontend/ASTUnit.cpp +++ b/clang/lib/Frontend/ASTUnit.cpp @@ -1397,7 +1397,8 @@ llvm::ErrorOr NewPreamble = PrecompiledPreamble::Build( PreambleInvocationIn, MainFileBuffer.get(), Bounds, *Diagnostics, VFS, - PCHContainerOps, /*StoreInMemory=*/false, Callbacks); + PCHContainerOps, PreambleStoragePath, /*StoreInMemory=*/false, + Callbacks); PreambleInvocationIn.getFrontendOpts().SkipFunctionBodies = PreviousSkipFunctionBodies; @@ -1741,12 +1742,13 @@ const char **ArgBegin, const char **ArgEnd, std::shared_ptr PCHContainerOps, IntrusiveRefCntPtr Diags, StringRef ResourceFilesPath, - bool OnlyLocalDecls, CaptureDiagsKind CaptureDiagnostics, - ArrayRef RemappedFiles, bool RemappedFilesKeepOriginalName, - unsigned PrecompilePreambleAfterNParses, TranslationUnitKind TUKind, - bool CacheCodeCompletionResults, bool IncludeBriefCommentsInCodeCompletion, - bool AllowPCHWithCompilerErrors, SkipFunctionBodiesScope SkipFunctionBodies, - bool SingleFileParse, bool UserFilesAreVolatile, bool ForSerialization, + StringRef PreambleStoragePath, bool OnlyLocalDecls, + CaptureDiagsKind CaptureDiagnostics, ArrayRef RemappedFiles, + bool RemappedFilesKeepOriginalName, unsigned PrecompilePreambleAfterNParses, + TranslationUnitKind TUKind, bool CacheCodeCompletionResults, + bool IncludeBriefCommentsInCodeCompletion, bool AllowPCHWithCompilerErrors, + SkipFunctionBodiesScope SkipFunctionBodies, bool SingleFileParse, + bool UserFilesAreVolatile, bool ForSerialization, bool RetainExcludedConditionalBlocks, std::optional ModuleFormat, std::unique_ptr *ErrAST, IntrusiveRefCntPtr VFS) { @@ -1801,6 +1803,7 @@ VFS = llvm::vfs::getRealFileSystem(); VFS = createVFSFromCompilerInvocation(*CI, *Diags, VFS); AST->FileMgr = new FileManager(AST->FileSystemOpts, VFS); + AST->PreambleStoragePath = PreambleStoragePath; AST->ModuleCache = new InMemoryModuleCache; AST->OnlyLocalDecls = OnlyLocalDecls; AST->CaptureDiagnostics = CaptureDiagnostics; diff --git a/clang/lib/Frontend/PrecompiledPreamble.cpp b/clang/lib/Frontend/PrecompiledPreamble.cpp --- a/clang/lib/Frontend/PrecompiledPreamble.cpp +++ b/clang/lib/Frontend/PrecompiledPreamble.cpp @@ -197,20 +197,32 @@ class TempPCHFile { public: // A main method used to construct TempPCHFile. - static std::unique_ptr create() { + static std::unique_ptr create(StringRef StoragePath) { // FIXME: This is a hack so that we can override the preamble file during // crash-recovery testing, which is the only case where the preamble files // are not necessarily cleaned up. if (const char *TmpFile = ::getenv("CINDEXTEST_PREAMBLE_FILE")) return std::unique_ptr(new TempPCHFile(TmpFile)); - llvm::SmallString<64> File; - // Using a version of createTemporaryFile with a file descriptor guarantees + llvm::SmallString<128> File; + // Using the versions of createTemporaryFile() and + // createUniqueFile() with a file descriptor guarantees // that we would never get a race condition in a multi-threaded setting // (i.e., multiple threads getting the same temporary path). int FD; - if (auto EC = - llvm::sys::fs::createTemporaryFile("preamble", "pch", FD, File)) + std::error_code EC; + if (StoragePath.empty()) + EC = llvm::sys::fs::createTemporaryFile("preamble", "pch", FD, File); + else { + llvm::SmallString<128> TempPath = StoragePath; + // Use the same filename model as fs::createTemporaryFile(). + llvm::sys::path::append(TempPath, "preamble-%%%%%%.pch"); + namespace fs = llvm::sys::fs; + // Use the same owner-only file permissions as fs::createTemporaryFile(). + EC = fs::createUniqueFile(TempPath, FD, File, fs::OF_None, + fs::owner_read | fs::owner_write); + } + if (EC) return nullptr; // We only needed to make sure the file exists, close the file right away. llvm::sys::Process::SafelyCloseFileDescriptor(FD); @@ -402,8 +414,8 @@ const llvm::MemoryBuffer *MainFileBuffer, PreambleBounds Bounds, DiagnosticsEngine &Diagnostics, IntrusiveRefCntPtr VFS, - std::shared_ptr PCHContainerOps, bool StoreInMemory, - PreambleCallbacks &Callbacks) { + std::shared_ptr PCHContainerOps, + StringRef StoragePath, bool StoreInMemory, PreambleCallbacks &Callbacks) { assert(VFS && "VFS is null"); auto PreambleInvocation = std::make_shared(Invocation); @@ -418,7 +430,8 @@ } else { // Create a temporary file for the precompiled preamble. In rare // circumstances, this can fail. - std::unique_ptr PreamblePCHFile = TempPCHFile::create(); + std::unique_ptr PreamblePCHFile = + TempPCHFile::create(StoragePath); if (!PreamblePCHFile) return BuildPreambleError::CouldntCreateTempFile; Storage = PCHStorage::file(std::move(PreamblePCHFile)); diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c --- a/clang/tools/c-index-test/c-index-test.c +++ b/clang/tools/c-index-test/c-index-test.c @@ -67,6 +67,27 @@ extern char *dirname(char *); #endif +CXIndex createIndex(int ExcludeDeclarationsFromPCH, int DisplayDiagnostics) { + CXIndex Idx; + + CXIndexOptions Opts; + Opts.Size = sizeof(CXIndexOptions); + Opts.GlobalOptions = clang_getDefaultGlobalOptions(); + Opts.ExcludeDeclarationsFromPCH = ExcludeDeclarationsFromPCH; + Opts.DisplayDiagnostics = DisplayDiagnostics; + Opts.PreambleStoragePath = NULL; + Opts.InvocationEmissionPath = getenv("CINDEXTEST_INVOCATION_EMISSION_PATH"); + + Idx = clang_createIndexWithOptions(&Opts); + if (!Idx) { + fprintf(stderr, + "clang_createIndexWithOptions() failed. " + "CINDEX_VERSION_MINOR = %d, sizeof(CXIndexOptions) = %u\n", + CINDEX_VERSION_MINOR, (unsigned)Opts.Size); + } + return Idx; +} + /** Return the default parsing options. */ static unsigned getDefaultParsingOptions(void) { unsigned options = CXTranslationUnit_DetailedPreprocessingRecord; @@ -2046,18 +2067,16 @@ int result; unsigned Repeats = 0; unsigned I; - const char *InvocationPath; - Idx = clang_createIndex(/* excludeDeclsFromPCH */ - (!strcmp(filter, "local") || - !strcmp(filter, "local-display") || - !strcmp(filter, "local-pretty")) - ? 1 - : 0, - /* displayDiagnostics=*/1); - InvocationPath = getenv("CINDEXTEST_INVOCATION_EMISSION_PATH"); - if (InvocationPath) - clang_CXIndex_setInvocationEmissionPathOption(Idx, InvocationPath); + Idx = createIndex(/* excludeDeclsFromPCH */ + (!strcmp(filter, "local") || + !strcmp(filter, "local-display") || + !strcmp(filter, "local-pretty")) + ? 1 + : 0, + /* displayDiagnostics=*/1); + if (!Idx) + return -1; if ((CommentSchemaFile = parse_comments_schema(argc, argv))) { argc--; @@ -2701,7 +2720,6 @@ CXTranslationUnit TU; unsigned I, Repeats = 1; unsigned completionOptions = clang_defaultCodeCompleteOptions(); - const char *InvocationPath; if (getenv("CINDEXTEST_CODE_COMPLETE_PATTERNS")) completionOptions |= CXCodeComplete_IncludeCodePatterns; @@ -2724,10 +2742,9 @@ if (parse_remapped_files(argc, argv, 2, &unsaved_files, &num_unsaved_files)) return -1; - CIdx = clang_createIndex(0, 0); - InvocationPath = getenv("CINDEXTEST_INVOCATION_EMISSION_PATH"); - if (InvocationPath) - clang_CXIndex_setInvocationEmissionPathOption(CIdx, InvocationPath); + CIdx = createIndex(0, 0); + if (!CIdx) + return -1; if (getenv("CINDEXTEST_EDITING")) Repeats = 5; @@ -4816,17 +4833,14 @@ int num_unsaved_files = 0; enum CXErrorCode Err; int result = 0; - const char *InvocationPath; CXString SGF; const char *usr; usr = input + strlen("-single-symbol-sgf-for="); - Idx = clang_createIndex(/* excludeDeclsFromPCH */ 1, - /* displayDiagnostics=*/0); - InvocationPath = getenv("CINDEXTEST_INVOCATION_EMISSION_PATH"); - if (InvocationPath) - clang_CXIndex_setInvocationEmissionPathOption(Idx, InvocationPath); + Idx = createIndex(/* excludeDeclsFromPCH */ 1, /* displayDiagnostics=*/0); + if (!Idx) + return -1; if (parse_remapped_files(argc, argv, 0, &unsaved_files, &num_unsaved_files)) { result = -1; diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -3647,8 +3647,9 @@ static llvm::ManagedStatic RegisterFatalErrorHandlerOnce; -CXIndex clang_createIndex(int excludeDeclarationsFromPCH, - int displayDiagnostics) { +static CIndexer *clang_createIndex_Impl(int excludeDeclarationsFromPCH, + int displayDiagnostics, + unsigned globalOptions) { // We use crash recovery to make some of our APIs more reliable, implicitly // enable it. if (!getenv("LIBCLANG_DISABLE_CRASH_RECOVERY")) @@ -3671,22 +3672,42 @@ CIdxr->setOnlyLocalDecls(); if (displayDiagnostics) CIdxr->setDisplayDiagnostics(); - - if (getenv("LIBCLANG_BGPRIO_INDEX")) - CIdxr->setCXGlobalOptFlags(CIdxr->getCXGlobalOptFlags() | - CXGlobalOpt_ThreadBackgroundPriorityForIndexing); - if (getenv("LIBCLANG_BGPRIO_EDIT")) - CIdxr->setCXGlobalOptFlags(CIdxr->getCXGlobalOptFlags() | - CXGlobalOpt_ThreadBackgroundPriorityForEditing); + CIdxr->setCXGlobalOptFlags(globalOptions); return CIdxr; } +CXIndex clang_createIndex(int excludeDeclarationsFromPCH, + int displayDiagnostics) { + return clang_createIndex_Impl(excludeDeclarationsFromPCH, displayDiagnostics, + clang_getDefaultGlobalOptions()); +} + void clang_disposeIndex(CXIndex CIdx) { if (CIdx) delete static_cast(CIdx); } +unsigned clang_getDefaultGlobalOptions() { + unsigned GlobalOptions = CXGlobalOpt_None; + if (getenv("LIBCLANG_BGPRIO_INDEX")) + GlobalOptions |= CXGlobalOpt_ThreadBackgroundPriorityForIndexing; + if (getenv("LIBCLANG_BGPRIO_EDIT")) + GlobalOptions |= CXGlobalOpt_ThreadBackgroundPriorityForEditing; + return GlobalOptions; +} + +CXIndex clang_createIndexWithOptions(const CXIndexOptions *options) { + if (options->Size != sizeof(CXIndexOptions)) + return NULL; + CIndexer *CIdxr = clang_createIndex_Impl(options->ExcludeDeclarationsFromPCH, + options->DisplayDiagnostics, + options->GlobalOptions); + CIdxr->setPreambleStoragePath(options->PreambleStoragePath); + CIdxr->setInvocationEmissionPath(options->InvocationEmissionPath); + return CIdxr; +} + void clang_CXIndex_setGlobalOptions(CXIndex CIdx, unsigned options) { if (CIdx) static_cast(CIdx)->setCXGlobalOptFlags(options); @@ -3896,8 +3917,8 @@ std::unique_ptr Unit(ASTUnit::LoadFromCommandLine( Args->data(), Args->data() + Args->size(), CXXIdx->getPCHContainerOperations(), Diags, - CXXIdx->getClangResourcesPath(), CXXIdx->getOnlyLocalDecls(), - CaptureDiagnostics, *RemappedFiles.get(), + CXXIdx->getClangResourcesPath(), CXXIdx->getPreambleStoragePath(), + CXXIdx->getOnlyLocalDecls(), CaptureDiagnostics, *RemappedFiles.get(), /*RemappedFilesKeepOriginalName=*/true, PrecompilePreambleAfterNParses, TUKind, CacheCodeCompletionResults, IncludeBriefCommentsInCodeCompletion, /*AllowPCHWithCompilerErrors=*/true, SkipFunctionBodies, SingleFileParse, diff --git a/clang/tools/libclang/CIndexer.h b/clang/tools/libclang/CIndexer.h --- a/clang/tools/libclang/CIndexer.h +++ b/clang/tools/libclang/CIndexer.h @@ -41,6 +41,7 @@ std::string ToolchainPath; + std::string PreambleStoragePath; std::string InvocationEmissionPath; public: @@ -77,6 +78,12 @@ StringRef getClangToolchainPath(); + void setPreambleStoragePath(StringRef Str) { + PreambleStoragePath = Str.str(); + } + + StringRef getPreambleStoragePath() const { return PreambleStoragePath; } + void setInvocationEmissionPath(StringRef Str) { InvocationEmissionPath = std::string(Str); } diff --git a/clang/tools/libclang/libclang.map b/clang/tools/libclang/libclang.map --- a/clang/tools/libclang/libclang.map +++ b/clang/tools/libclang/libclang.map @@ -421,6 +421,8 @@ LLVM_17 { global: clang_CXXMethod_isExplicit; + clang_getDefaultGlobalOptions; + clang_createIndexWithOptions; }; # Example of how to add a new symbol version entry. If you do add a new symbol diff --git a/clang/unittests/Frontend/ASTUnitTest.cpp b/clang/unittests/Frontend/ASTUnitTest.cpp --- a/clang/unittests/Frontend/ASTUnitTest.cpp +++ b/clang/unittests/Frontend/ASTUnitTest.cpp @@ -167,7 +167,7 @@ std::unique_ptr ErrUnit; ASTUnit *AST = ASTUnit::LoadFromCommandLine( - &Args[0], &Args[4], PCHContainerOps, Diags, "", false, + &Args[0], &Args[4], PCHContainerOps, Diags, "", "", false, CaptureDiagsKind::All, std::nullopt, true, 0, TU_Complete, false, false, false, SkipFunctionBodiesScope::None, false, true, false, false, std::nullopt, &ErrUnit, nullptr); diff --git a/clang/unittests/libclang/LibclangTest.cpp b/clang/unittests/libclang/LibclangTest.cpp --- a/clang/unittests/libclang/LibclangTest.cpp +++ b/clang/unittests/libclang/LibclangTest.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "TestUtils.h" #include "clang-c/Index.h" #include "clang-c/Rewrite.h" #include "llvm/ADT/StringRef.h" @@ -14,7 +15,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" -#include "TestUtils.h" +#include #include #include #include @@ -355,6 +356,115 @@ clang_ModuleMapDescriptor_dispose(MMD); } +class LibclangPreambleStorageTest : public LibclangParseTest { + std::string Main = "main.cpp"; + +protected: + std::string PreambleDir; + void InitializePreambleDir() { + llvm::SmallString<128> PathBuffer(TestDir); + llvm::sys::path::append(PathBuffer, "preambles"); + namespace fs = llvm::sys::fs; + ASSERT_FALSE(fs::create_directory(PathBuffer, false, fs::perms::owner_all)); + + PreambleDir = static_cast(PathBuffer); + FilesAndDirsToRemove.insert(PreambleDir); + } + +public: + void CountPreamblesInPreambleDir(int PreambleCount) { + // For some reason, the preamble is not created without '\n' before `int`. + WriteFile(Main, "\nint main() {}"); + + TUFlags |= CXTranslationUnit_CreatePreambleOnFirstParse; + ClangTU = clang_parseTranslationUnit(Index, Main.c_str(), nullptr, 0, + nullptr, 0, TUFlags); + + int FileCount = 0; + + namespace fs = llvm::sys::fs; + std::error_code EC; + for (fs::directory_iterator File(PreambleDir, EC), FileEnd; + File != FileEnd && !EC; File.increment(EC)) { + ++FileCount; + + EXPECT_EQ(File->type(), fs::file_type::regular_file); + + const auto Filename = llvm::sys::path::filename(File->path()); + EXPECT_EQ(Filename.size(), std::strlen("preamble-%%%%%%.pch")); + EXPECT_TRUE(Filename.startswith("preamble-")); + EXPECT_TRUE(Filename.endswith(".pch")); + + const auto Status = File->status(); + ASSERT_TRUE(Status); + if (false) { + // The permissions assertion below fails, because the .pch.tmp file is + // created with default permissions and replaces the .pch file along + // with its permissions. Therefore the permissions set in + // TempPCHFile::create() don't matter in the end. + EXPECT_EQ(Status->permissions(), fs::owner_read | fs::owner_write); + } + } + + EXPECT_EQ(FileCount, PreambleCount); + } +}; + +class LibclangNotOverriddenPreambleStoragePathTest + : public LibclangPreambleStorageTest { +protected: + void CreateIndex() override { + InitializePreambleDir(); + LibclangPreambleStorageTest::CreateIndex(); + } +}; + +class LibclangSetPreambleStoragePathTest : public LibclangPreambleStorageTest { + virtual const char *PreambleStoragePath() = 0; + +protected: + void CreateIndex() override { + InitializePreambleDir(); + + CXIndexOptions Opts; + Opts.Size = sizeof(CXIndexOptions); + Opts.GlobalOptions = clang_getDefaultGlobalOptions(); + Opts.ExcludeDeclarationsFromPCH = false; + Opts.DisplayDiagnostics = false; + Opts.PreambleStoragePath = PreambleStoragePath(); + Opts.InvocationEmissionPath = nullptr; + + Index = clang_createIndexWithOptions(&Opts); + ASSERT_TRUE(Index); + } +}; + +class LibclangNullPreambleStoragePathTest + : public LibclangSetPreambleStoragePathTest { + const char *PreambleStoragePath() override { return nullptr; } +}; +class LibclangEmptyPreambleStoragePathTest + : public LibclangSetPreambleStoragePathTest { + const char *PreambleStoragePath() override { return ""; } +}; +class LibclangPreambleDirPreambleStoragePathTest + : public LibclangSetPreambleStoragePathTest { + const char *PreambleStoragePath() override { return PreambleDir.c_str(); } +}; + +TEST_F(LibclangNotOverriddenPreambleStoragePathTest, CountPreambles) { + CountPreamblesInPreambleDir(0); +} +TEST_F(LibclangNullPreambleStoragePathTest, CountPreambles) { + CountPreamblesInPreambleDir(0); +} +TEST_F(LibclangEmptyPreambleStoragePathTest, CountPreambles) { + CountPreamblesInPreambleDir(0); +} +TEST_F(LibclangPreambleDirPreambleStoragePathTest, CountPreambles) { + CountPreamblesInPreambleDir(1); +} + TEST_F(LibclangParseTest, AllSkippedRanges) { std::string Header = "header.h", Main = "main.cpp"; WriteFile(Header, diff --git a/clang/unittests/libclang/TestUtils.h b/clang/unittests/libclang/TestUtils.h --- a/clang/unittests/libclang/TestUtils.h +++ b/clang/unittests/libclang/TestUtils.h @@ -22,11 +22,11 @@ #include class LibclangParseTest : public ::testing::Test { - // std::greater<> to remove files before their parent dirs in TearDown(). - std::set> Files; typedef std::unique_ptr fixed_addr_string; std::map UnsavedFileContents; public: + // std::greater<> to remove files before their parent dirs in TearDown(). + std::set> FilesAndDirsToRemove; std::string TestDir; bool RemoveTestDirRecursivelyDuringTeardown = false; CXIndex Index; @@ -40,7 +40,7 @@ TestDir = std::string(Dir.str()); TUFlags = CXTranslationUnit_DetailedPreprocessingRecord | clang_defaultEditingTranslationUnitOptions(); - Index = clang_createIndex(0, 0); + CreateIndex(); ClangTU = nullptr; } void TearDown() override { @@ -48,7 +48,7 @@ clang_disposeIndex(Index); namespace fs = llvm::sys::fs; - for (const std::string &Path : Files) + for (const std::string &Path : FilesAndDirsToRemove) EXPECT_FALSE(fs::remove(Path, /*IgnoreNonExisting=*/false)); if (RemoveTestDirRecursivelyDuringTeardown) EXPECT_FALSE(fs::remove_directories(TestDir, /*IgnoreErrors=*/false)); @@ -63,7 +63,7 @@ FileI != FileEnd; ++FileI) { ASSERT_NE(*FileI, "."); path::append(Path, *FileI); - Files.emplace(Path.str()); + FilesAndDirsToRemove.emplace(Path.str()); } Filename = std::string(Path.str()); } @@ -101,6 +101,9 @@ return string; }; +protected: + virtual void CreateIndex() { Index = clang_createIndex(0, 0); } + private: template static CXChildVisitResult TraverseStateless(CXCursor cx, CXCursor parent,