Index: clang-tidy/ClangTidy.cpp =================================================================== --- clang-tidy/ClangTidy.cpp +++ clang-tidy/ClangTidy.cpp @@ -107,6 +107,10 @@ DiagPrinter->BeginSourceFile(LangOpts); } + SourceManager& getSourceManager() { + return SourceMgr; + } + void reportDiagnostic(const ClangTidyError &Error) { const ClangTidyMessage &Message = Error.Message; SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset); @@ -124,7 +128,10 @@ auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]")) << Message.Message << Name; for (const tooling::Replacement &Fix : Error.Fix) { - SourceLocation FixLoc = getLocation(Fix.getFilePath(), Fix.getOffset()); + SmallString<128> FixAbsoluteFilePath = Fix.getFilePath(); + Files.makeAbsolutePath(FixAbsoluteFilePath); + SourceLocation FixLoc = + getLocation(FixAbsoluteFilePath, Fix.getOffset()); SourceLocation FixEndLoc = FixLoc.getLocWithOffset(Fix.getLength()); Diag << FixItHint::CreateReplacement(SourceRange(FixLoc, FixEndLoc), Fix.getReplacementText()); @@ -232,6 +239,13 @@ Context.setCurrentFile(File); Context.setASTContext(&Compiler.getASTContext()); + auto WorkingDir = Compiler.getSourceManager() + .getFileManager() + .getVirtualFileSystem() + ->getCurrentWorkingDirectory(); + if (WorkingDir) + Context.setCurrentBuildDirectory(WorkingDir.get()); + std::vector> Checks; CheckFactories->createChecks(&Context, Checks); @@ -393,7 +407,8 @@ runClangTidy(std::unique_ptr OptionsProvider, const tooling::CompilationDatabase &Compilations, ArrayRef InputFiles, - std::vector *Errors, ProfileData *Profile) { + std::vector *Errors, + ProfileData *Profile) { ClangTool Tool(Compilations, InputFiles); clang::tidy::ClangTidyContext Context(std::move(OptionsProvider)); ArgumentsAdjuster PerFileExtraArgumentsInserter = [&Context]( @@ -446,8 +461,24 @@ void handleErrors(const std::vector &Errors, bool Fix, unsigned &WarningsAsErrorsCount) { ErrorReporter Reporter(Fix); - for (const ClangTidyError &Error : Errors) + vfs::FileSystem &FileSystem = + *Reporter.getSourceManager().getFileManager().getVirtualFileSystem(); + auto InitialWorkingDir = FileSystem.getCurrentWorkingDirectory(); + if (!InitialWorkingDir) + llvm::report_fatal_error("Cannot get current working path."); + + for (const ClangTidyError &Error : Errors) { + if (!Error.BuildDirectory.empty()) { + // By default, the working directory of file system is the current + // clang-tidy running directory. + // + // Change the directory to the one used during the analysis. + FileSystem.setCurrentWorkingDirectory(Error.BuildDirectory); + } Reporter.reportDiagnostic(Error); + // Return to the initial directory to correctly resolve next Error. + FileSystem.setCurrentWorkingDirectory(InitialWorkingDir.get()); + } Reporter.Finish(); WarningsAsErrorsCount += Reporter.getWarningsAsErrorsCount(); } Index: clang-tidy/ClangTidyDiagnosticConsumer.h =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.h +++ clang-tidy/ClangTidyDiagnosticConsumer.h @@ -57,13 +57,23 @@ Error = DiagnosticsEngine::Error }; - ClangTidyError(StringRef CheckName, Level DiagLevel, bool IsWarningAsError); + ClangTidyError(StringRef CheckName, Level DiagLevel, bool IsWarningAsError, + StringRef BuildDirectory); std::string CheckName; ClangTidyMessage Message; tooling::Replacements Fix; SmallVector Notes; + // A build directory of the diagnostic source file. + // + // It's an absolute path which is `directory` field of the source file in + // compilation database. If users don't specify the compilation database + // directory, it is the current directory where clang-tidy runs. + // + // Note: it is empty in unittest. + std::string BuildDirectory; + Level DiagLevel; bool IsWarningAsError; }; @@ -198,6 +208,16 @@ void setCheckProfileData(ProfileData *Profile); ProfileData *getCheckProfileData() const { return Profile; } + /// \brief Should be called when starting to process new translation unit. + void setCurrentBuildDirectory(StringRef BuildDirectory) { + CurrentBuildDirectory = BuildDirectory; + } + + /// \brief Returns all build directories. + const std::string &getCurrentBuildDirectory() { + return CurrentBuildDirectory; + } + private: // Calls setDiagnosticsEngine() and storeError(). friend class ClangTidyDiagnosticConsumer; @@ -222,6 +242,8 @@ ClangTidyStats Stats; + std::string CurrentBuildDirectory; + llvm::DenseMap CheckNamesByDiagnosticID; ProfileData *Profile; Index: clang-tidy/ClangTidyDiagnosticConsumer.cpp =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -116,8 +116,9 @@ ClangTidyError::ClangTidyError(StringRef CheckName, ClangTidyError::Level DiagLevel, - bool IsWarningAsError) - : CheckName(CheckName), DiagLevel(DiagLevel), + bool IsWarningAsError, + StringRef BuildDirectory) + : CheckName(CheckName), BuildDirectory(BuildDirectory), DiagLevel(DiagLevel), IsWarningAsError(IsWarningAsError) {} // Returns true if GlobList starts with the negative indicator ('-'), removes it @@ -335,7 +336,8 @@ bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && Context.getWarningAsErrorFilter().contains(CheckName); - Errors.push_back(ClangTidyError(CheckName, Level, IsWarningAsError)); + Errors.push_back(ClangTidyError(CheckName, Level, IsWarningAsError, + Context.getCurrentBuildDirectory())); } // FIXME: Provide correct LangOptions for each file. Index: test/clang-tidy/Inputs/compilation-database/header.h =================================================================== --- /dev/null +++ test/clang-tidy/Inputs/compilation-database/header.h @@ -0,0 +1,4 @@ +#define NULL 0 + +int *p = NULL; +// CHECK-FIX: int *p = nullptr; Index: test/clang-tidy/Inputs/compilation-database/template.json =================================================================== --- /dev/null +++ test/clang-tidy/Inputs/compilation-database/template.json @@ -0,0 +1,32 @@ +[ +{ + "directory": "test_dir/a", + "command": "clang++ -o test.o test_dir/a/a.cpp", + "file": "test_dir/a/a.cpp" +}, +{ + "directory": "test_dir/a", + "command": "clang++ -o test.o test_dir/a/b.cpp", + "file": "test_dir/a/b.cpp" +}, +{ + "directory": "test_dir/", + "command": "clang++ -o test.o test_dir/b/b.cpp", + "file": "test_dir/b/b.cpp" +}, +{ + "directory": "test_dir/b", + "command": "clang++ -o test.o ../b/c.cpp", + "file": "test_dir/b/c.cpp" +}, +{ + "directory": "test_dir/b", + "command": "clang++ -I../include -o test.o ../b/d.cpp", + "file": "test_dir/b/d.cpp" +}, +{ + "directory": "test_dir/", + "command": "clang++ -o test.o test_dir/b/not-exist.cpp", + "file": "test_dir/b/not-exist.cpp" +} +] Index: test/clang-tidy/clang-tidy-run-with-database.cpp =================================================================== --- /dev/null +++ test/clang-tidy/clang-tidy-run-with-database.cpp @@ -0,0 +1,29 @@ +// REQUIRES: shell +// RUN: mkdir -p %T/compilation-database-test +// RUN: mkdir -p %T/compilation-database-test/include +// RUN: mkdir -p %T/compilation-database-test/a +// RUN: mkdir -p %T/compilation-database-test/b +// RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp +// RUN: sed 's|pointer|AA|g' %t.cpp > %T/compilation-database-test/a/a.cpp +// RUN: sed 's|pointer|AA|g' %s > %T/compilation-database-test/a/a.cpp.msg +// RUN: sed 's|pointer|AB|g' %t.cpp > %T/compilation-database-test/a/b.cpp +// RUN: sed 's|pointer|AB|g' %s > %T/compilation-database-test/a/b.cpp.msg +// RUN: sed 's|pointer|BB|g' %t.cpp > %T/compilation-database-test/b/b.cpp +// RUN: sed 's|pointer|BB|g' %s > %T/compilation-database-test/b/b.cpp.msg +// RUN: sed 's|pointer|BC|g' %t.cpp > %T/compilation-database-test/b/c.cpp +// RUN: sed 's|pointer|BC|g' %s > %T/compilation-database-test/b/c.cpp.msg +// RUN: grep -Ev "// *[A-Z-]+:" %S/Inputs/compilation-database/header.h > %T/compilation-database-test/include/header.h +// RUN: echo '#include "header.h"' > %T/compilation-database-test/b/d.cpp +// RUN: sed 's|test_dir|%T/compilation-database-test|g' %S/Inputs/compilation-database/template.json > %T/compile_commands.json +// RUN: clang-tidy --checks=-*,modernize-use-nullptr -p %T %T/compilation-database-test/b/not-exist -header-filter=.* +// RUN: clang-tidy --checks=-*,modernize-use-nullptr -p %T %T/compilation-database-test/a/a.cpp %T/compilation-database-test/a/b.cpp %T/compilation-database-test/b/b.cpp %T/compilation-database-test/b/c.cpp %T/compilation-database-test/b/d.cpp -header-filter=.* -fix +// RUN: FileCheck -input-file=%T/compilation-database-test/a/a.cpp %T/compilation-database-test/a/a.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/a/b.cpp %T/compilation-database-test/a/b.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/b/b.cpp %T/compilation-database-test/b/b.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/b/c.cpp %T/compilation-database-test/b/c.cpp.msg -check-prefix=CHECK-FIX +// RUN: FileCheck -input-file=%T/compilation-database-test/include/header.h %S/Inputs/compilation-database/header.h -check-prefix=CHECK-FIX +#define NULL 0 + +int *pointer = NULL; +// CHECK-FIX: int *pointer = nullptr; +// CHECK: warning: use nullptr [modernize-use-nullpt]