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

0001 // ct_lvtclp_toolexecutor.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_diagnostic_consumer.h>
0021 #include <ct_lvtclp_toolexecutor.h>
0022 
0023 #include <QRunnable>
0024 #include <QThreadPool>
0025 
0026 #include <llvm/Support/VirtualFileSystem.h>
0027 
0028 #include <climits>
0029 #include <mutex>
0030 #include <utility>
0031 #include <vector>
0032 
0033 // much of this component is taken straight from clang source
0034 // that source is licenced as Apache-2.0 with llvm-exception
0035 // See https://llvm.org/LICENSE.txt
0036 //     clang/lib/Tooling/AllTUsExecution.cpp
0037 
0038 namespace {
0039 
0040 int unsignedToInt(unsigned i)
0041 {
0042     if (i > (unsigned) INT_MAX) {
0043         return INT_MAX;
0044     }
0045     return static_cast<int>(i);
0046 }
0047 
0048 llvm::Error makeError(const llvm::Twine& msg)
0049 {
0050     return llvm::make_error<llvm::StringError>(msg, llvm::inconvertibleErrorCode());
0051 }
0052 
0053 class ThreadSafeToolResults : public clang::tooling::ToolResults {
0054   private:
0055     // DATA
0056     clang::tooling::InMemoryToolResults d_results;
0057     std::mutex d_mutex;
0058 
0059   public:
0060     // MODIFIERS
0061     void addResult(llvm::StringRef key, llvm::StringRef value) override
0062     {
0063         std::unique_lock<std::mutex> guard(d_mutex);
0064         d_results.addResult(key, value);
0065     }
0066 
0067     std::vector<std::pair<llvm::StringRef, llvm::StringRef>> AllKVResults() override
0068     {
0069         return d_results.AllKVResults();
0070     }
0071 
0072     void forEachResult(llvm::function_ref<void(llvm::StringRef key, llvm::StringRef value)> callback) override
0073     {
0074         d_results.forEachResult(callback);
0075     }
0076 };
0077 
0078 } // unnamed namespace
0079 
0080 namespace Codethink::lvtclp {
0081 
0082 struct ToolExecutor::Private {
0083     const clang::tooling::CompilationDatabase& compilations;
0084 
0085     ThreadSafeToolResults results;
0086     clang::tooling::ExecutionContext context;
0087 
0088     llvm::StringMap<std::string> overlayFiles;
0089 
0090     unsigned threadCount;
0091 
0092     std::function<void(std::string, long)> messageCallback;
0093     lvtmdb::ObjectStore& memDb;
0094 
0095     QThreadPool pool;
0096 
0097     Private(const clang::tooling::CompilationDatabase& compDb,
0098             unsigned threadCount,
0099             std::function<void(std::string, long)> messageCallback,
0100             lvtmdb::ObjectStore& memDb):
0101         compilations(compDb),
0102         context(&results),
0103         threadCount(threadCount),
0104         messageCallback(std::move(messageCallback)),
0105         memDb(memDb)
0106     {
0107     }
0108 };
0109 
0110 class ToolExecutor::RunnableThread : public QRunnable {
0111     // The ancient version of Qt in appimage (5.11) doesn't support creating a
0112     // QRunnable from a lambda so we have to write a lambda by hand here :(
0113 
0114   public:
0115     // PUBLIC TYPES
0116     using Action = std::pair<std::unique_ptr<clang::tooling::FrontendActionFactory>, clang::tooling::ArgumentsAdjuster>;
0117 
0118   private:
0119     // PRIVATE DATA
0120     const std::string d_file;
0121     ToolExecutor *d_executor;
0122     const Action& d_action;
0123     std::function<void(llvm::Twine)> d_appendError;
0124     std::function<void(std::string, long)> d_messageCallback;
0125     lvtmdb::ObjectStore& d_memDb;
0126 
0127   public:
0128     // CREATORS
0129     RunnableThread(std::string file,
0130                    ToolExecutor *executor,
0131                    const Action& action,
0132                    std::function<void(llvm::Twine)> appendError,
0133                    std::function<void(std::string, long)> messageCallback,
0134                    lvtmdb::ObjectStore& memDb):
0135         d_file(std::move(file)),
0136         d_executor(executor),
0137         d_action(action),
0138         d_appendError(std::move(appendError)),
0139         d_messageCallback(std::move(messageCallback)),
0140         d_memDb(memDb)
0141     {
0142         setAutoDelete(true);
0143     }
0144 
0145     ~RunnableThread() override = default;
0146 
0147     // MUTATORS
0148     void run() override
0149     {
0150         clang::tooling::ClangTool tool(d_executor->d->compilations,
0151                                        {d_file},
0152                                        std::make_shared<clang::PCHContainerOperations>(),
0153                                        llvm::vfs::createPhysicalFileSystem());
0154         auto consumer = lvtclp::DiagnosticConsumer(d_memDb, d_messageCallback);
0155         tool.setDiagnosticConsumer(&consumer);
0156         for (const auto& fileAndContent : d_executor->d->overlayFiles) {
0157             tool.mapVirtualFile(fileAndContent.first(), fileAndContent.second);
0158         }
0159 
0160         // 0 on success;
0161         // 1 if any error occurred;
0162         // 2 if there is no error but some files are skipped due to missing compile commands.
0163         auto ret = tool.run(d_action.first.get());
0164 
0165         if (ret == 1) {
0166             d_appendError(llvm::Twine("Failed to run action on ") + d_file + "\n");
0167         }
0168     }
0169 };
0170 
0171 ToolExecutor::ToolExecutor(const clang::tooling::CompilationDatabase& compDb,
0172                            unsigned threadCount,
0173                            std::function<void(std::string, long)> messageCallback,
0174                            lvtmdb::ObjectStore& memDb):
0175     d(std::make_unique<Private>(compDb, threadCount, std::move(messageCallback), memDb))
0176 {
0177 }
0178 
0179 ToolExecutor::~ToolExecutor() noexcept = default;
0180 
0181 llvm::Error ToolExecutor::execute(
0182     llvm::ArrayRef<std::pair<std::unique_ptr<clang::tooling::FrontendActionFactory>, clang::tooling::ArgumentsAdjuster>>
0183         actions)
0184 {
0185     if (actions.empty()) {
0186         return makeError("No action to execute.");
0187     }
0188 
0189     if (actions.size() != 1) {
0190         return makeError("Only support executing 1 action");
0191     }
0192 
0193     std::string errorMsg;
0194     std::mutex logMutex;
0195     auto appendError = [&errorMsg, &logMutex](llvm::Twine err) {
0196         std::unique_lock<std::mutex> lock(logMutex);
0197         errorMsg += err.str();
0198     };
0199 
0200     const auto& action = actions.front();
0201 
0202     {
0203         d->pool.setMaxThreadCount(unsignedToInt(d->threadCount));
0204 
0205         for (const std::string& file : d->compilations.getAllFiles()) {
0206             // deleted automatically by QThreadPool
0207             // see QRunnable::setAutoDelete
0208             QRunnable *runnable = new RunnableThread(file, this, action, appendError, d->messageCallback, d->memDb);
0209             d->pool.start(runnable);
0210         }
0211 
0212         // make sure all tasks have finished
0213         d->pool.waitForDone();
0214     }
0215 
0216     if (!errorMsg.empty()) {
0217         return makeError(errorMsg);
0218     }
0219 
0220     return llvm::Error::success();
0221 }
0222 
0223 clang::tooling::ExecutionContext *ToolExecutor::getExecutionContext()
0224 {
0225     return &d->context;
0226 }
0227 
0228 clang::tooling::ToolResults *ToolExecutor::getToolResults()
0229 {
0230     return &d->results;
0231 }
0232 
0233 void ToolExecutor::mapVirtualFile(llvm::StringRef filePath, llvm::StringRef content)
0234 {
0235     d->overlayFiles[filePath] = std::string(content);
0236 }
0237 
0238 void ToolExecutor::cancelRun()
0239 {
0240     d->pool.clear();
0241 }
0242 
0243 llvm::StringRef ToolExecutor::getExecutorName() const
0244 {
0245     return "Codethink::lvtclp::ToolExecutor";
0246 }
0247 
0248 } // namespace Codethink::lvtclp