From 37fdde8e2c93ea2b1bcdf505d08e93aaba5ca2ba Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Fri, 5 Dec 2025 00:42:22 +0100 Subject: [PATCH 1/7] Add CGQuery CLI and support Reachability analysis in CGQuery --- graph/include/ReachabilityAnalysis.h | 3 + graph/src/ReachabilityAnalysis.cpp | 13 +++- tools/CMakeLists.txt | 1 + tools/cgquery/CGQuery.cpp | 97 ++++++++++++++++++++++++++++ tools/cgquery/CMakeLists.txt | 5 ++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tools/cgquery/CGQuery.cpp create mode 100644 tools/cgquery/CMakeLists.txt diff --git a/graph/include/ReachabilityAnalysis.h b/graph/include/ReachabilityAnalysis.h index aad4dade..51cd70b1 100644 --- a/graph/include/ReachabilityAnalysis.h +++ b/graph/include/ReachabilityAnalysis.h @@ -29,6 +29,9 @@ class ReachabilityAnalysis { /** Compute if path exists between any two nodes in graph */ bool existsPathBetween(const CgNode* const src, const CgNode* const dest, bool forceUpdate = false); + /** Retrieve reachable nodes from any node **/ + const std::unordered_set& getReachableNodesFrom(const CgNode* const node, bool forceUpdate = false); + private: void runForNode(const CgNode* const n); diff --git a/graph/src/ReachabilityAnalysis.cpp b/graph/src/ReachabilityAnalysis.cpp index 688f529b..0e97b57b 100644 --- a/graph/src/ReachabilityAnalysis.cpp +++ b/graph/src/ReachabilityAnalysis.cpp @@ -65,4 +65,15 @@ bool ReachabilityAnalysis::existsPathBetween(const CgNode* const src, const CgNo return reachableSet.find(dest) != reachableSet.end(); } -} // namespace metacg::analysis \ No newline at end of file + +const std::unordered_set& ReachabilityAnalysis::getReachableNodesFrom(const CgNode* const node, bool forceUpdate) { + if (forceUpdate || computedFor.count(node) == 0) { + runForNode(node); + } + + auto it = reachableNodes.find(node); + assert(it != reachableNodes.end()); + return it->second; +} + +} // namespace metacg::analysis diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 811b801d..dcf82e8b 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(cgmerge2) add_subdirectory(cgconvert) add_subdirectory(cgformat) add_subdirectory(cgdiff) +add_subdirectory(cgquery) diff --git a/tools/cgquery/CGQuery.cpp b/tools/cgquery/CGQuery.cpp new file mode 100644 index 00000000..30ded670 --- /dev/null +++ b/tools/cgquery/CGQuery.cpp @@ -0,0 +1,97 @@ +#include "Callgraph.h" +#include "CgNode.h" +#include "CgTypes.h" +#include "ReachabilityAnalysis.h" +#include "io/MCGReader.h" +#include +#include + + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Usage: cgquery [options] \n"; + return 1; + } + + std::string command = argv[1]; + std::string cg_name = argv[argc-1]; + + if (command == "help" || command == "-h" || command == "--help") { + std::cout << "Usage: cgquery [options] \n"; + std::cout << "Available commands:\n"; + std::cout << " reaches Query reachable nodes or check reachability\n"; + return 0; + } + + if (argc < 3) { + std::cerr << "Need to specify command and input file." << std::endl; + std::cerr << "Use cgquery help for available commands." << std::endl; + return 1; + } + auto fs = metacg::io::FileSource(cg_name); + std::unique_ptr r1 = metacg::io::createReader(fs); + auto cg = r1->read(); + + // Build argv vector including program name for cxxopts + std::vector args; + args.push_back(argv[0]); // Program name + for (int i = 2; i < argc; ++i) { + args.push_back(argv[i]); + } + + if (command == "reaches") { + cxxopts::Options options("cgquery reaches", "Query reachable nodes or check if a node is reachable from a certain node"); + options.add_options() + ("s, source", "Start function. Lists all functions reachable from this node.", cxxopts::value()) + ("t, to", "If given, only checks whether this function is reachable from --source.", cxxopts::value()) + ("h,help", "Print help"); + + auto result = options.parse(static_cast(args.size()), args.data()); + + if (result.count("help")) { + std::cout << options.help() << "\n"; + return 0; + } + + if (!result.count("source")) { + std::cerr << "Please specify the source node" << std::endl; + std::cout << options.help() << std::endl; + return 2; + } + + + + auto* sourceNode = cg->getFirstNode(result["source"].as()); + if (!sourceNode) { + std::cerr << "Node '" << result["source"].as() << "' not found in CG.\n"; + return 1; + } + + metacg::analysis::ReachabilityAnalysis reachabilityAnalysis(cg.get()); + + if (result.count("to")) { + std::string toName = result["to"].as(); + + auto* toNode = cg->getFirstNode(toName); + if (!toNode) { + std::cerr << "Entry node '" << toName << "' not found in CG.\n"; + return 1; + } + + return reachabilityAnalysis.existsPathBetween(sourceNode, toNode, true) ? 0 : 1; + } + else { + auto reachableNodes = reachabilityAnalysis.getReachableNodesFrom(sourceNode, true); + std::cout << "Reachable nodes:\n"; + + for (const auto n : reachableNodes) { + std::cout << n->getFunctionName() << '\n'; + } + + return 0; + } + } + + return 0; +} + diff --git a/tools/cgquery/CMakeLists.txt b/tools/cgquery/CMakeLists.txt new file mode 100644 index 00000000..cb9f710a --- /dev/null +++ b/tools/cgquery/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(cgquery CGQuery.cpp) + +add_metacg(cgquery) +add_cxxopts(cgquery) + From c6d9f47875f8ba0a32145c7140b2eda52be91db0 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 18 Dec 2025 15:20:18 +0100 Subject: [PATCH 2/7] Add README --- tools/cgquery/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tools/cgquery/README.md diff --git a/tools/cgquery/README.md b/tools/cgquery/README.md new file mode 100644 index 00000000..6024f3b0 --- /dev/null +++ b/tools/cgquery/README.md @@ -0,0 +1,12 @@ +# CGQuery + +CGQuery is a command-line tool to perform various analyses on metacg call graphs. + + +## Usage + +``` +cgquery [options] +``` + +Use `cgquery help` to list available commands. From 501b3f26883a62946c10a6d69abf191bb38c3849 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 25 Dec 2025 02:19:43 +0100 Subject: [PATCH 3/7] Fix help options for commands --- tools/cgquery/CGQuery.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tools/cgquery/CGQuery.cpp b/tools/cgquery/CGQuery.cpp index 30ded670..0c28ad50 100644 --- a/tools/cgquery/CGQuery.cpp +++ b/tools/cgquery/CGQuery.cpp @@ -28,10 +28,6 @@ int main(int argc, char** argv) { std::cerr << "Use cgquery help for available commands." << std::endl; return 1; } - auto fs = metacg::io::FileSource(cg_name); - std::unique_ptr r1 = metacg::io::createReader(fs); - auto cg = r1->read(); - // Build argv vector including program name for cxxopts std::vector args; args.push_back(argv[0]); // Program name @@ -42,13 +38,18 @@ int main(int argc, char** argv) { if (command == "reaches") { cxxopts::Options options("cgquery reaches", "Query reachable nodes or check if a node is reachable from a certain node"); options.add_options() - ("s, source", "Start function. Lists all functions reachable from this node.", cxxopts::value()) - ("t, to", "If given, only checks whether this function is reachable from --source.", cxxopts::value()) - ("h,help", "Print help"); + ("s,source", "Start node", cxxopts::value()) + ("t,to", "Target node", cxxopts::value()) + ("h,help", "Print help") + ("input", "Call graph file", cxxopts::value()); + + options.parse_positional({"input"}); + options.positional_help(""); auto result = options.parse(static_cast(args.size()), args.data()); if (result.count("help")) { + std::cout << "help" << std::endl; std::cout << options.help() << "\n"; return 0; } @@ -59,6 +60,11 @@ int main(int argc, char** argv) { return 2; } + auto fs = metacg::io::FileSource(cg_name); + std::unique_ptr r1 = metacg::io::createReader(fs); + auto cg = r1->read(); + + auto* sourceNode = cg->getFirstNode(result["source"].as()); From 51ef9899f5f48601c607aa7547a0e9ec7aa732d6 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 25 Dec 2025 02:22:25 +0100 Subject: [PATCH 4/7] Enable support for node ids (v4) and warn if functioname is not unique --- tools/cgquery/CGQuery.cpp | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/tools/cgquery/CGQuery.cpp b/tools/cgquery/CGQuery.cpp index 0c28ad50..5961d7b9 100644 --- a/tools/cgquery/CGQuery.cpp +++ b/tools/cgquery/CGQuery.cpp @@ -1,12 +1,20 @@ #include "Callgraph.h" #include "CgNode.h" #include "CgTypes.h" +#include "LoggerUtil.h" #include "ReachabilityAnalysis.h" #include "io/MCGReader.h" #include #include +bool is_number(const std::string& s) +{ + return !s.empty() && + std::all_of(s.begin(), s.end(), + [](unsigned char c) { return std::isdigit(c); }); +} + int main(int argc, char** argv) { if (argc < 2) { std::cerr << "Usage: cgquery [options] \n"; @@ -66,8 +74,21 @@ int main(int argc, char** argv) { + auto sourceStr = result["source"].as(); + metacg::CgNode* sourceNode = nullptr; + + if (is_number(sourceStr)) { + sourceNode = cg->getNode(std::stoul(sourceStr)); + } else { + if (cg->countNodes(sourceStr) > 1) { + metacg::MCGLogger::logWarn( + "To node name '" + sourceStr + + "' is not unique; using first matching node. " + "Please provide a unique node ID."); - auto* sourceNode = cg->getFirstNode(result["source"].as()); + }; + sourceNode = cg->getFirstNode(sourceStr); + } if (!sourceNode) { std::cerr << "Node '" << result["source"].as() << "' not found in CG.\n"; return 1; @@ -77,8 +98,19 @@ int main(int argc, char** argv) { if (result.count("to")) { std::string toName = result["to"].as(); + metacg::CgNode* toNode = nullptr; + if (is_number(toName)) { + toNode = cg->getNode(std::stoul(toName)); + } else { + if (cg->countNodes(toName) > 1) { + metacg::MCGLogger::logWarn( + "To node name '" + toName + + "' is not unique; using first matching node. " + "Please provide a unique node ID."); + }; + toNode = cg->getFirstNode(toName); + } - auto* toNode = cg->getFirstNode(toName); if (!toNode) { std::cerr << "Entry node '" << toName << "' not found in CG.\n"; return 1; From d692f5bc470dbe5c5c83f3192863e997d17a2bbb Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 25 Dec 2025 02:23:04 +0100 Subject: [PATCH 5/7] Remove debug log --- tools/cgquery/CGQuery.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/cgquery/CGQuery.cpp b/tools/cgquery/CGQuery.cpp index 5961d7b9..fc0afe5b 100644 --- a/tools/cgquery/CGQuery.cpp +++ b/tools/cgquery/CGQuery.cpp @@ -57,7 +57,6 @@ int main(int argc, char** argv) { auto result = options.parse(static_cast(args.size()), args.data()); if (result.count("help")) { - std::cout << "help" << std::endl; std::cout << options.help() << "\n"; return 0; } From 895dc722bb50065c31a30fac45e18d34426e5f4c Mon Sep 17 00:00:00 2001 From: silas-martens <158048386+silas-martens@users.noreply.github.com> Date: Thu, 25 Dec 2025 02:40:40 +0100 Subject: [PATCH 6/7] Use negation instead of ternary operator Co-authored-by: Peter Arzt --- tools/cgquery/CGQuery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cgquery/CGQuery.cpp b/tools/cgquery/CGQuery.cpp index fc0afe5b..c35e8c75 100644 --- a/tools/cgquery/CGQuery.cpp +++ b/tools/cgquery/CGQuery.cpp @@ -115,7 +115,7 @@ int main(int argc, char** argv) { return 1; } - return reachabilityAnalysis.existsPathBetween(sourceNode, toNode, true) ? 0 : 1; + return !reachabilityAnalysis.existsPathBetween(sourceNode, toNode, true); } else { auto reachableNodes = reachabilityAnalysis.getReachableNodesFrom(sourceNode, true); From 0a63f584ec678512b0835e5e62c79f03a7f251ba Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Wed, 7 Jan 2026 14:48:27 +0100 Subject: [PATCH 7/7] [NFC] Fix CMakeLists formatting --- tools/cgquery/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/cgquery/CMakeLists.txt b/tools/cgquery/CMakeLists.txt index cb9f710a..31c74402 100644 --- a/tools/cgquery/CMakeLists.txt +++ b/tools/cgquery/CMakeLists.txt @@ -2,4 +2,3 @@ add_executable(cgquery CGQuery.cpp) add_metacg(cgquery) add_cxxopts(cgquery) -