From b3b5cf99fc90a2bb2b15e20637a34940b7b57d05 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:34:34 +0100 Subject: [PATCH 01/23] build(cmake): add App module sources to target --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) 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) From 4adf3d70cbccb058852c0028061dd61006e49be5 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:35:07 +0100 Subject: [PATCH 02/23] fix(config): add standard headers and drop ipc dependency --- include/Config/config.hpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) 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; // }; From 53aa23a59cac319ca76c4bd67244e867177c0b35 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:35:48 +0100 Subject: [PATCH 03/23] fix(ipc): avoid unlinking socket path on client close --- include/Process/Ipc/IpcStrategy.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 } } }; From 7fafb8ae0c2f9e6d323b8f622cd0766cda55209a Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:36:14 +0100 Subject: [PATCH 04/23] fix(process): use per-run temp log file for UnixProcess --- include/Process/LinuxProcess.hpp | 56 +++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 15 deletions(-) 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_; }; From bc7e3d922f101823a65b7e9ac5b4ee9fcf25953c Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:36:51 +0100 Subject: [PATCH 05/23] feat(output): add per-tool capture buffer and tool_out/tool_err --- include/Process/ThreadProcess.hpp | 107 ++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 6 deletions(-) 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); } } } From 965f149371149fa3092b9894d7b5ffb4d7108bf8 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:37:56 +0100 Subject: [PATCH 06/23] refactor(tools): route tool outputs through tool_out/tool_err --- include/Process/Tools/AnalysisTools.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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; } } From d8a88e38188522cddede0d4849af51c0306fd1d5 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:38:24 +0100 Subject: [PATCH 07/23] fix(tools): include IpcStrategy definition --- include/Process/Tools/AnalysisToolsBase.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 +} From 01cf832b3fdb56454dc1fe491b51e0627b3b2668 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:38:58 +0100 Subject: [PATCH 08/23] refactor(tools): forward-declare IpcStrategy and add std includes --- include/Process/Tools/IAnalysisTools.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 { From f909a27e568e37374013ee86b1eb49a885a4640c Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:39:23 +0100 Subject: [PATCH 09/23] feat(invoker): capture per-tool output and drop global mutex --- include/Process/Tools/ToolsInvoker.hpp | 31 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) 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; }; } From f8cf92568f33964213299d437bd94a3d8ec6b6c9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:40:37 +0100 Subject: [PATCH 10/23] feat(ipc): add serve to IPC_TYPES --- include/ctrace_defs/types.hpp | 1 + 1 file changed, 1 insertion(+) 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 }; From abecde24b8838d928687af0d2966f768c0d0a369 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:41:00 +0100 Subject: [PATCH 11/23] refactor(main): delegate to App config/runner modules --- main.cpp | 224 ++----------------------------------------------------- 1 file changed, 7 insertions(+), 217 deletions(-) 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); } From 1db6cc4bc2a4b558b90fe601b7c0be673e1f1fef Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:41:29 +0100 Subject: [PATCH 12/23] refactor(stack-analyzer): emit tool output via tool_out --- src/Process/Tools/StackAnalyzerToolImplementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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()) { From fef1cdb0a1f4b54cd3625b980cf20cba3d4bd46d Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:41:50 +0100 Subject: [PATCH 13/23] refactor(tscancode): emit outputs via tool_out/tool_err --- src/Process/Tools/TscancodeToolImplementation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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; } } From c8d9c31b0f37c511a595203f0e21fb8846ed308c Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:43:48 +0100 Subject: [PATCH 14/23] build(cmake): add cpp-httplib FetchContent config --- cmake/httpLib.cmake | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 cmake/httpLib.cmake 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) From e1f31842e2b0fe4f42ccd35527efdb77684f20f1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:44:32 +0100 Subject: [PATCH 15/23] feat(ipc): add HTTP server for JSON API --- include/Process/Ipc/HttpServer.hpp | 555 +++++++++++++++++++++++++++++ 1 file changed, 555 insertions(+) create mode 100644 include/Process/Ipc/HttpServer.hpp 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"); + } + } +}; From 6716db976af74fb9a0e0b5fba25a0aab933ed785 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:45:27 +0100 Subject: [PATCH 16/23] chore(app): add config module header --- include/App/Config.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 include/App/Config.hpp 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 From e2b87823befee16b5496a50f0829d2b261677dac Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:46:07 +0100 Subject: [PATCH 17/23] chore(app): add files module header --- include/App/Files.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 include/App/Files.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 From 24b9a46bd958baa855271567823bd9dfc73faccc Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:46:29 +0100 Subject: [PATCH 18/23] chore(app): add runner module header --- include/App/Runner.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 include/App/Runner.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 From a2c92a006e348befd699b89b99447e90fc626136 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:46:45 +0100 Subject: [PATCH 19/23] chore(attrs): add CT_NODISCARD macro header --- include/attributes.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 include/attributes.hpp 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 From 3944f0a337252be9dbf5de76d0a407c815747914 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:47:10 +0100 Subject: [PATCH 20/23] feat(app): move CLI config parsing into App module --- src/App/Config.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/App/Config.cpp 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; + } +} From ba15ebb1a7dc6607883b51ecc740b25fc532b336 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:47:32 +0100 Subject: [PATCH 21/23] feat(app): move source file resolution into App module --- src/App/Files.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/App/Files.cpp 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; + } +} From a37f003c3f7f66cb1243b66cb59e0a1387d69c87 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:47:49 +0100 Subject: [PATCH 22/23] chore(app): add runner module header --- src/App/Runner.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/App/Runner.cpp 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; + } +} From 6a39975fe3e03472f709c5b6ec8b2daad858e96a Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 22 Jan 2026 01:50:25 +0100 Subject: [PATCH 23/23] docs(readme): update docs, examples and usage --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) 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++