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