File indexing completed on 2024-05-19 05:42:00

0001 // ct_lvtclp_cpp_tool.cpp                                                 -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtclp_cpp_tool.h>
0021 
0022 #include <ct_lvtclp_clputil.h>
0023 #include <ct_lvtclp_compilerutil.h>
0024 #include <ct_lvtclp_fileutil.h>
0025 #include <ct_lvtclp_logicaldepscanner.h>
0026 #include <ct_lvtclp_logicalpostprocessutil.h>
0027 #include <ct_lvtclp_physicaldepscanner.h>
0028 #include <ct_lvtclp_toolexecutor.h>
0029 
0030 #include <ct_lvtmdb_objectstore.h>
0031 #include <ct_lvtshr_functional.h>
0032 #include <ct_lvtshr_stringhelpers.h>
0033 
0034 #include <clang/Tooling/CommonOptionsParser.h>
0035 
0036 #include <llvm/Support/GlobPattern.h>
0037 #include <mutex>
0038 #include <optional>
0039 #include <sstream>
0040 #include <string>
0041 #include <unordered_set>
0042 #include <utility>
0043 #include <vector>
0044 
0045 #include <QDebug>
0046 
0047 Q_LOGGING_CATEGORY(LogTool, "log.cpp_tool")
0048 
0049 namespace {
0050 
0051 using Codethink::lvtclp::ClpUtil;
0052 // using Codethink::lvtclp::FileUtil;
0053 using Codethink::lvtclp::LvtCompilationDatabase;
0054 
0055 // =================================
0056 // class PartialCompilationDatabase
0057 // =================================
0058 class LvtCompilationDatabaseImpl : public LvtCompilationDatabase {
0059     // Partial implementation of LvtCompilationDatabase to share code between
0060     // PartialCompilationDatabase and SubsetCompilationDatabase.
0061     // Implements looking up files and packages in the compilation database
0062 
0063   private:
0064     // DATA
0065     std::vector<std::string> d_files;
0066     std::vector<std::filesystem::path> d_paths;
0067     std::unordered_set<std::string> d_components;
0068     std::unordered_set<std::string> d_pkgs;
0069     std::optional<std::filesystem::path> d_prefix;
0070 
0071   protected:
0072     const std::vector<std::string>& files() const
0073     {
0074         return d_files;
0075     }
0076 
0077     const std::vector<std::filesystem::path>& paths() const
0078     {
0079         return d_paths;
0080     }
0081 
0082     void reserve(std::size_t size)
0083     {
0084         d_files.reserve(size);
0085         d_paths.reserve(size);
0086         d_components.reserve(size);
0087         // d_pkgs will need a size much less than size. Don't try to estimate.
0088     }
0089 
0090     void shrinkToFit()
0091     {
0092         d_files.shrink_to_fit();
0093         d_paths.shrink_to_fit();
0094         // d_components: no shrink_to_fit for std::unordered_set
0095         // d_pkgs: no shrink_to_fit for std::unordered_set
0096     }
0097 
0098     void addPath(const std::filesystem::path& path)
0099     // path should be weakly_canonical
0100     {
0101         using Codethink::lvtclp::FileType;
0102 
0103         const auto it = std::find(d_paths.begin(), d_paths.end(), path);
0104         if (it != d_paths.end()) {
0105             return;
0106         }
0107 
0108         // we store multiple copies of the path in different formats so we only
0109         // have to pay for the conversions once
0110         d_paths.push_back(path);
0111         d_files.push_back(path.string());
0112 
0113         std::filesystem::path component(path);
0114         component.replace_extension();
0115         d_components.insert(component.string());
0116 
0117         // these two work because the directory structure is fixed:
0118         // .../groups/grp/grppkg/grppkg_component.cpp etc
0119         // package
0120         d_pkgs.insert(path.parent_path().string());
0121         // package group
0122         d_pkgs.insert(path.parent_path().parent_path().string());
0123     }
0124 
0125     void setPrefix(std::filesystem::path prefix)
0126     {
0127         d_prefix.emplace(std::move(prefix));
0128     }
0129 
0130   public:
0131     bool containsFile(const std::string& path) const override
0132     {
0133         using Codethink::lvtclp::FileType;
0134         const FileType type = Codethink::lvtclp::ClpUtil::categorisePath(path);
0135         if (type != FileType::e_Source && type != FileType::e_Header) {
0136             return false;
0137         }
0138 
0139         assert(d_prefix);
0140         std::filesystem::path filePath = std::filesystem::weakly_canonical(*d_prefix / path);
0141 
0142         // the compilation DB only knows about source files, so look for any
0143         // file which has is the same name, ignoring the extension.
0144         // Lakosian rules say we can't have headers without source (and vice-versa)
0145         filePath.replace_extension();
0146 
0147         return d_components.find(filePath.string()) != d_components.end();
0148     }
0149 
0150     bool containsPackage(const std::string& pkg) const override
0151     {
0152         assert(d_prefix);
0153         std::filesystem::path pkgPath = std::filesystem::weakly_canonical(*d_prefix / pkg);
0154         return d_pkgs.find(pkgPath.string()) != d_pkgs.end();
0155     }
0156 };
0157 
0158 // =================================
0159 // class PartialCompilationDatabase
0160 // =================================
0161 
0162 class PartialCompilationDatabase : public LvtCompilationDatabaseImpl {
0163     // Produce a subset of another compilation database using ignore globs
0164     // Also applies modifications to compiler include directories according to
0165     // lvtclp::CompilerUtil
0166 
0167   private:
0168     // DATA
0169     std::vector<std::string> d_ignoreGlobs;
0170     std::function<void(const std::string&, long)> d_messageCallback;
0171     std::vector<clang::tooling::CompileCommand> d_compileCommands;
0172     std::filesystem::path d_commonParent;
0173     std::vector<llvm::GlobPattern> d_ignorePatterns;
0174 
0175   public:
0176     // CREATORS
0177     explicit PartialCompilationDatabase(std::filesystem::path sourcePath,
0178                                         std::vector<clang::tooling::CompileCommand> compileCommands,
0179                                         std::function<void(const std::string&, long)> messageCallback):
0180         d_messageCallback(std::move(messageCallback)),
0181         d_compileCommands(std::move(compileCommands)),
0182         d_commonParent(std::move(sourcePath))
0183     {
0184     }
0185 
0186     void setIgnoreGlobs(const std::vector<llvm::GlobPattern>& ignoreGlobs)
0187     {
0188         d_ignorePatterns = ignoreGlobs;
0189     }
0190 
0191     // MANIPULATORS
0192     void setup(Codethink::lvtclp::CppTool::UseSystemHeaders useSystemHeaders, bool printToConsole)
0193     {
0194         using Codethink::lvtclp::CompilerUtil;
0195         std::vector<std::string> sysIncludes;
0196 
0197         std::optional<std::string> bundledHeaders = CompilerUtil::findBundledHeaders(printToConsole);
0198         if (!bundledHeaders) {
0199             const bool searchForHeaders = useSystemHeaders == Codethink::lvtclp::CppTool::UseSystemHeaders::e_Yes
0200                 || ((useSystemHeaders == Codethink::lvtclp::CppTool::UseSystemHeaders::e_Query)
0201                     && CompilerUtil::weNeedSystemHeaders());
0202 
0203             if (searchForHeaders) {
0204                 sysIncludes = CompilerUtil::findSystemIncludes();
0205                 for (std::string& include : sysIncludes) {
0206                     std::string arg;
0207                     arg.append("-isystem").append(include);
0208                     include = std::move(arg);
0209                 }
0210             }
0211         } else {
0212             sysIncludes.push_back("-isystem" + *bundledHeaders);
0213         }
0214 
0215         // Command lines that doesn't exist on clang, and could be potentially used
0216         // from compile_commands.json - if we allow them in, we will have problems.
0217         const std::vector<std::string> missingCommandLineOnClang = {"-mno-direct-extern-access"};
0218 
0219         auto removeIgnoredFilesLambda = [this](clang::tooling::CompileCommand& cmd) -> bool {
0220             return ClpUtil::isFileIgnored(cmd.Filename, d_ignorePatterns);
0221         };
0222 
0223         d_compileCommands.erase(
0224             std::remove_if(d_compileCommands.begin(), d_compileCommands.end(), removeIgnoredFilesLambda),
0225 
0226             std::end(d_compileCommands));
0227 
0228         for (auto& cmd : d_compileCommands) {
0229             std::filesystem::path path = std::filesystem::weakly_canonical(cmd.Filename);
0230             addPath(path);
0231             cmd.Filename = path.string();
0232 
0233             // Disable all warnings to defeat any -Werror arguments
0234             // the source is configured to use and to make the output
0235             // easier to read
0236             cmd.CommandLine.emplace_back("-Wno-everything");
0237 
0238             auto new_end = std::remove_if(cmd.CommandLine.begin(),
0239                                           cmd.CommandLine.end(),
0240                                           [&missingCommandLineOnClang](const std::string& command) -> bool {
0241                                               for (const auto& missingCmdLine : missingCommandLineOnClang) {
0242                                                   if (command.find(missingCmdLine) != command.npos) {
0243                                                       return true;
0244                                                   }
0245                                               }
0246                                               return false;
0247                                           });
0248 
0249             cmd.CommandLine.erase(new_end, cmd.CommandLine.end());
0250 
0251             // add system includes
0252             std::copy(sysIncludes.begin(), sysIncludes.end(), std::back_inserter(cmd.CommandLine));
0253         }
0254 
0255         shrinkToFit();
0256         setPrefix(d_commonParent);
0257     }
0258 
0259     // ACCESSORS
0260     const std::filesystem::path& commonParent() const
0261     {
0262         return d_commonParent;
0263     }
0264 
0265     std::vector<clang::tooling::CompileCommand> getCompileCommands(llvm::StringRef FilePath) const override
0266     {
0267         for (auto const& cmd : d_compileCommands) {
0268             if (cmd.Filename == FilePath) {
0269                 return {cmd};
0270             }
0271         }
0272         return {};
0273     }
0274 
0275     std::vector<std::string> getAllFiles() const override
0276     {
0277         return files();
0278     }
0279 
0280     std::vector<clang::tooling::CompileCommand> getAllCompileCommands() const override
0281     {
0282         return d_compileCommands;
0283     }
0284 };
0285 
0286 class SubsetCompilationDatabase : public LvtCompilationDatabaseImpl {
0287     // A subset compilation database using an allow-list of paths
0288   private:
0289     // DATA
0290     const clang::tooling::CompilationDatabase& d_parent;
0291 
0292   public:
0293     // CREATORS
0294     explicit SubsetCompilationDatabase(const clang::tooling::CompilationDatabase& parent,
0295                                        const std::vector<std::string>& paths,
0296                                        const std::filesystem::path& commonParent):
0297         d_parent(parent)
0298     {
0299         reserve(paths.size());
0300 
0301         for (const std::string& path : paths) {
0302             std::filesystem::path fullPath = commonParent / path;
0303             addPath(std::filesystem::weakly_canonical(fullPath));
0304         }
0305 
0306         shrinkToFit();
0307         setPrefix(commonParent);
0308     }
0309 
0310     // ACCESSORS
0311     std::vector<clang::tooling::CompileCommand> getCompileCommands(llvm::StringRef FilePath) const override
0312     {
0313         const std::vector<std::string>& files = SubsetCompilationDatabase::files();
0314         const auto it = std::find(files.begin(), files.end(), FilePath);
0315         if (it != files.end()) {
0316             return d_parent.getCompileCommands(FilePath);
0317         }
0318 
0319         return {};
0320     }
0321 
0322     std::vector<std::string> getAllFiles() const override
0323     {
0324         std::vector<std::string> out;
0325         out.reserve(paths().size()); // estimate
0326 
0327         std::vector<std::string> parentFiles = d_parent.getAllFiles();
0328         for (const std::string& path : files()) {
0329             const auto it = std::find(parentFiles.begin(), parentFiles.end(), path);
0330             if (it != parentFiles.end()) {
0331                 out.push_back(path);
0332             }
0333         }
0334 
0335         return out;
0336     }
0337 
0338     std::vector<clang::tooling::CompileCommand> getAllCompileCommands() const override
0339     {
0340         std::vector<clang::tooling::CompileCommand> out;
0341         out.reserve(paths().size()); // estimate
0342 
0343         for (const std::string& path : files()) {
0344             std::vector<clang::tooling::CompileCommand> pathCmds = d_parent.getCompileCommands(path);
0345             std::move(pathCmds.begin(), pathCmds.end(), std::back_inserter(out));
0346         }
0347 
0348         return out;
0349     }
0350 
0351     int numCompileCommands() const
0352     // using int because qt signals don't like std::size_t and ultimately
0353     // we are passing this to QProgressBar::setMaximum anyway
0354     {
0355         const std::size_t size = getAllCompileCommands().size();
0356 
0357         // we would likely have run out of memory long before we could have
0358         // INT_MAX compile commands
0359         if (size > INT_MAX) {
0360             return INT_MAX;
0361         }
0362         return static_cast<int>(size);
0363     }
0364 };
0365 
0366 } // unnamed namespace
0367 
0368 namespace Codethink::lvtclp {
0369 
0370 struct CppTool::Private {
0371     std::filesystem::path sourcePath;
0372     std::optional<clang::tooling::CompileCommand> compileCommand;
0373     std::function<void(const std::string&, long)> messageCallback;
0374     std::vector<std::filesystem::path> compileCommandsJsons;
0375     std::filesystem::path databasePath;
0376     unsigned numThreads = 1;
0377     std::vector<llvm::GlobPattern> ignoreList;
0378     std::vector<std::filesystem::path> nonLakosianDirs;
0379     std::vector<std::pair<std::string, std::string>> thirdPartyDirs;
0380     bool printToConsole = false;
0381     std::optional<HeaderCallbacks::HeaderLocationCallback_f> headerLocationCallback = std::nullopt;
0382     std::optional<HandleCppCommentsCallback_f> handleCppCommentsCallback = std::nullopt;
0383 
0384     UseSystemHeaders useSystemHeaders = UseSystemHeaders::e_Query;
0385 
0386     // The tool can be used either with a local memory database or with a
0387     // shared one. Only one can be used at a time. The default is to use
0388     // localMemDb. If setMemDb(other) is called, will ignore the local one.
0389     lvtmdb::ObjectStore localMemDb;
0390     std::shared_ptr<lvtmdb::ObjectStore> sharedMemDb = nullptr;
0391 
0392     std::optional<PartialCompilationDatabase> compilationDb;
0393     // full compilation db (compileCommandJson minus ignoreList)
0394 
0395     std::optional<SubsetCompilationDatabase> incrementalCdb;
0396     // partial compilation db containing only the paths which need to be
0397     // re-parsed for an incremental update
0398 
0399     // Normally the ToolExecutor is managed from runPhysical() or runFull()
0400     // but it can be cancelled from another thread. The mutex should be held
0401     // when cancelling the executor or deleting it. The mutex should not be
0402     // held to execute the tool executor because that would prevent the
0403     // cancellation. It is safe to execute without the mutex because the
0404     // toolExecutor is not invalidated from the cancellation thread.
0405     std::mutex executorMutex;
0406     ToolExecutor *toolExecutor = nullptr;
0407     // only non-null when there is an executor running
0408     bool executorCancelled = false;
0409 
0410     bool lastRunMadeChanges = false;
0411 
0412     bool showDatabaseErrors = false;
0413     // Flag that indicates that database errors should be
0414     // reported to the UI.
0415 
0416     [[nodiscard]] lvtmdb::ObjectStore& memDb()
0417     {
0418         return sharedMemDb ? *sharedMemDb : localMemDb;
0419     }
0420 
0421     void setNumThreads(unsigned numThreadsIn)
0422     {
0423         if (numThreadsIn < 1) {
0424             numThreads = 1;
0425         } else {
0426             numThreads = numThreadsIn;
0427         }
0428     }
0429 
0430     void setIgnoreList(const std::vector<std::string>& userIgnoreList)
0431     {
0432         ignoreList.clear();
0433 
0434         // add non-duplicates
0435         std::set<std::string> nonDuplicatedItems(std::begin(userIgnoreList), std::end(userIgnoreList));
0436         for (const auto& ignoreFile : nonDuplicatedItems) {
0437             llvm::Expected<llvm::GlobPattern> pat = llvm::GlobPattern::create(ignoreFile);
0438             if (pat) {
0439                 ignoreList.push_back(pat.get());
0440             }
0441         }
0442 
0443         if (compilationDb) {
0444             compilationDb->setIgnoreGlobs(ignoreList);
0445         }
0446     }
0447 
0448     void setNonLakosianDirs(const std::vector<std::filesystem::path>& nonLakosians)
0449     {
0450         nonLakosianDirs.reserve(nonLakosians.size());
0451 
0452         std::transform(nonLakosians.begin(),
0453                        nonLakosians.end(),
0454                        std::back_inserter(nonLakosianDirs),
0455                        [](const std::filesystem::path& dir) {
0456                            return std::filesystem::weakly_canonical(dir);
0457                        });
0458     }
0459 
0460     Private(CppTool *tool,
0461             std::filesystem::path inSourcePath,
0462             std::vector<std::filesystem::path> inCompileCommandsJsons,
0463             std::filesystem::path inDatabasePath,
0464             unsigned numThreadsIn,
0465             const std::vector<std::string>& ignoreList,
0466             const std::vector<std::filesystem::path>& nonLakosians,
0467             std::vector<std::pair<std::string, std::string>> thirdPartyDirs,
0468             bool inPrintToConsole = true):
0469         sourcePath(std::move(inSourcePath)),
0470         messageCallback([tool](const std::string& msg, long tid) {
0471             tool->messageCallback(msg, tid);
0472         }),
0473         compileCommandsJsons(std::move(inCompileCommandsJsons)),
0474         databasePath(std::move(inDatabasePath)),
0475         thirdPartyDirs(std::move(thirdPartyDirs)),
0476         printToConsole(inPrintToConsole)
0477     {
0478         setNumThreads(numThreadsIn);
0479         setIgnoreList(ignoreList);
0480         setNonLakosianDirs(nonLakosians);
0481     }
0482 
0483     Private(CppTool *tool,
0484             std::filesystem::path inSourcePath,
0485             clang::tooling::CompileCommand compileCommand,
0486             std::filesystem::path inDatabasePath,
0487             const std::vector<std::string>& ignoreList,
0488             const std::vector<std::filesystem::path>& nonLakosians,
0489             std::vector<std::pair<std::string, std::string>> thirdPartyDirs,
0490             bool inPrintToConsole = true):
0491         sourcePath(std::move(inSourcePath)),
0492         compileCommand(std::in_place, compileCommand),
0493         messageCallback([tool](const std::string& msg, long tid) {
0494             tool->messageCallback(msg, tid);
0495         }),
0496         databasePath(std::move(inDatabasePath)),
0497         thirdPartyDirs(std::move(thirdPartyDirs)),
0498         printToConsole(inPrintToConsole)
0499     {
0500         setNumThreads(1);
0501         setIgnoreList(ignoreList);
0502         setNonLakosianDirs(nonLakosians);
0503     }
0504 
0505     Private(CppTool *tool,
0506             std::filesystem::path inSourcePath,
0507             const std::vector<clang::tooling::CompileCommand>& compileCommands,
0508             std::filesystem::path inDatabasePath,
0509             unsigned numThreadsIn,
0510             const std::vector<std::string>& ignoreList,
0511             std::vector<std::filesystem::path> nonLakosianDirs,
0512             std::vector<std::pair<std::string, std::string>> thirdPartyDirs,
0513             bool inPrintToConsole):
0514         sourcePath(std::move(inSourcePath)),
0515         messageCallback([tool](const std::string& msg, long tid) {
0516             tool->messageCallback(msg, tid);
0517         }),
0518         databasePath(std::move(inDatabasePath)),
0519         nonLakosianDirs(std::move(nonLakosianDirs)),
0520         thirdPartyDirs(std::move(thirdPartyDirs)),
0521         printToConsole(inPrintToConsole),
0522         compilationDb(std::in_place, sourcePath, compileCommands, messageCallback)
0523     {
0524         setNumThreads(numThreadsIn);
0525         setIgnoreList(ignoreList);
0526     }
0527 };
0528 
0529 CppTool::CppTool(std::filesystem::path sourcePath,
0530                  const std::vector<std::filesystem::path>& compileCommandsJsons,
0531                  const std::filesystem::path& databasePath,
0532                  unsigned numThreads,
0533                  const std::vector<std::string>& ignoreList,
0534                  const std::vector<std::filesystem::path>& nonLakosianDirs,
0535                  std::vector<std::pair<std::string, std::string>> thirdPartyDirs,
0536                  bool printToConsole):
0537     d(std::make_unique<CppTool::Private>(this,
0538                                          std::move(sourcePath),
0539                                          compileCommandsJsons,
0540                                          databasePath,
0541                                          numThreads,
0542                                          ignoreList,
0543                                          nonLakosianDirs,
0544                                          std::move(thirdPartyDirs),
0545                                          printToConsole))
0546 {
0547 }
0548 
0549 CppTool::CppTool(std::filesystem::path sourcePath,
0550                  const clang::tooling::CompileCommand& compileCommand,
0551                  const std::filesystem::path& databasePath,
0552                  const std::vector<std::string>& ignoreList,
0553                  const std::vector<std::filesystem::path>& nonLakosianDirs,
0554                  std::vector<std::pair<std::string, std::string>> thirdPartyDirs,
0555                  bool printToConsole):
0556     d(std::make_unique<CppTool::Private>(this,
0557                                          std::move(sourcePath),
0558                                          compileCommand,
0559                                          databasePath,
0560                                          ignoreList,
0561                                          nonLakosianDirs,
0562                                          std::move(thirdPartyDirs),
0563                                          printToConsole))
0564 {
0565 }
0566 
0567 CppTool::CppTool(std::filesystem::path sourcePath,
0568                  const clang::tooling::CompilationDatabase& db,
0569                  const std::filesystem::path& databasePath,
0570                  unsigned numThreads,
0571                  const std::vector<std::string>& ignoreList,
0572                  const std::vector<std::filesystem::path>& nonLakosianDirs,
0573                  std::vector<std::pair<std::string, std::string>> thirdPartyDirs,
0574                  bool printToConsole):
0575     d(std::make_unique<CppTool::Private>(this,
0576                                          std::move(sourcePath),
0577                                          db.getAllCompileCommands(),
0578                                          databasePath,
0579                                          numThreads,
0580                                          ignoreList,
0581                                          nonLakosianDirs,
0582                                          std::move(thirdPartyDirs),
0583                                          printToConsole))
0584 {
0585     d->compilationDb->setup(UseSystemHeaders::e_Query, !printToConsole);
0586 }
0587 
0588 CppTool::~CppTool() noexcept = default;
0589 
0590 void CppTool::setSharedMemDb(std::shared_ptr<lvtmdb::ObjectStore> const& sharedMemDb)
0591 {
0592     d->sharedMemDb = sharedMemDb;
0593 }
0594 
0595 lvtmdb::ObjectStore& CppTool::getObjectStore()
0596 {
0597     return d->memDb();
0598 }
0599 
0600 void CppTool::setUseSystemHeaders(UseSystemHeaders value)
0601 {
0602     d->useSystemHeaders = value;
0603 }
0604 
0605 bool CppTool::ensureSetup()
0606 {
0607     if (!processCompilationDatabase()) {
0608         return false;
0609     }
0610     assert(d->compilationDb);
0611 
0612     return true;
0613 }
0614 
0615 bool CppTool::processCompilationDatabase()
0616 {
0617     if (d->compilationDb) {
0618         return true;
0619     }
0620 
0621     if (!d->compileCommandsJsons.empty()) {
0622         CombinedCompilationDatabase compDb;
0623         for (const std::filesystem::path& path : d->compileCommandsJsons) {
0624             auto result = compDb.addCompilationDatabase(path);
0625             if (result.has_error()) {
0626                 switch (result.error().kind) {
0627                 case CompilationDatabaseError::Kind::ErrorLoadingFromFile: {
0628                     Q_EMIT messageFromThread(
0629                         tr(("Error loading file " + path.string() + " with " + result.error().message).c_str()),
0630                         0);
0631                     break;
0632                 }
0633                 case CompilationDatabaseError::Kind::CompileCommandsContainsNoCommands: {
0634                     Q_EMIT messageFromThread(
0635                         tr(("Error processing " + path.string() + " contains no commands").c_str()),
0636                         0);
0637                     break;
0638                 }
0639                 case CompilationDatabaseError::Kind::CompileCommandsContainsNoFiles: {
0640                     Q_EMIT messageFromThread(tr(("Error processing " + path.string() + " contains no files").c_str()),
0641                                              0);
0642                     break;
0643                 }
0644                 }
0645                 return false;
0646             }
0647         }
0648         d->compilationDb.emplace(d->sourcePath, compDb.getAllCompileCommands(), d->messageCallback);
0649     } else if (d->compileCommand.has_value()) {
0650         d->compilationDb.emplace(d->sourcePath, std::vector({d->compileCommand.value()}), d->messageCallback);
0651     } else {
0652         std::cerr << "No compilation database nor compilation command on the tool execution";
0653         return false;
0654     }
0655 
0656     d->compilationDb->setIgnoreGlobs(d->ignoreList);
0657     d->compilationDb->setup(d->useSystemHeaders, !d->printToConsole);
0658     return true;
0659 }
0660 
0661 void CppTool::setupIncrementalUpdate(FilesystemScanner::IncrementalResult& res, bool doIncremental)
0662 // Processes what changes the filesystem scanner found, populating
0663 // d->incrementalCdb with a code database containing only the files we need
0664 // to re-parse later
0665 {
0666     d->lastRunMadeChanges = !res.newFiles.empty() || !res.modifiedFiles.empty() || !res.deletedFiles.empty();
0667     if (!d->lastRunMadeChanges) {
0668         // no changes to report
0669 
0670         std::vector<std::string> files;
0671         if (!doIncremental) {
0672             // operate on every file anyway because we are in non-incremental mode
0673             files = d->compilationDb->getAllFiles();
0674         } // else files is empty because there are no changes
0675 
0676         d->incrementalCdb.emplace(*d->compilationDb, files, d->compilationDb->commonParent());
0677         return;
0678     }
0679 
0680     // delete files which were deleted or modified
0681     QList<std::string> removedFiles;
0682     std::set<intptr_t> removedPtrs;
0683     auto deleteFile = [this, &removedFiles, &removedPtrs](const std::string& qualifiedName) {
0684         if (d->printToConsole) {
0685             qDebug() << "Going to remove file: " << qualifiedName.c_str();
0686         }
0687         d->memDb().withRWLock([&] {
0688             const bool alreadyRemoved =
0689                 std::find(std::begin(removedFiles), std::end(removedFiles), qualifiedName) != std::end(removedFiles);
0690             if (!alreadyRemoved) {
0691                 removedFiles += d->memDb().removeFile(d->memDb().getFile(qualifiedName), removedPtrs);
0692             }
0693         });
0694     };
0695 
0696     std::for_each(res.modifiedFiles.begin(), res.modifiedFiles.end(), deleteFile);
0697     std::for_each(res.deletedFiles.begin(), res.deletedFiles.end(), deleteFile);
0698 
0699     for (const std::string& pkg : res.deletedPkgs) {
0700         lvtmdb::PackageObject *memPkg = nullptr;
0701 
0702         d->memDb().withROLock([&] {
0703             memPkg = d->memDb().getPackage(pkg);
0704         });
0705         std::set<intptr_t> removed;
0706         d->memDb().removePackage(memPkg, removed);
0707     }
0708 
0709     if (d->printToConsole) {
0710         qDebug() << "All files that are removed from our call:";
0711         for (const std::string& file : qAsConst(removedFiles)) {
0712             qDebug() << "\t " << file.c_str();
0713         }
0714     }
0715 
0716     // set up compilation database for incremental update
0717     std::vector<std::string> newPaths;
0718     if (doIncremental) {
0719         for (const std::string& removedFile : qAsConst(removedFiles)) {
0720             // we only want to add files removed because of dependency propagation,
0721             // not because the real files are gone
0722             const auto it = std::find(res.deletedFiles.begin(), res.deletedFiles.end(), removedFile);
0723             if (it == res.deletedFiles.end()) {
0724                 newPaths.push_back(removedFile);
0725             }
0726         }
0727 
0728         std::move(res.newFiles.begin(), res.newFiles.end(), std::back_inserter(newPaths));
0729         std::move(res.modifiedFiles.begin(), res.modifiedFiles.end(), std::back_inserter(newPaths));
0730     } else {
0731         // We were asked not to do an incremental update so add all existing
0732         // files anyway
0733         newPaths = d->compilationDb->getAllFiles();
0734     }
0735 
0736     if (d->printToConsole) {
0737         qDebug() << "Files for the incremental db";
0738         for (const std::string& file : newPaths) {
0739             qDebug() << "\t " << file.c_str();
0740         }
0741     }
0742 
0743     d->incrementalCdb.emplace(*d->compilationDb, newPaths, d->compilationDb->commonParent());
0744 }
0745 
0746 bool CppTool::findPhysicalEntities(bool doIncremental)
0747 {
0748     if (!ensureSetup()) {
0749         return false;
0750     }
0751 
0752     bool dbErrorState = false;
0753 
0754     const lvtmdb::ObjectStore::State oldState = d->memDb().state();
0755     if (oldState == lvtmdb::ObjectStore::State::Error || oldState == lvtmdb::ObjectStore::State::PhysicalError) {
0756         // we can't trust anything in this database
0757         doIncremental = false;
0758         dbErrorState = true;
0759 
0760         // Since the Wt-to-Soci refactoring, we don't store previous acquired data on the database anymore, so if we
0761         // clear the database for any error, we'll lose the conectivity between the components. But there are some minor
0762         // errors that may be "ok" to continue, such as partial inclusion errors (e.g.: ONE file in a given component
0763         // couldn't be found) - In such cases it is not worth to wipe out the entire codebase just because of one
0764         // inclusion error.
0765         // TODO: Verify if there's a better handling for error databases and/or warn the user about it.
0766         //        // Don't trust anything in an error database
0767         //        d->memDb().withRWLock([&]() {
0768         //            d->memDb().clear();
0769         //        });
0770     }
0771 
0772     // We can do a physical update if we completed the last physical parse.
0773     if (oldState != lvtmdb::ObjectStore::State::PhysicalReady && oldState != lvtmdb::ObjectStore::State::AllReady
0774         && oldState != lvtmdb::ObjectStore::State::LogicalError) {
0775         // the database never contained physical information so we can't do an
0776         // incremental update
0777         doIncremental = false;
0778     }
0779 
0780     FilesystemScanner scanner(d->memDb(),
0781                               d->compilationDb->commonParent(),
0782                               *d->compilationDb,
0783                               d->messageCallback,
0784                               d->printToConsole,
0785                               d->nonLakosianDirs,
0786                               d->ignoreList);
0787 
0788     {
0789         std::unique_lock<std::mutex> lock(d->executorMutex);
0790         if (d->executorCancelled) {
0791             d->executorCancelled = false;
0792             return false;
0793         }
0794     }
0795 
0796     FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb();
0797     {
0798         std::unique_lock<std::mutex> lock(d->executorMutex);
0799         if (d->executorCancelled) {
0800             d->executorCancelled = false;
0801             return false;
0802         }
0803     }
0804 
0805     setupIncrementalUpdate(res, doIncremental);
0806 
0807     {
0808         std::unique_lock<std::mutex> lock(d->executorMutex);
0809         if (d->executorCancelled) {
0810             d->executorCancelled = false;
0811             return false;
0812         }
0813     }
0814 
0815     if (d->lastRunMadeChanges || dbErrorState) {
0816         // we only have physical entities: no dependencies
0817         d->memDb().setState(lvtmdb::ObjectStore::State::NoneReady);
0818     }
0819 
0820     return true;
0821 }
0822 
0823 void CppTool::setPrintToConsole(bool b)
0824 {
0825     d->printToConsole = b;
0826 }
0827 
0828 bool CppTool::runPhysical(bool skipScan)
0829 {
0830     if (!ensureSetup()) {
0831         return false;
0832     }
0833     {
0834         std::unique_lock<std::mutex> lock(d->executorMutex);
0835         if (d->executorCancelled) {
0836             d->executorCancelled = false;
0837             return false;
0838         }
0839     }
0840 
0841     if (!skipScan && !findPhysicalEntities(true)) {
0842         return false;
0843     }
0844 
0845     assert(d->incrementalCdb);
0846 
0847     auto filenameCallback = [this](const std::string& path) {
0848         if (d->printToConsole) {
0849             qDebug() << "Processing file " << path;
0850         }
0851         Q_EMIT processingFileNotification(QString::fromStdString(path));
0852     };
0853 
0854     auto messageCallback = [this](const std::string& message, long threadId) {
0855         if (d->printToConsole) {
0856             qDebug() << message;
0857         }
0858         Q_EMIT messageFromThread(QString::fromStdString(message), threadId);
0859     };
0860 
0861     std::unique_ptr<clang::tooling::FrontendActionFactory> dependencyScanner =
0862         std::make_unique<DepScanActionFactory>(d->memDb(),
0863                                                d->compilationDb->commonParent(),
0864                                                d->nonLakosianDirs,
0865                                                d->thirdPartyDirs,
0866                                                filenameCallback,
0867                                                d->ignoreList,
0868                                                d->headerLocationCallback);
0869 
0870     Q_EMIT aboutToCallClangNotification(d->incrementalCdb->numCompileCommands());
0871 
0872     d->toolExecutor = new ToolExecutor(*d->incrementalCdb, d->numThreads, messageCallback, d->memDb());
0873 
0874     llvm::Error err = d->toolExecutor->execute(std::move(dependencyScanner));
0875 
0876     bool cancelled = false;
0877     {
0878         std::unique_lock<std::mutex> lock(d->executorMutex);
0879         delete d->toolExecutor;
0880         d->toolExecutor = nullptr;
0881         cancelled = d->executorCancelled;
0882         d->executorCancelled = false;
0883     }
0884 
0885     if (err) {
0886         d->memDb().setState(lvtmdb::ObjectStore::State::PhysicalError);
0887 
0888         err = llvm::handleErrors(std::move(err), [&](llvm::StringError const& err) -> llvm::Error {
0889             Q_EMIT messageFromThread(QString::fromStdString(err.getMessage()), 0);
0890             return llvm::Error::success();
0891         });
0892         return !err;
0893     }
0894 
0895     if (cancelled) {
0896         d->memDb().setState(lvtmdb::ObjectStore::State::NoneReady);
0897     } else {
0898         if (d->memDb().state() != lvtmdb::ObjectStore::State::AllReady
0899             || d->memDb().state() != lvtmdb::ObjectStore::State::LogicalError) {
0900             d->memDb().setState(lvtmdb::ObjectStore::State::PhysicalReady);
0901         }
0902     }
0903 
0904     return !err;
0905 }
0906 
0907 bool CppTool::runFull(bool skipPhysical)
0908 {
0909     if (!ensureSetup()) {
0910         return false;
0911     }
0912     {
0913         std::unique_lock<std::mutex> lock(d->executorMutex);
0914         if (d->executorCancelled) {
0915             d->executorCancelled = false;
0916             return false;
0917         }
0918     }
0919 
0920     bool doIncremental = true;
0921     const lvtmdb::ObjectStore::State oldState = d->memDb().state();
0922     if (oldState != lvtmdb::ObjectStore::State ::AllReady) {
0923         // the database never contained logical information so we can't do an
0924         // incremental update
0925         doIncremental = false;
0926     }
0927 
0928     if (!findPhysicalEntities(doIncremental)) {
0929         return false;
0930     }
0931 
0932     if (!skipPhysical && !runPhysical(true)) {
0933         return false;
0934     }
0935 
0936     assert(d->incrementalCdb);
0937 
0938     if (oldState != lvtmdb::ObjectStore::State::AllReady) {
0939         // if we aren't starting from a database with logical data, we need to
0940         // re-run on every file, even if incremental updates only found changes
0941         // in a few files
0942         d->incrementalCdb.emplace(*d->compilationDb, d->compilationDb->getAllFiles(), d->compilationDb->commonParent());
0943     }
0944 
0945     auto filenameCallback = [this](const std::string& path) {
0946         if (d->printToConsole) {
0947             qDebug() << "Processing file " << path;
0948         }
0949         Q_EMIT processingFileNotification(QString::fromStdString(path));
0950     };
0951 
0952     std::optional<std::function<void(const std::string&, long)>> messageOpt;
0953     if (d->showDatabaseErrors) {
0954         messageOpt = d->messageCallback;
0955     }
0956 
0957     std::unique_ptr<clang::tooling::FrontendActionFactory> actionFactory =
0958         std::make_unique<LogicalDepActionFactory>(d->memDb(),
0959                                                   d->compilationDb->commonParent(),
0960                                                   d->nonLakosianDirs,
0961                                                   d->thirdPartyDirs,
0962                                                   filenameCallback,
0963                                                   messageOpt,
0964                                                   d->printToConsole,
0965                                                   d->handleCppCommentsCallback);
0966 
0967     Q_EMIT aboutToCallClangNotification(d->incrementalCdb->numCompileCommands());
0968 
0969     d->toolExecutor = new ToolExecutor(*d->incrementalCdb, d->numThreads, d->messageCallback, d->memDb());
0970 
0971     llvm::Error err = d->toolExecutor->execute(std::move(actionFactory));
0972 
0973     bool cancelled = false;
0974     {
0975         std::unique_lock<std::mutex> lock(d->executorMutex);
0976         delete d->toolExecutor;
0977         d->toolExecutor = nullptr;
0978         cancelled = d->executorCancelled;
0979         d->executorCancelled = false;
0980     }
0981 
0982     if (err) {
0983         d->memDb().setState(lvtmdb::ObjectStore::State::LogicalError);
0984 
0985         err = llvm::handleErrors(std::move(err), [&](llvm::StringError const& err) -> llvm::Error {
0986             Q_EMIT messageFromThread(QString::fromStdString(err.getMessage()), 0);
0987             return llvm::Error::success();
0988         });
0989         return !err;
0990     }
0991 
0992     if (!cancelled) {
0993         d->memDb().setState(lvtmdb::ObjectStore::State::AllReady);
0994     }
0995     // else keep it the same because physical info is probably okay
0996 
0997     if (!cancelled) {
0998         if (d->printToConsole) {
0999             qDebug() << "Collating output database";
1000         }
1001         Q_EMIT processingFileNotification(tr("Collating output database"));
1002         // d->memDb().withROLock([&] {
1003         //    d->memDb().writeToDb(d->outputDb.session());
1004         //    d->outputDb.setState(static_cast<lvtmdb::ObjectStore::State>(d->memDb().state()));
1005         // });
1006     }
1007 
1008     if (!LogicalPostProcessUtil::postprocess(d->memDb(), d->printToConsole)) {
1009         return false;
1010     }
1011 
1012     return !err;
1013 }
1014 
1015 bool CppTool::lastRunMadeChanges() const
1016 {
1017     return d->lastRunMadeChanges;
1018 }
1019 
1020 void CppTool::setHeaderLocationCallback(HeaderCallbacks::HeaderLocationCallback_f const& headerLocationCallback)
1021 {
1022     d->headerLocationCallback = headerLocationCallback;
1023 }
1024 
1025 void CppTool::setHandleCppCommentsCallback(HandleCppCommentsCallback_f const& handleCppCommentsCallback)
1026 {
1027     d->handleCppCommentsCallback = handleCppCommentsCallback;
1028 }
1029 
1030 void CppTool::cancelRun()
1031 {
1032     std::unique_lock<std::mutex> lock(d->executorMutex);
1033     d->executorCancelled = true;
1034 
1035     if (!d->toolExecutor) {
1036         return;
1037     }
1038 
1039     d->toolExecutor->cancelRun();
1040 }
1041 
1042 void CppTool::setShowDatabaseErrors(bool value)
1043 {
1044     d->showDatabaseErrors = value;
1045 }
1046 
1047 bool CppTool::finalizingThreads() const
1048 {
1049     return d->executorCancelled;
1050 }
1051 
1052 void CppTool::messageCallback(const std::string& message, long threadId)
1053 {
1054     if (d->printToConsole) {
1055         qDebug() << message;
1056     }
1057 
1058     Q_EMIT messageFromThread(QString::fromStdString(message), threadId);
1059 }
1060 
1061 } // namespace Codethink::lvtclp