diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp index aac449bfbc406..30ddfb2e84cf9 100644 --- a/clang/lib/Frontend/ASTUnit.cpp +++ b/clang/lib/Frontend/ASTUnit.cpp @@ -1145,6 +1145,7 @@ bool ASTUnit::Parse(std::shared_ptr PCHContainerOps, // Create the compiler instance to use for building the AST. std::unique_ptr Clang( new CompilerInstance(std::move(PCHContainerOps))); + Clang->setInvocation(CCInvocation); // Clean up on error, disengage it if the function returns successfully. auto CleanOnError = llvm::make_scope_exit([&]() { @@ -1171,7 +1172,6 @@ bool ASTUnit::Parse(std::shared_ptr PCHContainerOps, llvm::CrashRecoveryContextCleanupRegistrar CICleanup(Clang.get()); - Clang->setInvocation(CCInvocation); OriginalSourceFile = std::string(Clang->getFrontendOpts().Inputs[0].getFile()); @@ -1754,6 +1754,12 @@ ASTUnit *ASTUnit::LoadFromCommandLine( IntrusiveRefCntPtr VFS) { assert(Diags.get() && "no DiagnosticsEngine was provided"); + // If no VFS was provided, create one that tracks the physical file system. + // If '-working-directory' was passed as an argument, 'createInvocation' will + // set this as the current working directory of the VFS. + if (!VFS) + VFS = llvm::vfs::createPhysicalFileSystem(); + SmallVector StoredDiagnostics; std::shared_ptr CI; @@ -1799,8 +1805,6 @@ ASTUnit *ASTUnit::LoadFromCommandLine( ConfigureDiags(Diags, *AST, CaptureDiagnostics); AST->Diagnostics = Diags; AST->FileSystemOpts = CI->getFileSystemOpts(); - if (!VFS) - VFS = llvm::vfs::getRealFileSystem(); VFS = createVFSFromCompilerInvocation(*CI, *Diags, VFS); AST->FileMgr = new FileManager(AST->FileSystemOpts, VFS); AST->StorePreamblesInMemory = StorePreamblesInMemory; diff --git a/clang/unittests/Frontend/ASTUnitTest.cpp b/clang/unittests/Frontend/ASTUnitTest.cpp index bb3466575d8a7..852cfc7118b23 100644 --- a/clang/unittests/Frontend/ASTUnitTest.cpp +++ b/clang/unittests/Frontend/ASTUnitTest.cpp @@ -179,4 +179,36 @@ TEST_F(ASTUnitTest, LoadFromCommandLineEarlyError) { ASSERT_NE(ErrUnit->stored_diag_size(), 0U); } +TEST_F(ASTUnitTest, LoadFromCommandLineWorkingDirectory) { + EXPECT_FALSE( + llvm::sys::fs::createTemporaryFile("bar", "c", FD, InputFileName)); + auto Input = std::make_unique(InputFileName, FD); + Input->os() << ""; + + SmallString<128> WorkingDir; + ASSERT_FALSE(sys::fs::createUniqueDirectory("foo", WorkingDir)); + const char *Args[] = {"clang", "-working-directory", WorkingDir.c_str(), + InputFileName.c_str()}; + + auto Diags = CompilerInstance::createDiagnostics(new DiagnosticOptions()); + auto PCHContainerOps = std::make_shared(); + std::unique_ptr ErrUnit; + + auto *AST = ASTUnit::LoadFromCommandLine( + &Args[0], &Args[4], PCHContainerOps, Diags, "", false, "", false, + CaptureDiagsKind::All, std::nullopt, true, 0, TU_Complete, false, false, + false, SkipFunctionBodiesScope::None, false, true, false, false, + std::nullopt, &ErrUnit, nullptr); + + ASSERT_NE(AST, nullptr); + ASSERT_FALSE(Diags->hasErrorOccurred()); + + // Make sure '-working-directory' sets both the FileSystemOpts and underlying + // VFS working directory. + const auto &FM = AST->getFileManager(); + const auto &VFS = FM.getVirtualFileSystem(); + ASSERT_EQ(*VFS.getCurrentWorkingDirectory(), WorkingDir.str()); + ASSERT_EQ(FM.getFileSystemOpts().WorkingDir, WorkingDir.str()); +} + } // anonymous namespace diff --git a/clang/unittests/Frontend/CMakeLists.txt b/clang/unittests/Frontend/CMakeLists.txt index e11790d05abb9..0f05813338f2a 100644 --- a/clang/unittests/Frontend/CMakeLists.txt +++ b/clang/unittests/Frontend/CMakeLists.txt @@ -12,6 +12,7 @@ add_clang_unittest(FrontendTests CodeGenActionTest.cpp ParsedSourceLocationTest.cpp PCHPreambleTest.cpp + ReparseWorkingDirTest.cpp OutputStreamTest.cpp TextDiagnosticTest.cpp UtilsTest.cpp diff --git a/clang/unittests/Frontend/ReparseWorkingDirTest.cpp b/clang/unittests/Frontend/ReparseWorkingDirTest.cpp new file mode 100644 index 0000000000000..ca7ce23dd64b2 --- /dev/null +++ b/clang/unittests/Frontend/ReparseWorkingDirTest.cpp @@ -0,0 +1,118 @@ +//====-- unittests/Frontend/ReparseWorkingDirTest.cpp - FrontendAction tests =// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/FrontendOptions.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace clang; + +namespace { +class ReparseWorkingDirTest : public ::testing::Test { + IntrusiveRefCntPtr VFS; + std::shared_ptr PCHContainerOpts; + +public: + void SetUp() override { VFS = new vfs::InMemoryFileSystem(); } + void TearDown() override {} + + void setWorkingDirectory(StringRef Path) { + VFS->setCurrentWorkingDirectory(Path); + } + + void AddFile(const std::string &Filename, const std::string &Contents) { + ::time_t now; + ::time(&now); + VFS->addFile(Filename, now, + MemoryBuffer::getMemBufferCopy(Contents, Filename)); + } + + std::unique_ptr ParseAST(StringRef EntryFile) { + PCHContainerOpts = std::make_shared(); + auto CI = std::make_shared(); + CI->getFrontendOpts().Inputs.push_back(FrontendInputFile( + EntryFile, FrontendOptions::getInputKindForExtension( + llvm::sys::path::extension(EntryFile).substr(1)))); + + CI->getHeaderSearchOpts().AddPath("headers", + frontend::IncludeDirGroup::Quoted, + /*isFramework*/ false, + /*IgnoreSysRoot*/ false); + + CI->getFileSystemOpts().WorkingDir = *VFS->getCurrentWorkingDirectory(); + CI->getTargetOpts().Triple = "i386-unknown-linux-gnu"; + + IntrusiveRefCntPtr Diags( + CompilerInstance::createDiagnostics(new DiagnosticOptions, + new DiagnosticConsumer)); + + FileManager *FileMgr = new FileManager(CI->getFileSystemOpts(), VFS); + + std::unique_ptr AST = ASTUnit::LoadFromCompilerInvocation( + CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None, + /*PrecompilePreambleAfterNParses=*/1); + return AST; + } + + bool ReparseAST(const std::unique_ptr &AST) { + bool reparseFailed = + AST->Reparse(PCHContainerOpts, /*RemappedFiles*/ {}, VFS); + return !reparseFailed; + } +}; + +TEST_F(ReparseWorkingDirTest, ReparseWorkingDir) { + // Setup the working directory path. + SmallString<16> WorkingDir; +#ifdef _WIN32 + WorkingDir = "C:\\"; +#else + WorkingDir = "/"; +#endif + llvm::sys::path::append(WorkingDir, "root"); + setWorkingDirectory(WorkingDir); + + SmallString<32> Header; + llvm::sys::path::append(Header, WorkingDir, "headers", "header.h"); + + SmallString<32> MainName; + llvm::sys::path::append(MainName, WorkingDir, "main.cpp"); + + AddFile(MainName.str().str(), R"cpp( +#include "header.h" +int main() { return foo(); } +)cpp"); + AddFile(Header.str().str(), R"h( +static int foo() { return 0; } +)h"); + + // Parse the main file, ensuring we can include the header. + std::unique_ptr AST(ParseAST(MainName.str())); + ASSERT_TRUE(AST.get()); + ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + // Reparse and check that the working directory was preserved. + ASSERT_TRUE(ReparseAST(AST)); + + const auto &FM = AST->getFileManager(); + const auto &FS = FM.getVirtualFileSystem(); + ASSERT_EQ(FM.getFileSystemOpts().WorkingDir, WorkingDir); + ASSERT_EQ(*FS.getCurrentWorkingDirectory(), WorkingDir); +} + +} // end anonymous namespace