diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ac986d..8a10c92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,9 @@ set(COMMON_SOURCES src/ArgumentParser/BaseArgumentParser.cpp src/ArgumentParser/ArgumentManager.cpp src/ArgumentParser/ArgumentParserFactory.cpp + src/App/Config.cpp + src/App/Files.cpp + src/App/Runner.cpp src/ctrace_tools/mangle.cpp src/ctrace_tools/languageType.cpp src/ctrace_tools/strings.cpp @@ -55,12 +58,19 @@ if (USE_ADDRESS_SANITIZER) include(${CMAKE_SOURCE_DIR}/cmake/debug/sanitizer/AddressSanitizer.cmake) endif() +# === INCLUDE .CMAKE === + include(${CMAKE_SOURCE_DIR}/cmake/FetchNlohmannJson.cmake) target_link_libraries(ctrace PRIVATE nlohmann_json::nlohmann_json) include(${CMAKE_SOURCE_DIR}/cmake/stackUsageAnalyzer.cmake) target_link_libraries(ctrace PRIVATE coretrace::stack_usage_analyzer_lib) +include(${CMAKE_SOURCE_DIR}/cmake/httpLib.cmake) +target_link_libraries(ctrace PRIVATE httplib::httplib) + +# === OPTIONAL DEPENDENCIES === + option(USE_EXTERNAL_CLI11 "Download CLI11" OFF) if (USE_EXTERNAL_CLI11 AND PARSER_TYPE STREQUAL "CLI11") include(${CMAKE_SOURCE_DIR}/cmake/CLI11.cmake) diff --git a/README.md b/README.md index e292243..dbb34d2 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,11 @@ Options: --invoke Invokes specific tools (comma-separated). Available tools: flawfinder, ikos, cppcheck, tscancode. --input Specifies the source files to analyse (comma-separated). + --ipc IPC method: standardIO, socket, or serve. + --ipc-path IPC path (default: /tmp/coretrace_ipc). + --serve-host HTTP server host when --ipc=serve. + --serve-port HTTP server port when --ipc=serve. + --async Enables asynchronous execution. Examples: ctrace --input main.cpp,util.cpp --static --invoke=cppcheck,flawfinder @@ -69,6 +74,46 @@ Description: ./ctrace --input ../tests/EmptyForStatement.cc --entry-points=main --verbose --static --dyn ``` +### SERVER MODE + +Start the HTTP server: + +```bash +./ctrace --ipc serve --serve-host 127.0.0.1 --serve-port 8080 +``` + +Send a request: + +```bash +curl -X POST http://127.0.0.1:8080/api \ + -H "Content-Type: application/json" \ + -d '{ + "proto": "coretrace-1.0", + "id": 1, + "type": "request", + "method": "run_analysis", + "params": { + "input": ["./tests/buffer_overflow.cc"], + "entry_points": ["main"], + "static_analysis": true, + "dynamic_analysis": false, + "invoke": ["flawfinder"], + "sarif_format": true, + "report_file": "ctrace-report.txt", + "output_file": "ctrace.out", + "ipc": "serve", + "ipc_path": "/tmp/coretrace_ipc", + "async": false, + "verbose": true + } + }' +``` + +Response notes: +- `status` is `ok` or `error`. +- `result.outputs` groups tool output by tool name. +- Each output entry has `stream` and `message`. If a tool emits JSON, `message` is returned as a JSON object. + ### Mangle/Demangle API ```c++ diff --git a/cmake/httpLib.cmake b/cmake/httpLib.cmake new file mode 100644 index 0000000..0b5381a --- /dev/null +++ b/cmake/httpLib.cmake @@ -0,0 +1,9 @@ +include(FetchContent) + +FetchContent_Declare( + cpp_httplib + GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git + GIT_TAG v0.14.3 +) + +FetchContent_MakeAvailable(cpp_httplib) diff --git a/include/App/Config.hpp b/include/App/Config.hpp new file mode 100644 index 0000000..00aa2ac --- /dev/null +++ b/include/App/Config.hpp @@ -0,0 +1,12 @@ +#ifndef APP_CONFIG_HPP +#define APP_CONFIG_HPP + +#include "Config/config.hpp" +#include "attributes.hpp" + +namespace ctrace +{ + CT_NODISCARD ProgramConfig buildConfig(int argc, char *argv[]); +} + +#endif // APP_CONFIG_HPP diff --git a/include/App/Files.hpp b/include/App/Files.hpp new file mode 100644 index 0000000..ec8ee45 --- /dev/null +++ b/include/App/Files.hpp @@ -0,0 +1,15 @@ +#ifndef APP_FILES_HPP +#define APP_FILES_HPP + +#include +#include + +#include "Config/config.hpp" +#include "attributes.hpp" + +namespace ctrace +{ + CT_NODISCARD std::vector resolveSourceFiles(const ProgramConfig& config); +} + +#endif // APP_FILES_HPP diff --git a/include/App/Runner.hpp b/include/App/Runner.hpp new file mode 100644 index 0000000..c39bd8b --- /dev/null +++ b/include/App/Runner.hpp @@ -0,0 +1,13 @@ +#ifndef APP_RUNNER_HPP +#define APP_RUNNER_HPP + +#include "Config/config.hpp" +#include "attributes.hpp" + +namespace ctrace +{ + CT_NODISCARD int run_server(const ProgramConfig& config); + CT_NODISCARD int run_cli_analysis(const ProgramConfig& config); +} + +#endif // APP_RUNNER_HPP diff --git a/include/Config/config.hpp b/include/Config/config.hpp index dbf8048..9e5f1ae 100644 --- a/include/Config/config.hpp +++ b/include/Config/config.hpp @@ -1,6 +1,8 @@ #ifndef CONFIG_HPP #define CONFIG_HPP +#include +#include #include #include #include @@ -9,7 +11,6 @@ #include "ArgumentParser/ArgumentManager.hpp" #include "ArgumentParser/ArgumentParserFactory.hpp" #include "ctrace_tools/strings.hpp" -#include "Process/Ipc/IpcStrategy.hpp" #include "ctrace_defs/types.hpp" static void printHelp(void) @@ -79,6 +80,8 @@ namespace ctrace bool hasInvokedSpecificTools = false; ///< Indicates if specific tools are invoked. std::string ipc = ctrace_defs::IPC_TYPES.front(); ///< IPC method to use (e.g., fifo, socket). std::string ipcPath = "/tmp/coretrace_ipc"; ///< Path for IPC communication. + std::string serverHost = "127.0.0.1"; ///< Host for server IPC (if applicable). + int serverPort = 8080; ///< Port for server IPC (if applicable). std::vector specificTools; ///< List of specific tools to invoke. @@ -199,10 +202,14 @@ namespace ctrace if (std::find(ipc_list.begin(), ipc_list.end(), value) == ipc_list.end()) { std::cerr << "Invalid IPC type: '" << value << "'\n" - << "Available IPC types: "; + << "Available IPC types: ["; for (const auto& ipc : ipc_list) - std::cerr << ipc << " "; - std::cerr << std::endl; + { + std::cerr << ipc; + if (ipc != ipc_list.back()) + std::cerr << ", "; + } + std::cerr << "]" << std::endl; std::exit(EXIT_FAILURE); } config.global.ipc = value; @@ -211,6 +218,16 @@ namespace ctrace { config.global.ipcPath = value; }; + commands["--serve-host"] = [this](const std::string& value) + { + config.global.serverHost = value; + std::cout << "[DEBUG] Server host set to " << config.global.serverHost << std::endl; + }; + commands["--serve-port"] = [this](const std::string& value) + { + config.global.serverPort = std::stoi(value); + std::cout << "[DEBUG] Server port set to " << config.global.serverPort << std::endl; + }; // commands["--output"] = [this](const std::string& value) { // if (!config.files.empty()) config.files.back().output_file = value; // }; diff --git a/include/Process/Ipc/HttpServer.hpp b/include/Process/Ipc/HttpServer.hpp new file mode 100644 index 0000000..d482ce4 --- /dev/null +++ b/include/Process/Ipc/HttpServer.hpp @@ -0,0 +1,555 @@ +// server.cpp +#include +#include +#include +#include +#include +#include +#include + +#include "httplib.h" // cpp-httplib (header-only) +#include // nlohmann::json (header-only) + +#include "Config/config.hpp" +#include "Process/Tools/ToolsInvoker.hpp" +#include "ctrace_tools/strings.hpp" + +using json = nlohmann::json; + +// ============================================================================ +// Logging +// ============================================================================ + +class ILogger +{ + public: + virtual ~ILogger() = default; + virtual void info(const std::string& msg) = 0; + virtual void error(const std::string& msg) = 0; +}; + +class ConsoleLogger : public ILogger +{ + public: + void info(const std::string& msg) override + { + std::cout << "[INFO] :: " << msg << '\n'; + } + + void error(const std::string& msg) override + { + std::cerr << "[ERROR] :: " << msg << '\n'; + } + + void debug(const std::string& msg) + { + std::cout << "[DEBUG] :: " << msg << '\n'; + } + + void warn(const std::string& msg) + { + std::cout << "[WARN] :: " << msg << '\n'; + } +}; + +// ============================================================================ +// API / Contrôleur : gestion du protocole JSON-RPC-like +// ============================================================================ + +class ApiHandler +{ +public: + explicit ApiHandler(ILogger& logger) + : logger_(logger) + {} + + json handle_request(const json& request) + { + log_request(logger_, request); + + json response; + response["proto"] = request.value("proto", std::string("coretrace-1.0")); + response["id"] = request.value("id", 0); + response["type"] = "response"; + + std::string method = request.value("method", ""); + json params = request.value("params", json::object()); + + if (method == "run_analysis") { + return handle_run_analysis(response, params); + } + + // Méthode inconnue + response["status"] = "error"; + response["error"] = { + {"code", "UnknownMethod"}, + {"message", "Unknown method: " + method} + }; + return response; + } + +private: + struct ParseError + { + std::string code; + std::string message; + }; + + struct BoolField + { + const char* key; + bool* target; + }; + + struct StringField + { + const char* key; + std::string* target; + }; + + ILogger& logger_; + + static void log_request(ILogger& logger, const json& request) + { + logger.info("Incoming request: " + request.dump()); + } + + static bool read_bool(const json& params, const char* key, bool& out, ParseError& err) + { + const auto it = params.find(key); + if (it == params.end() || it->is_null()) + { + return true; + } + if (!it->is_boolean()) + { + err = {"InvalidParams", std::string("Expected boolean for '") + key + "'."}; + return false; + } + out = it->get(); + return true; + } + + static bool read_string(const json& params, const char* key, std::string& out, ParseError& err) + { + const auto it = params.find(key); + if (it == params.end() || it->is_null()) + { + return true; + } + if (!it->is_string()) + { + err = {"InvalidParams", std::string("Expected string for '") + key + "'."}; + return false; + } + out = it->get(); + return true; + } + + static bool read_string_list(const json& params, const char* key, std::vector& out, ParseError& err) + { + const auto it = params.find(key); + if (it == params.end() || it->is_null()) + { + return true; + } + + if (it->is_string()) + { + const std::string raw = it->get(); + const auto parts = ctrace_tools::strings::splitByComma(raw); + for (const auto part : parts) + { + if (!part.empty()) + { + out.emplace_back(part); + } + } + return true; + } + + if (!it->is_array()) + { + err = {"InvalidParams", std::string("Expected array or string for '") + key + "'."}; + return false; + } + + for (const auto& item : *it) + { + if (!item.is_string()) + { + err = {"InvalidParams", std::string("Expected string values in '") + key + "' array."}; + return false; + } + const std::string value = item.get(); + if (!value.empty()) + { + out.emplace_back(value); + } + } + return true; + } + + static bool apply_bool_fields(const json& params, ParseError& err, std::initializer_list fields) + { + for (const auto& field : fields) + { + if (!read_bool(params, field.key, *field.target, err)) + { + return false; + } + } + return true; + } + + static bool apply_string_fields(const json& params, ParseError& err, std::initializer_list fields) + { + for (const auto& field : fields) + { + if (!read_string(params, field.key, *field.target, err)) + { + return false; + } + } + return true; + } + + template + static bool apply_list_param(const json& params, const char* key, ParseError& err, ApplyFn&& apply) + { + std::vector values; + if (!read_string_list(params, key, values, err)) + { + return false; + } + if (!values.empty()) + { + apply(values); + } + return true; + } + + static std::string join_with_comma(const std::vector& items) + { + std::string joined; + for (size_t i = 0; i < items.size(); ++i) + { + if (i > 0) + { + joined.push_back(','); + } + joined.append(items[i]); + } + return joined; + } + + static bool apply_async_field(const json& params, ctrace::ProgramConfig& config, ParseError& err) + { + bool async_enabled = false; + if (!read_bool(params, "async", async_enabled, err)) + { + return false; + } + config.global.hasAsync = async_enabled ? std::launch::async : std::launch::deferred; + return true; + } + + static bool apply_ipc_field(const json& params, ctrace::ProgramConfig& config, ParseError& err) + { + std::string ipc_value; + + if (!read_string(params, "ipc", ipc_value, err)) + { + return false; + } + if (ipc_value.empty()) + { + return true; + } + if (ipc_value == "serv" || ipc_value == "server") + { + ipc_value = "serve"; + } + + const auto& ipc_list = ctrace_defs::IPC_TYPES; + if (std::find(ipc_list.begin(), ipc_list.end(), ipc_value) == ipc_list.end()) + { + err = { + "InvalidParams", + "Invalid IPC type: '" + ipc_value + "'. Available IPC types: [" + join_with_comma(ipc_list) + "]" + }; + return false; + } + if (ipc_value == "serve") + { + config.global.ipc = "standardIO"; + return true; + } + + config.global.ipc = ipc_value; + return true; + } + + static bool build_config_from_params(const json& params, ctrace::ProgramConfig& config, ParseError& err) + { + if (!params.is_object()) + { + err = {"InvalidParams", "Params must be a JSON object."}; + return false; + } + + if (!apply_bool_fields(params, err, { + {"verbose", &config.global.verbose}, + {"sarif_format", &config.global.hasSarifFormat}, + {"static_analysis", &config.global.hasStaticAnalysis}, + {"dynamic_analysis", &config.global.hasDynamicAnalysis}, + })) + { + return false; + } + if (!apply_async_field(params, config, err)) + { + return false; + } + if (!apply_string_fields(params, err, { + {"report_file", &config.global.report_file}, + {"output_file", &config.global.output_file}, + {"ipc_path", &config.global.ipcPath}, + })) + { + return false; + } + if (!apply_list_param(params, "entry_points", err, [&](const std::vector& values) { + config.global.entry_points = join_with_comma(values); + })) + { + return false; + } + if (!apply_list_param(params, "invoke", err, [&](const std::vector& values) { + config.global.hasInvokedSpecificTools = true; + config.global.specificTools = values; + })) + { + return false; + } + if (!apply_list_param(params, "input", err, [&](const std::vector& values) { + for (const auto& file : values) + { + if (!file.empty()) + { + config.addFile(file); + } + } + })) + { + return false; + } + if (!apply_ipc_field(params, config, err)) + { + return false; + } + + return true; + } + + static bool run_analysis(const ctrace::ProgramConfig& config, ILogger& logger, json& result, ParseError& err) + { + if (config.files.empty()) + { + err = {"MissingInput", "Input files are required for analysis."}; + return false; + } + if (!config.global.hasStaticAnalysis && !config.global.hasDynamicAnalysis && !config.global.hasInvokedSpecificTools) + { + err = {"NoAnalysisSelected", "Enable static_analysis, dynamic_analysis, or invoke tools."}; + return false; + } + + unsigned int threads = std::thread::hardware_concurrency(); + if (threads == 0) + { + threads = 1; + } + if (threads > 255) + { + threads = 255; + } + const uint8_t pool_size = static_cast(threads); + auto output_capture = std::make_shared(); + ctrace::ToolInvoker invoker(config, pool_size, config.global.hasAsync, output_capture); + + size_t processed = 0; + for (const auto& file : config.files) + { + if (file.src_file.empty()) + { + continue; + } + ++processed; + if (config.global.hasStaticAnalysis) + { + invoker.runStaticTools(file.src_file); + } + if (config.global.hasDynamicAnalysis) + { + invoker.runDynamicTools(file.src_file); + } + if (config.global.hasInvokedSpecificTools) + { + invoker.runSpecificTools(config.global.specificTools, file.src_file); + } + } + + if (processed == 0) + { + err = {"MissingInput", "Input files are required for analysis."}; + return false; + } + + logger.info("Analysis completed for " + std::to_string(processed) + " file(s)."); + + result["files"] = processed; + result["static_analysis"] = config.global.hasStaticAnalysis; + result["dynamic_analysis"] = config.global.hasDynamicAnalysis; + result["invoked_tools"] = config.global.specificTools; + result["sarif_format"] = config.global.hasSarifFormat; + result["report_file"] = config.global.report_file; + if (output_capture) + { + json outputs = json::object(); + const auto snapshot = output_capture->snapshot(); + for (const auto& [tool, lines] : snapshot) + { + json entries = json::array(); + for (const auto& line : lines) + { + json entry; + entry["stream"] = line.stream; + + const auto& message = line.message; + const auto first_non_space = message.find_first_not_of(" \t\n\r"); + if (first_non_space != std::string::npos && + (message[first_non_space] == '{' || message[first_non_space] == '[')) + { + json parsed = json::parse(message, nullptr, false); + if (!parsed.is_discarded()) + { + entry["message"] = parsed; + } + else + { + entry["message"] = message; + } + } + else + { + entry["message"] = message; + } + + entries.push_back(entry); + } + outputs[tool] = entries; + } + result["outputs"] = outputs; + } + return true; + } + + json handle_run_analysis(json& baseResponse, const json& params) + { + ctrace::ProgramConfig config; + ParseError err; + + if (!build_config_from_params(params, config, err)) + { + baseResponse["status"] = "error"; + baseResponse["error"] = { + {"code", err.code}, + {"message", err.message} + }; + return baseResponse; + } + + json result; + if (!run_analysis(config, logger_, result, err)) + { + baseResponse["status"] = "error"; + baseResponse["error"] = { + {"code", err.code}, + {"message", err.message} + }; + return baseResponse; + } + + baseResponse["status"] = "ok"; + baseResponse["result"] = result; + return baseResponse; + } +}; + +// ============================================================================ +// Couche HTTP / Transport +// ============================================================================ + +class HttpServer +{ + public: + HttpServer(ApiHandler& apiHandler, ILogger& logger) + : apiHandler_(apiHandler), logger_(logger) + {} + + void run(const std::string& host, int port) + { + // CORS + server_.Options("/api", [this](const httplib::Request&, httplib::Response& res) { + set_cors(res); + res.status = 200; + }); + + // Endpoint principal + server_.Post("/api", [this](const httplib::Request& req, httplib::Response& res) { + set_cors(res); + handle_post_api(req, res); + }); + + logger_.info("[SERVER] Listening on http://" + host + ":" + std::to_string(port)); + server_.listen(host.c_str(), port); + } + + private: + httplib::Server server_; + ApiHandler& apiHandler_; + ILogger& logger_; + + static void set_cors(httplib::Response& res) + { + res.set_header("Access-Control-Allow-Origin", "*"); + res.set_header("Access-Control-Allow-Methods", "POST, OPTIONS"); + res.set_header("Access-Control-Allow-Headers", "Content-Type"); + } + + void handle_post_api(const httplib::Request& req, httplib::Response& res) + { + try { + json request = json::parse(req.body); + json response = apiHandler_.handle_request(request); + + res.status = 200; + res.set_content(response.dump(), "application/json"); + } catch (const std::exception& e) { + logger_.error(std::string("Exception while handling /api: ") + e.what()); + + json err; + err["proto"] = "coretrace-1.0"; + err["type"] = "response"; + err["status"] = "error"; + err["error"] = { + {"code", "InvalidRequest"}, + {"message", e.what()} + }; + + res.status = 400; + res.set_content(err.dump(), "application/json"); + } + } +}; diff --git a/include/Process/Ipc/IpcStrategy.hpp b/include/Process/Ipc/IpcStrategy.hpp index c84b8c8..49eb9b1 100644 --- a/include/Process/Ipc/IpcStrategy.hpp +++ b/include/Process/Ipc/IpcStrategy.hpp @@ -3,7 +3,6 @@ #include -#include "../Tools/IAnalysisTools.hpp" #include "../ProcessFactory.hpp" #include "../ThreadProcess.hpp" @@ -67,7 +66,7 @@ class UnixSocketStrategy : public IpcStrategy if (sock != -1) { ::close(sock); sock = -1; - unlink(path.c_str()); // Optionnel + // unlink(path.c_str()); // Optionnel } } }; diff --git a/include/Process/LinuxProcess.hpp b/include/Process/LinuxProcess.hpp index a1216dc..ff574ae 100644 --- a/include/Process/LinuxProcess.hpp +++ b/include/Process/LinuxProcess.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "Process.hpp" #include "ThreadProcess.hpp" @@ -24,15 +25,27 @@ class UnixProcess : public Process { prepareArguments(); } - void run() override { + void run() override + { ctrace::Thread::Output::cout("Running Unix/Linux process"); + char log_template[] = "/tmp/ctrace_process_log_XXXXXX"; + int logFd = mkstemp(log_template); + if (logFd == -1) + { + throw std::runtime_error("Failed to create log file"); + } + log_path_ = log_template; + pid_t pid = fork(); - if (pid == -1) { + if (pid == -1) + { + close(logFd); throw std::runtime_error("Failed to fork process"); } - if (pid == 0) { + if (pid == 0) + { std::vector execArgs; execArgs.push_back(const_cast(m_command.c_str())); for (auto& arg : m_arguments) { @@ -40,10 +53,6 @@ class UnixProcess : public Process { } execArgs.push_back(nullptr); - int logFd = open("process.log", O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (logFd == -1) { - throw std::runtime_error("Failed to open log file"); - } dup2(logFd, STDOUT_FILENO); dup2(logFd, STDERR_FILENO); close(logFd); @@ -51,40 +60,57 @@ class UnixProcess : public Process { execvp(m_command.c_str(), execArgs.data()); _exit(EXIT_FAILURE); } else { + close(logFd); int status; waitpid(pid, &status, 0); captureLogs(); } } - void cleanup() override { + void cleanup() override + { ctrace::Thread::Output::cout("Cleaning up Unix/Linux process"); - // unlink("process.log"); + if (!log_path_.empty()) + { + unlink(log_path_.c_str()); + log_path_.clear(); + } } - void prepareArguments() override { + void prepareArguments() override + { ctrace::Thread::Output::cout("Preparing arguments for Unix/Linux"); // m_arguments.clear(); // m_arguments.push_back("-l"); } - void captureLogs() override { - ctrace::Thread::Output::cout("Capturing logs from process.log"); - FILE* logFile = fopen("process.log", "r"); - if (!logFile) { + void captureLogs() override + { + ctrace::Thread::Output::cout("Capturing logs from process file"); + if (log_path_.empty()) + { + return; + } + FILE* logFile = fopen(log_path_.c_str(), "r"); + if (!logFile) + { throw std::runtime_error("Failed to open log file"); } char buffer[1024]; logOutput.clear(); - while (fgets(buffer, sizeof(buffer), logFile)) { + while (fgets(buffer, sizeof(buffer), logFile)) + { logOutput += buffer; } fclose(logFile); + unlink(log_path_.c_str()); + log_path_.clear(); // std::cout << "Logs captured: " << logOutput << "\n"; } private: std::string m_command; + std::string log_path_; }; diff --git a/include/Process/ThreadProcess.hpp b/include/Process/ThreadProcess.hpp index 956bec8..131a702 100644 --- a/include/Process/ThreadProcess.hpp +++ b/include/Process/ThreadProcess.hpp @@ -1,21 +1,116 @@ #ifndef THREAD_PROCESS_HPP #define THREAD_PROCESS_HPP +#include +#include +#include +#include +#include +#include + namespace ctrace { namespace Thread { namespace Output { - static std::mutex cout_mutex; + struct CapturedLine + { + std::string stream; + std::string message; + }; + + class CaptureBuffer + { + public: + void append(const std::string& tool, const std::string& stream, const std::string& message) + { + std::lock_guard lock(mutex_); + lines_[tool].push_back({stream, message}); + } + + std::unordered_map> snapshot() const + { + std::lock_guard lock(mutex_); + return lines_; + } + + private: + mutable std::mutex mutex_; + std::unordered_map> lines_; + }; + + struct CaptureContext + { + std::shared_ptr buffer; + std::string tool; + bool mirror_to_console = true; + }; + + inline std::mutex cout_mutex; + inline thread_local const CaptureContext* capture_context = nullptr; + + class ScopedCapture + { + public: + explicit ScopedCapture(const CaptureContext* ctx) + : previous_(capture_context), active_(ctx != nullptr) + { + if (active_) + { + capture_context = ctx; + } + } + + ~ScopedCapture() + { + if (active_) + { + capture_context = previous_; + } + } + + private: + const CaptureContext* previous_; + bool active_; + }; - static void cout(const std::string& message) { + static void emit_line(const std::string& stream, + const std::string& message, + std::ostream& target, + bool capture_line) + { + const CaptureContext* ctx = capture_context; + if (capture_line && ctx && ctx->buffer) + { + ctx->buffer->append(ctx->tool, stream, message); + if (!ctx->mirror_to_console) + { + return; + } + } std::lock_guard lock(cout_mutex); - std::cout << message << std::endl; + target << message << std::endl; } - static void cerr(const std::string& message) { - std::lock_guard lock(cout_mutex); - std::cerr << message << std::endl; + + static void cout(const std::string& message) + { + emit_line("stdout", message, std::cout, false); + } + + static void cerr(const std::string& message) + { + emit_line("stderr", message, std::cerr, false); + } + + static void tool_out(const std::string& message) + { + emit_line("stdout", message, std::cout, true); + } + + static void tool_err(const std::string& message) + { + emit_line("stderr", message, std::cerr, true); } } } diff --git a/include/Process/Tools/AnalysisTools.hpp b/include/Process/Tools/AnalysisTools.hpp index 0be37e4..10f6bb2 100644 --- a/include/Process/Tools/AnalysisTools.hpp +++ b/include/Process/Tools/AnalysisTools.hpp @@ -131,10 +131,10 @@ class IkosToolImplementation : public AnalysisToolBase auto process = ProcessFactory::createProcess("./ikos/src/ikos-build/bin/ikos", argsProcess); // ou "cmd.exe" pour Windows // std::this_thread::sleep_for(std::chrono::seconds(5)); process->execute(); - ctrace::Thread::Output::cout(process->logOutput); + ctrace::Thread::Output::tool_out(process->logOutput); } catch (const std::exception& e) { - ctrace::Thread::Output::cout("Error: " + std::string(e.what())); + ctrace::Thread::Output::tool_err("Error: " + std::string(e.what())); // return 1; } } @@ -180,7 +180,7 @@ class FlawfinderToolImplementation : public AnalysisToolBase if (config.global.ipc == "standardIO") { - ctrace::Thread::Output::cout(process->logOutput); + ctrace::Thread::Output::tool_out(process->logOutput); } else { @@ -188,7 +188,7 @@ class FlawfinderToolImplementation : public AnalysisToolBase } } catch (const std::exception& e) { - ctrace::Thread::Output::cout("Error: " + std::string(e.what())); + ctrace::Thread::Output::tool_err("Error: " + std::string(e.what())); return; } } @@ -233,9 +233,9 @@ class CppCheckToolImplementation : public AnalysisToolBase auto process = ProcessFactory::createProcess("/opt/homebrew/bin/cppcheck", argsProcess); // ou "cmd.exe" pour Windows process->execute(); - ctrace::Thread::Output::cout(process->logOutput); + ctrace::Thread::Output::tool_out(process->logOutput); } catch (const std::exception& e) { - ctrace::Thread::Output::cout("Error: " + std::string(e.what())); + ctrace::Thread::Output::tool_err("Error: " + std::string(e.what())); return; } } diff --git a/include/Process/Tools/AnalysisToolsBase.hpp b/include/Process/Tools/AnalysisToolsBase.hpp index 8a90adb..025e457 100644 --- a/include/Process/Tools/AnalysisToolsBase.hpp +++ b/include/Process/Tools/AnalysisToolsBase.hpp @@ -1,4 +1,5 @@ #include "IAnalysisTools.hpp" +#include "../Ipc/IpcStrategy.hpp" namespace ctrace { @@ -13,4 +14,4 @@ namespace ctrace } }; -} \ No newline at end of file +} diff --git a/include/Process/Tools/IAnalysisTools.hpp b/include/Process/Tools/IAnalysisTools.hpp index 12190e3..be78fa8 100644 --- a/include/Process/Tools/IAnalysisTools.hpp +++ b/include/Process/Tools/IAnalysisTools.hpp @@ -2,8 +2,12 @@ #define IANALYSISTOOLS_HPP #include +#include +#include + #include "Config/config.hpp" -#include "../Ipc/IpcStrategy.hpp" + +class IpcStrategy; namespace ctrace { diff --git a/include/Process/Tools/ToolsInvoker.hpp b/include/Process/Tools/ToolsInvoker.hpp index 6819835..9bc4a21 100644 --- a/include/Process/Tools/ToolsInvoker.hpp +++ b/include/Process/Tools/ToolsInvoker.hpp @@ -7,6 +7,7 @@ #include #include +#include "Process/Ipc/IpcStrategy.hpp" #include "AnalysisTools.hpp" #include @@ -18,8 +19,6 @@ #include #include -std::mutex queueMutex; - class ThreadPool { public: ThreadPool(size_t numThreads) @@ -82,8 +81,14 @@ namespace ctrace class ToolInvoker { public: - ToolInvoker(ctrace::ProgramConfig config, uint8_t nbThreadPool, std::launch policy) - : m_config(config), m_nbThreadPool(nbThreadPool), m_policy(policy) + ToolInvoker(ctrace::ProgramConfig config, + uint8_t nbThreadPool, + std::launch policy, + std::shared_ptr output_capture = nullptr) + : m_config(config), + m_nbThreadPool(nbThreadPool), + m_policy(policy), + m_output_capture(output_capture) { std::cout << "\033[36mInitializing ToolInvoker...\033[0m\n"; @@ -123,8 +128,11 @@ class ToolInvoker { // tools.at(tool_name)->execute(file, m_config); results.push_back(std::async( m_policy, - [&tool = tools.at(tool_name), file, this]() + [this, tool_name, file]() { + auto& tool = tools.at(tool_name); + ctrace::Thread::Output::CaptureContext ctx{m_output_capture, tool_name, true}; + ctrace::Thread::Output::ScopedCapture capture(m_output_capture ? &ctx : nullptr); return tool->execute(file, m_config); } )); @@ -140,7 +148,10 @@ class ToolInvoker { { for (const auto& tool_name : dynamic_tools) { - tools.at(tool_name)->execute(file, m_config); + auto& tool = tools.at(tool_name); + ctrace::Thread::Output::CaptureContext ctx{m_output_capture, tool_name, true}; + ctrace::Thread::Output::ScopedCapture capture(m_output_capture ? &ctx : nullptr); + tool->execute(file, m_config); } } @@ -151,10 +162,15 @@ class ToolInvoker { { if (tools.count(name)) { - tools.at(name)->execute(file, m_config); + auto& tool = tools.at(name); + ctrace::Thread::Output::CaptureContext ctx{m_output_capture, name, true}; + ctrace::Thread::Output::ScopedCapture capture(m_output_capture ? &ctx : nullptr); + tool->execute(file, m_config); } else { + ctrace::Thread::Output::CaptureContext ctx{m_output_capture, name, true}; + ctrace::Thread::Output::ScopedCapture capture(m_output_capture ? &ctx : nullptr); ctrace::Thread::Output::cerr("\033[31mUnknown tool: " + name + "\033[0m"); } } @@ -168,6 +184,7 @@ class ToolInvoker { uint8_t m_nbThreadPool; std::launch m_policy; std::shared_ptr m_ipc; + std::shared_ptr m_output_capture; }; } diff --git a/include/attributes.hpp b/include/attributes.hpp new file mode 100644 index 0000000..5d2c75a --- /dev/null +++ b/include/attributes.hpp @@ -0,0 +1,20 @@ +#ifndef COMPILERLIB_ATTRIBUTES_HPP +#define COMPILERLIB_ATTRIBUTES_HPP + +#if defined(__cplusplus) +# if defined(__has_cpp_attribute) +# if __has_cpp_attribute(nodiscard) +# define CT_NODISCARD [[nodiscard]] +# endif +# endif +#endif + +#ifndef CT_NODISCARD +# if defined(__GNUC__) || defined(__clang__) +# define CT_NODISCARD __attribute__((warn_unused_result)) +# else +# define CT_NODISCARD +# endif +#endif + +#endif // COMPILERLIB_ATTRIBUTES_HPP diff --git a/include/ctrace_defs/types.hpp b/include/ctrace_defs/types.hpp index 722608d..38d59de 100644 --- a/include/ctrace_defs/types.hpp +++ b/include/ctrace_defs/types.hpp @@ -21,6 +21,7 @@ namespace ctrace_defs inline const std::vector IPC_TYPES = { "standardIO", // default "socket", + "serve", // "pipe" // future implementation }; diff --git a/main.cpp b/main.cpp index c11da8d..143cc30 100644 --- a/main.cpp +++ b/main.cpp @@ -1,230 +1,20 @@ -#include "ArgumentParser/ArgumentManager.hpp" -#include "ArgumentParser/ArgumentParserFactory.hpp" -#include "Process/ProcessFactory.hpp" -#include "Process/Tools/ToolsInvoker.hpp" -#include "Config/config.hpp" - -#include "ctrace_tools/languageType.hpp" +#include "App/Config.hpp" +#include "App/Runner.hpp" #include "ctrace_tools/colors.hpp" #include -#include -#include -#include -#include -#include - -#include -#include - -using json = nlohmann::json; - - -namespace ctrace -{ - ProgramConfig buildConfig(int argc, char *argv[]) - { - auto parser = createArgumentParser(); - ArgumentManager argManager(std::move(parser)); - ProgramConfig config; - - // TODO : lvl verbosity - argManager.addOption("--verbose", false, 'v'); - argManager.addFlag("--help", 'h'); - argManager.addOption("--output", true, 'o'); - argManager.addOption("--invoke", true, 'i'); - argManager.addOption("--sarif-format", false, 'f'); - argManager.addOption("--input", true, 's'); - argManager.addOption("--static", false, 'x'); - argManager.addOption("--dyn", false, 'd'); - argManager.addOption("--entry-points", true, 'e'); - argManager.addOption("--report-file", true, 'r'); - argManager.addOption("--async", false, 'a'); - argManager.addOption("--ipc", true, 'p'); - argManager.addOption("--ipc-path", true, 't'); - - // Parsing des arguments - argManager.parse(argc, argv); - - // Traitement avec Command - ConfigProcessor processor(config); - processor.process(argManager); - // processor.execute(argManager); - - if (argManager.hasNoOption()) - { - processor.execute("--help", ""); - std::exit(0); - } - - return config; - } - - std::vector resolveSourceFiles(const ProgramConfig& config) - { - std::vector sourceFiles; - sourceFiles.reserve(config.files.size()); - - const auto appendResolved = [&](const std::string& candidate, - const std::filesystem::path& baseDir) -> bool - { - if (candidate.empty()) - { - return false; - } - std::filesystem::path resolved(candidate); - if (resolved.is_relative() && !baseDir.empty()) - { - resolved = baseDir / resolved; - } - resolved = resolved.lexically_normal(); - sourceFiles.emplace_back(resolved.string()); - return true; - }; - - for (const auto& fileConfig : config.files) - { - const std::string& entry = fileConfig.src_file; - - bool expanded = false; - if (!entry.empty() && (entry.ends_with(".json") || entry.ends_with(".JSON"))) - { - std::ifstream manifestStream(entry); - if (manifestStream.is_open()) - { - std::ostringstream buffer; - buffer << manifestStream.rdbuf(); - - const auto manifest = json::parse(buffer.str(), nullptr, false); - if (!manifest.is_discarded()) - { - const std::filesystem::path manifestDir = std::filesystem::path(entry).parent_path(); - - const auto appendFromArray = [&](const json& arr) -> bool - { - bool appended = false; - for (const auto& item : arr) - { - if (item.is_string()) - { - appended |= appendResolved(item.get(), manifestDir); - } - else if (item.is_object()) - { - if (const auto it = item.find("file"); it != item.end() && it->is_string()) - { - appended |= appendResolved(it->get(), manifestDir); - } - else if (const auto itSrc = item.find("src_file"); itSrc != item.end() && itSrc->is_string()) - { - appended |= appendResolved(itSrc->get(), manifestDir); - } - else if (const auto itPath = item.find("path"); itPath != item.end() && itPath->is_string()) - { - appended |= appendResolved(itPath->get(), manifestDir); - } - } - } - return appended; - }; - - if (manifest.is_array()) - { - expanded = appendFromArray(manifest); - } - else if (manifest.is_object()) - { - if (const auto it = manifest.find("files"); it != manifest.end() && it->is_array()) - { - expanded = appendFromArray(*it); - } - else if (const auto it = manifest.find("sources"); it != manifest.end() && it->is_array()) - { - expanded = appendFromArray(*it); - } - else if (const auto it = manifest.find("compile_commands"); it != manifest.end() && it->is_array()) - { - expanded = appendFromArray(*it); - } - else if (const auto itFile = manifest.find("file"); itFile != manifest.end() && itFile->is_string()) - { - expanded = appendResolved(itFile->get(), manifestDir); - } - else if (const auto itSrc = manifest.find("src_file"); itSrc != manifest.end() && itSrc->is_string()) - { - expanded = appendResolved(itSrc->get(), manifestDir); - } - else if (const auto itPath = manifest.find("path"); itPath != manifest.end() && itPath->is_string()) - { - expanded = appendResolved(itPath->get(), manifestDir); - } - } - } - } - } - - if (!expanded) - { - sourceFiles.emplace_back(entry); - } - } - - return sourceFiles; - } -} int main(int argc, char *argv[]) { ctrace::ProgramConfig config = ctrace::buildConfig(argc, argv); - ctrace::ToolInvoker invoker(config, std::thread::hardware_concurrency(), config.global.hasAsync); - std::cout << ctrace::Color::CYAN << "asynchronous execution: " - << (config.global.hasAsync == std::launch::async ? ctrace::Color::GREEN : ctrace::Color::RED) - << (config.global.hasAsync == std::launch::async ? "enabled" : "disabled") + std::cout << ctrace::Color::GREEN + << "CoreTrace - Comprehensive Tracing and Analysis Tool" << ctrace::Color::RESET << std::endl; - std::cout << ctrace::Color::CYAN << "verbose: " - << (config.global.verbose ? ctrace::Color::GREEN : ctrace::Color::RED) - << config.global.verbose << ctrace::Color::RESET << std::endl; - - std::cout << ctrace::Color::CYAN << "sarif format: " - << (config.global.hasSarifFormat ? ctrace::Color::GREEN : ctrace::Color::RED) - << config.global.hasSarifFormat << ctrace::Color::RESET << std::endl; - - std::cout << ctrace::Color::CYAN << "dynamic analysis: " - << (config.global.hasDynamicAnalysis ? ctrace::Color::GREEN : ctrace::Color::RED) - << config.global.hasDynamicAnalysis << ctrace::Color::RESET << std::endl; - - std::cout << ctrace::Color::CYAN << "Report file: " - << ctrace::Color::YELLOW << config.global.report_file - << ctrace::Color::RESET << std::endl; - - std::cout << ctrace::Color::CYAN << "entry point: " - << ctrace::Color::YELLOW << config.global.entry_points - << ctrace::Color::RESET << std::endl; - - std::vector sourceFiles = ctrace::resolveSourceFiles(config); - - for (const auto& file : sourceFiles) + if (config.global.ipc == "serve") { - std::cout << ctrace::Color::CYAN << "File: " - << ctrace::Color::YELLOW << file << ctrace::Color::RESET << std::endl; - - if (config.global.hasStaticAnalysis) - { - std::cout << ctrace::Color::CYAN << "Running static analysis..." << ctrace::Color::RESET << std::endl; - invoker.runStaticTools(file); - } - if (config.global.hasDynamicAnalysis) - { - std::cout << ctrace::Color::CYAN << "Running dynamic analysis..." << ctrace::Color::RESET << std::endl; - invoker.runDynamicTools(file); - } - if (config.global.hasInvokedSpecificTools) - { - std::cout << ctrace::Color::CYAN << "Running specific tools..." << ctrace::Color::RESET << std::endl; - invoker.runSpecificTools(config.global.specificTools, file); - } + return ctrace::run_server(config); } - return 0; + return ctrace::run_cli_analysis(config); } diff --git a/src/App/Config.cpp b/src/App/Config.cpp new file mode 100644 index 0000000..19f54fc --- /dev/null +++ b/src/App/Config.cpp @@ -0,0 +1,59 @@ +#include "App/Config.hpp" + +#include "ArgumentParser/ArgumentManager.hpp" +#include "ArgumentParser/ArgumentParserFactory.hpp" + +#include +#include + +namespace ctrace +{ + CT_NODISCARD ProgramConfig buildConfig(int argc, char *argv[]) + { + auto parser = createArgumentParser(); + ArgumentManager argManager(std::move(parser)); + ProgramConfig config; + + // TODO : lvl verbosity + argManager.addOption("--verbose", false, 'v'); + argManager.addFlag("--help", 'h'); + argManager.addOption("--output", true, 'o'); + argManager.addOption("--invoke", true, 'i'); + argManager.addOption("--sarif-format", false, 'f'); + argManager.addOption("--input", true, 's'); + argManager.addOption("--static", false, 'x'); + argManager.addOption("--dyn", false, 'd'); + argManager.addOption("--entry-points", true, 'e'); + argManager.addOption("--report-file", true, 'r'); + argManager.addOption("--async", false, 'a'); + argManager.addOption("--ipc", true, 'p'); + argManager.addOption("--ipc-path", true, 't'); + argManager.addOption("--serve-host", true, 'z'); + argManager.addOption("--serve-port", true, 'y'); + + // Parsing des arguments + argManager.parse(argc, argv); + + // Traitement avec Command + ConfigProcessor processor(config); + processor.process(argManager); + // processor.execute(argManager); + + if (argManager.hasNoOption()) + { + processor.execute("--help", ""); + std::exit(0); + } + if (!(argManager.getOptionValue("--ipc") == "serve") + && (argManager.hasOption("--serve-host") + || argManager.hasOption("--serve-port")) + ) + { + std::cout << "[INFO] UNCONSISTENT SERVER OPTIONS: --serve-host or --serve-port needed --ipc=server." + << std::endl; + std::exit(1); + } + + return config; + } +} diff --git a/src/App/Files.cpp b/src/App/Files.cpp new file mode 100644 index 0000000..a052914 --- /dev/null +++ b/src/App/Files.cpp @@ -0,0 +1,129 @@ +#include "App/Files.hpp" + +#include +#include +#include +#include + +namespace ctrace +{ + CT_NODISCARD std::vector resolveSourceFiles(const ProgramConfig& config) + { + using json = nlohmann::json; + + std::vector sourceFiles; + sourceFiles.reserve(config.files.size()); + + const auto appendResolved = [&](const std::string& candidate, + const std::filesystem::path& baseDir) -> bool + { + if (candidate.empty()) + { + return false; + } + std::filesystem::path resolved(candidate); + if (resolved.is_relative() && !baseDir.empty()) + { + resolved = baseDir / resolved; + } + resolved = resolved.lexically_normal(); + sourceFiles.emplace_back(resolved.string()); + return true; + }; + + for (const auto& fileConfig : config.files) + { + const std::string& entry = fileConfig.src_file; + + bool expanded = false; + if (!entry.empty() && (entry.ends_with(".json") || entry.ends_with(".JSON"))) + { + std::ifstream manifestStream(entry); + if (manifestStream.is_open()) + { + std::ostringstream buffer; + buffer << manifestStream.rdbuf(); + + const auto manifest = json::parse(buffer.str(), nullptr, false); + if (!manifest.is_discarded()) + { + const std::filesystem::path manifestDir = std::filesystem::path(entry).parent_path(); + + const auto appendFromArray = [&](const json& arr) -> bool + { + bool appended = false; + for (const auto& item : arr) + { + if (item.is_string()) + { + appended |= appendResolved(item.get(), manifestDir); + } + else if (item.is_object()) + { + if (const auto it = item.find("file"); it != item.end() && it->is_string()) + { + appended |= appendResolved(it->get(), manifestDir); + } + else if (const auto itSrc = item.find("src_file"); itSrc != item.end() + && itSrc->is_string()) + { + appended |= appendResolved(itSrc->get(), manifestDir); + } + else if (const auto itPath = item.find("path"); itPath != item.end() + && itPath->is_string()) + { + appended |= appendResolved(itPath->get(), manifestDir); + } + } + } + return appended; + }; + + if (manifest.is_array()) + { + expanded = appendFromArray(manifest); + } + else if (manifest.is_object()) + { + if (const auto it = manifest.find("files"); it != manifest.end() && it->is_array()) + { + expanded = appendFromArray(*it); + } + else if (const auto it = manifest.find("sources"); it != manifest.end() && it->is_array()) + { + expanded = appendFromArray(*it); + } + else if (const auto it = manifest.find("compile_commands"); it != manifest.end() + && it->is_array()) + { + expanded = appendFromArray(*it); + } + else if (const auto itFile = manifest.find("file"); itFile != manifest.end() + && itFile->is_string()) + { + expanded = appendResolved(itFile->get(), manifestDir); + } + else if (const auto itSrc = manifest.find("src_file"); itSrc != manifest.end() + && itSrc->is_string()) + { + expanded = appendResolved(itSrc->get(), manifestDir); + } + else if (const auto itPath = manifest.find("path"); itPath != manifest.end() + && itPath->is_string()) + { + expanded = appendResolved(itPath->get(), manifestDir); + } + } + } + } + } + + if (!expanded) + { + sourceFiles.emplace_back(entry); + } + } + + return sourceFiles; + } +} diff --git a/src/App/Runner.cpp b/src/App/Runner.cpp new file mode 100644 index 0000000..3efd169 --- /dev/null +++ b/src/App/Runner.cpp @@ -0,0 +1,80 @@ +#include "App/Runner.hpp" + +#include "App/Files.hpp" +#include "Process/Ipc/HttpServer.hpp" +#include "Process/Tools/ToolsInvoker.hpp" +#include "ctrace_tools/colors.hpp" + +#include +#include +#include + +namespace ctrace +{ + CT_NODISCARD int run_server(const ProgramConfig& config) + { + std::cout << "\033[36mStarting IPC server at " + << config.global.serverHost << ":" + << config.global.serverPort << "...\033[0m\n"; + ConsoleLogger logger; + ApiHandler apiHandler(logger); + HttpServer server(apiHandler, logger); + server.run(config.global.serverHost, config.global.serverPort); + return EXIT_SUCCESS; + } + + CT_NODISCARD int run_cli_analysis(const ProgramConfig& config) + { + ctrace::ToolInvoker invoker(config, std::thread::hardware_concurrency(), config.global.hasAsync); + + std::cout << ctrace::Color::CYAN << "asynchronous execution: " + << (config.global.hasAsync == std::launch::async ? ctrace::Color::GREEN : ctrace::Color::RED) + << (config.global.hasAsync == std::launch::async ? "enabled" : "disabled") + << ctrace::Color::RESET << std::endl; + + std::cout << ctrace::Color::CYAN << "verbose: " + << (config.global.verbose ? ctrace::Color::GREEN : ctrace::Color::RED) + << config.global.verbose << ctrace::Color::RESET << std::endl; + + std::cout << ctrace::Color::CYAN << "sarif format: " + << (config.global.hasSarifFormat ? ctrace::Color::GREEN : ctrace::Color::RED) + << config.global.hasSarifFormat << ctrace::Color::RESET << std::endl; + + std::cout << ctrace::Color::CYAN << "dynamic analysis: " + << (config.global.hasDynamicAnalysis ? ctrace::Color::GREEN : ctrace::Color::RED) + << config.global.hasDynamicAnalysis << ctrace::Color::RESET << std::endl; + + std::cout << ctrace::Color::CYAN << "Report file: " + << ctrace::Color::YELLOW << config.global.report_file + << ctrace::Color::RESET << std::endl; + + std::cout << ctrace::Color::CYAN << "entry point: " + << ctrace::Color::YELLOW << config.global.entry_points + << ctrace::Color::RESET << std::endl; + + std::vector sourceFiles = ctrace::resolveSourceFiles(config); + + for (const auto& file : sourceFiles) + { + std::cout << ctrace::Color::CYAN << "File: " + << ctrace::Color::YELLOW << file << ctrace::Color::RESET << std::endl; + + if (config.global.hasStaticAnalysis) + { + std::cout << ctrace::Color::CYAN << "Running static analysis..." << ctrace::Color::RESET << std::endl; + invoker.runStaticTools(file); + } + if (config.global.hasDynamicAnalysis) + { + std::cout << ctrace::Color::CYAN << "Running dynamic analysis..." << ctrace::Color::RESET << std::endl; + invoker.runDynamicTools(file); + } + if (config.global.hasInvokedSpecificTools) + { + std::cout << ctrace::Color::CYAN << "Running specific tools..." << ctrace::Color::RESET << std::endl; + invoker.runSpecificTools(config.global.specificTools, file); + } + } + return 0; + } +} diff --git a/src/Process/Tools/StackAnalyzerToolImplementation.cpp b/src/Process/Tools/StackAnalyzerToolImplementation.cpp index e520c9f..1fbaa24 100644 --- a/src/Process/Tools/StackAnalyzerToolImplementation.cpp +++ b/src/Process/Tools/StackAnalyzerToolImplementation.cpp @@ -20,7 +20,7 @@ void StackAnalyzerToolImplementation::execute(const std::string& file, ctrace::P { if (config.global.hasSarifFormat) { - ctrace::Thread::Output::cout(ctrace::stack::toJson(res, filename)); + ctrace::Thread::Output::tool_out(ctrace::stack::toJson(res, filename)); return; } if (res.functions.empty()) { diff --git a/src/Process/Tools/TscancodeToolImplementation.cpp b/src/Process/Tools/TscancodeToolImplementation.cpp index 3cbadeb..9b830e5 100644 --- a/src/Process/Tools/TscancodeToolImplementation.cpp +++ b/src/Process/Tools/TscancodeToolImplementation.cpp @@ -21,12 +21,12 @@ void TscancodeToolImplementation::execute(const std::string& file, ProgramConfig auto process = ProcessFactory::createProcess("./tscancode/src/tscancode/trunk/tscancode", argsProcess); process->execute(); - ctrace::Thread::Output::cout(process->logOutput); + ctrace::Thread::Output::tool_out(process->logOutput); ctrace::Thread::Output::cout("Finished tscancode on " + file); if (has_sarif_format) { - ctrace::Thread::Output::cout(sarifFormat(process->logOutput, "ccoretrace-sarif-tscancode.json").dump()); + ctrace::Thread::Output::tool_out(sarifFormat(process->logOutput, "ccoretrace-sarif-tscancode.json").dump()); } // if (has_json_format) // { @@ -36,7 +36,7 @@ void TscancodeToolImplementation::execute(const std::string& file, ProgramConfig } catch (const std::exception& e) { - ctrace::Thread::Output::cerr("Error: " + std::string(e.what())); + ctrace::Thread::Output::tool_err("Error: " + std::string(e.what())); return; } }