From 6d242ae411e9ab36f5be419b2155967929a30ca9 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 15:39:58 +0100 Subject: [PATCH 1/9] Add Dominator/Postdominator analysis --- graph/include/DominatorAnalysis.h | 141 ++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 graph/include/DominatorAnalysis.h diff --git a/graph/include/DominatorAnalysis.h b/graph/include/DominatorAnalysis.h new file mode 100644 index 00000000..c92d3fb4 --- /dev/null +++ b/graph/include/DominatorAnalysis.h @@ -0,0 +1,141 @@ +#ifndef METACG_DOMINATORANALYSIS_H +#define METACG_DOMINATORANALYSIS_H + + +#include +#include +#include +#include +#include + +namespace metacg::analysis { + +enum class TraverseDir { + Forward, + Backward +}; + +template +struct DomData { + using NodeSet = std::unordered_set; + const NodeT* node{nullptr}; + NodeSet Doms{}; + bool initialized{false}; +}; + +template +using DomAnalysisResult = std::unordered_map>; + +template +DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode); + +template +Container unordered_intersection(const Container& a, const Container& b) { +// std::set orderedA(a.begin(), a.end()); +// std::set orderedB(b.begin(), b.end()); + Container result; +// std::set_intersection(orderedA.begin(), orderedA.end(), +// orderedB.begin(), orderedB.end(), +// std::inserter(result, result.begin())); + for (auto& item : a) { + if (std::find(b.begin(), b.end(), item) != b.end()) { + result.insert(item); + } + } + return result; +} + +// Adopted from https://stackoverflow.com/questions/25505868/the-intersection-of-multiple-sorted-arrays +template +typename DomData::NodeSet intersection (const std::vector &nodes, const DomAnalysisResult& DomMap) { + if (nodes.empty()) + return {}; + + auto last_intersection = DomMap.at(nodes[0]).Doms; + + typename DomData::NodeSet curr_intersection; + + for (std::size_t i = 1; i < nodes.size(); ++i) { + auto& currSet = DomMap.at(nodes[i]).Doms; + //LOG_STATUS("Intersecting " << dumpNodeSet("a", last_intersection) << " and " << dumpNodeSet("b", currSet) << "\n"); + + // Note: std::set_intersection does not work with unordered_set. + curr_intersection = unordered_intersection(last_intersection, currSet); +// std::set_intersection(last_intersection.begin(), last_intersection.end(), +// currSet.begin(), currSet.end(), +// std::inserter(curr_intersection, curr_intersection.begin())); + //LOG_STATUS("Result of intersection" << dumpNodeSet("", curr_intersection) << "\n"); + std::swap(last_intersection, curr_intersection); + curr_intersection.clear(); + } + return last_intersection; +} + +template +DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode) { + + auto incoming = [&](const NodeT* n) { + if constexpr (dir == TraverseDir::Forward) + return graph.getCallers(*n); + else + return graph.getCallees(*n); + }; + + auto outgoing = [&](const NodeT* n) { + if constexpr (dir == TraverseDir::Backward) + return graph.getCallers(*n); + else + return graph.getCallees(*n); + }; + + using DomDataT = DomData; + + DomAnalysisResult DomMap{{&exitNode, {&exitNode, {}, false}}}; + + std::deque workQueue{&DomMap[&exitNode]}; + + auto addToQueue = [&workQueue](DomDataT* const data) { + if (std::find(workQueue.begin(), workQueue.end(), data) == + workQueue.end()) { + workQueue.push_back(data); + } + }; + + do { + auto& nodeData = *workQueue.front(); + workQueue.pop_front(); + + std::vector initializedCallees{}; + for (auto* calleePtr : incoming(nodeData.node)) { + if (DomMap[calleePtr].initialized) { + initializedCallees.push_back(calleePtr); + } + } + + typename DomDataT::NodeSet DomNew = intersection(initializedCallees, DomMap); + DomNew.insert(nodeData.node); + + //LOG_STATUS("New Doms " << dumpNodeSet(nodeData.node->getName(), DomNew) << "\n"); + + if (!nodeData.initialized || nodeData.Doms != DomNew) { + nodeData.Doms = std::move(DomNew); + nodeData.initialized = true; + + auto outs = outgoing(nodeData.node); + for (auto* callerPtr : outs) { + auto& data = DomMap[callerPtr]; + if (!data.node) { + data.node = callerPtr; + } + addToQueue(&data); + } + + } + } while (!workQueue.empty()); + + return DomMap; +} + +} + +#endif From 68a57b88b5cb6545cbcc4b908960f527f188246b Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 15:32:14 +0100 Subject: [PATCH 2/9] Add Dom/Postdom/Reaches command classes --- tools/cgquery/include/commands/Command.h | 12 +++ tools/cgquery/include/commands/DomCommand.h | 7 ++ .../cgquery/include/commands/PostDomCommand.h | 7 ++ .../cgquery/include/commands/ReachesCommand.h | 7 ++ tools/cgquery/include/utils.h | 41 ++++++++++ tools/cgquery/src/commands/DomCommand.cpp | 75 ++++++++++++++++++ tools/cgquery/src/commands/PostDomCommand.cpp | 75 ++++++++++++++++++ tools/cgquery/src/commands/ReachesCommand.cpp | 79 +++++++++++++++++++ 8 files changed, 303 insertions(+) create mode 100644 tools/cgquery/include/commands/Command.h create mode 100644 tools/cgquery/include/commands/DomCommand.h create mode 100644 tools/cgquery/include/commands/PostDomCommand.h create mode 100644 tools/cgquery/include/commands/ReachesCommand.h create mode 100644 tools/cgquery/include/utils.h create mode 100644 tools/cgquery/src/commands/DomCommand.cpp create mode 100644 tools/cgquery/src/commands/PostDomCommand.cpp create mode 100644 tools/cgquery/src/commands/ReachesCommand.cpp diff --git a/tools/cgquery/include/commands/Command.h b/tools/cgquery/include/commands/Command.h new file mode 100644 index 00000000..874dddc8 --- /dev/null +++ b/tools/cgquery/include/commands/Command.h @@ -0,0 +1,12 @@ +#pragma once +#include "Callgraph.h" +#include +#include + +struct Command { + virtual ~Command() = default; + virtual int run(const std::vector& args) = 0; + virtual std::string name() const = 0; +protected: + metacg::Callgraph* cg; +}; diff --git a/tools/cgquery/include/commands/DomCommand.h b/tools/cgquery/include/commands/DomCommand.h new file mode 100644 index 00000000..710f85fc --- /dev/null +++ b/tools/cgquery/include/commands/DomCommand.h @@ -0,0 +1,7 @@ +#pragma once +#include "Command.h" + +struct DomCommand : Command { + int run(const std::vector& args) override; + std::string name() const override { return "dom"; }; +}; diff --git a/tools/cgquery/include/commands/PostDomCommand.h b/tools/cgquery/include/commands/PostDomCommand.h new file mode 100644 index 00000000..ae23a422 --- /dev/null +++ b/tools/cgquery/include/commands/PostDomCommand.h @@ -0,0 +1,7 @@ +#pragma once +#include "Command.h" + +struct PostDomCommand : Command { + int run(const std::vector& args) override; + std::string name() const override { return "postdom"; }; +}; diff --git a/tools/cgquery/include/commands/ReachesCommand.h b/tools/cgquery/include/commands/ReachesCommand.h new file mode 100644 index 00000000..79957d72 --- /dev/null +++ b/tools/cgquery/include/commands/ReachesCommand.h @@ -0,0 +1,7 @@ +#pragma once +#include "Command.h" + +struct ReachesCommand : Command { + int run(const std::vector& args) override; + std::string name() const override { return "reaches"; } +}; diff --git a/tools/cgquery/include/utils.h b/tools/cgquery/include/utils.h new file mode 100644 index 00000000..aa5f5e36 --- /dev/null +++ b/tools/cgquery/include/utils.h @@ -0,0 +1,41 @@ +#pragma once +#include "Callgraph.h" +#include "CgNode.h" +#include "io/MCGReader.h" +#include +#include +#include + +enum ExitCode { + Success = 0, + Error = 1, + Usage = 2 +}; + +inline bool isNumber(const std::string& s) +{ + return !s.empty() && + std::all_of(s.begin(), s.end(), + [](unsigned char c) { return std::isdigit(c); }); +} + +inline metacg::CgNode* findNode( + metacg::Callgraph* cg, + const std::string& spec, + const std::string& role) +{ + metacg::CgNode* node = nullptr; + + if (isNumber(spec)) { + node = cg->getNode(std::stoul(spec)); + } else { + if (cg->countNodes(spec) > 1) { + metacg::MCGLogger::logWarn( + role + " name '" + spec + + "' is not unique; using first match."); + } + node = cg->getFirstNode(spec); + } + return node; +} + diff --git a/tools/cgquery/src/commands/DomCommand.cpp b/tools/cgquery/src/commands/DomCommand.cpp new file mode 100644 index 00000000..071f55f6 --- /dev/null +++ b/tools/cgquery/src/commands/DomCommand.cpp @@ -0,0 +1,75 @@ +#include "commands/DomCommand.h" +#include "cxxopts.hpp" +#include "DominatorAnalysis.h" +#include "utils.h" + +int DomCommand::run(const std::vector& args) { + cxxopts::Options options("cgquery dom" , "Dominator analysis"); + options.add_options() + ("n,node", "Target node", cxxopts::value()) + ("e, entry", "Entry node", cxxopts::value()) + ("input", "Call graph file", cxxopts::value()) + ("h,help", "Print help"); + + options.parse_positional({"input"}); + options.positional_help(""); + + try { + auto result = options.parse(static_cast(args.size()), args.data()); + + if (result.count("help")) { + std::cout << options.help() << "\n"; + return ExitCode::Success; + } + + if (!result.count("node") || !result.count("entry")) { + std::cerr << "Specify target node and entry node.\n"; + std::cerr << options.help(); + return ExitCode::Success; + } + + std::string cg_name = result["input"].as(); + auto fs = metacg::io::FileSource(cg_name); + auto reader = metacg::io::createReader(fs); + std::unique_ptr cg = reader->read(); + + std::string nodeName = result["node"].as(); + std::string refName = result["entry"].as(); + + auto* targetNodePtr = findNode(cg.get(), nodeName, "Target node"); + auto* refNodePtr = findNode(cg.get(), refName, "Entry node"); + + if (!targetNodePtr) { + std::cerr << "Node '" << nodeName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } + if (!refNodePtr) { + std::cerr << "Node '" << refName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } + + std::unordered_map> dom = + metacg::analysis::computeDoms< + metacg::CgNode, + metacg::Callgraph, + metacg::analysis::TraverseDir::Forward>(*cg, *refNodePtr); + + const auto& data = dom[targetNodePtr]; + if (!data.initialized) { + std::cerr << "Dominator analysis not initialized for node '" << nodeName << "'\n"; + return ExitCode::Error; + } + + for (const auto* dPtr : data.Doms) { + std::cout << dPtr->getFunctionName() << "\n"; + } + } + catch (const cxxopts::exceptions::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + std::cerr << options.help() << "\n"; + return ExitCode::Error; + } + + return ExitCode::Success; + +} diff --git a/tools/cgquery/src/commands/PostDomCommand.cpp b/tools/cgquery/src/commands/PostDomCommand.cpp new file mode 100644 index 00000000..90abc14c --- /dev/null +++ b/tools/cgquery/src/commands/PostDomCommand.cpp @@ -0,0 +1,75 @@ +#include "commands/PostDomCommand.h" +#include "cxxopts.hpp" +#include "DominatorAnalysis.h" +#include "utils.h" + + +int PostDomCommand::run(const std::vector& args) { + cxxopts::Options options("cgquery dom" , "Postdominator analysis"); + options.add_options() + ("n,node", "Target node", cxxopts::value()) + ("e,exit", "Exit node", cxxopts::value()) + ("input", "Call graph file", cxxopts::value()) + ("h,help", "Print help"); + + options.parse_positional({"input"}); + options.positional_help(""); + + try { + auto result = options.parse(static_cast(args.size()), args.data()); + + if (result.count("help")) { + std::cout << options.help() << "\n"; + return ExitCode::Success; + } + + if (!result.count("node") || !result.count("exit")) { + std::cerr << "Specify target node and entry node.\n"; + std::cerr << options.help(); + return ExitCode::Success; + } + + std::string cg_name = result["input"].as(); + auto fs = metacg::io::FileSource(cg_name); + auto reader = metacg::io::createReader(fs); + std::unique_ptr cg = reader->read(); + + std::string nodeName = result["node"].as(); + std::string refName = result["exit"].as(); + + auto* targetNodePtr = findNode(cg.get(), nodeName, "Target node"); + auto* refNodePtr = findNode(cg.get(), refName, "Exit node"); + + if (!targetNodePtr) { + std::cerr << "Node '" << nodeName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } + if (!refNodePtr) { + std::cerr << "Node '" << refName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } + + std::unordered_map> dom = + metacg::analysis::computeDoms< + metacg::CgNode, + metacg::Callgraph, + metacg::analysis::TraverseDir::Backward>(*cg, *refNodePtr); + + const auto& data = dom[targetNodePtr]; + if (!data.initialized) { + std::cerr << "Postdominator analysis not initialized for node '" << nodeName << "'\n"; + return ExitCode::Error; + } + + for (const auto* dPtr : data.Doms) { + std::cout << dPtr->getFunctionName() << "\n"; + } + } + catch (const cxxopts::exceptions::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + std::cerr << options.help() << "\n"; + return ExitCode::Error; + } + + return ExitCode::Success; +} diff --git a/tools/cgquery/src/commands/ReachesCommand.cpp b/tools/cgquery/src/commands/ReachesCommand.cpp new file mode 100644 index 00000000..bb9b9b7c --- /dev/null +++ b/tools/cgquery/src/commands/ReachesCommand.cpp @@ -0,0 +1,79 @@ +#include "commands/ReachesCommand.h" +#include "cxxopts.hpp" +#include "ReachabilityAnalysis.h" +#include "CgNode.h" +#include "io/MCGReader.h" +#include "utils.h" + +int ReachesCommand::run(const std::vector& args) { + 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 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(""); + + try { + auto result = options.parse(static_cast(args.size()), args.data()); + + if (result.count("help")) { + std::cout << options.help() << "\n"; + return ExitCode::Success; + } + + std::string cg_name = result["input"].as(); + auto fs = metacg::io::FileSource(cg_name); + auto reader = metacg::io::createReader(fs); + std::unique_ptr cg = reader->read(); + + if (!result.count("source")) { + std::cerr << "Please specify the source node" << std::endl; + std::cout << options.help() << std::endl; + return ExitCode::Usage; + } + + + auto sourceStr = result["source"].as(); + metacg::CgNode* sourceNode = nullptr; + + sourceNode = findNode(cg.get(), sourceStr, "Source node"); + + if (!sourceNode) { + std::cerr << "Node '" << result["source"].as() << "' not found in CG.\n"; + return ExitCode::Error; + } + + metacg::analysis::ReachabilityAnalysis reachabilityAnalysis(cg.get()); + + if (result.count("to")) { + std::string toName = result["to"].as(); + metacg::CgNode* toNode = findNode(cg.get(), toName, "To node"); + + if (!toNode) { + std::cerr << "Entry node '" << toName << "' not found in CG.\n"; + return ExitCode::Error; + } + + return !reachabilityAnalysis.existsPathBetween(sourceNode, toNode, true); + } + else { + auto reachableNodes = reachabilityAnalysis.getReachableNodesFrom(sourceNode, true); + std::cout << "Reachable nodes:\n"; + + for (const auto n : reachableNodes) { + std::cout << n->getFunctionName() << '\n'; + } + + return ExitCode::Success; + } + } + catch (const cxxopts::exceptions::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + std::cerr << options.help() << "\n"; + return ExitCode::Error; + } +} + From b748d092dab479d43e7447d268540897b41a97ba Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 15:33:21 +0100 Subject: [PATCH 3/9] Refactor CGQuery and use command classes --- tools/cgquery/CGQuery.cpp | 134 ---------------------------------- tools/cgquery/CMakeLists.txt | 12 ++- tools/cgquery/src/CGQuery.cpp | 65 +++++++++++++++++ 3 files changed, 76 insertions(+), 135 deletions(-) delete mode 100644 tools/cgquery/CGQuery.cpp create mode 100644 tools/cgquery/src/CGQuery.cpp diff --git a/tools/cgquery/CGQuery.cpp b/tools/cgquery/CGQuery.cpp deleted file mode 100644 index c35e8c75..00000000 --- a/tools/cgquery/CGQuery.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#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"; - 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; - } - // 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 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 << 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 fs = metacg::io::FileSource(cg_name); - std::unique_ptr r1 = metacg::io::createReader(fs); - auto cg = r1->read(); - - - - 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."); - - }; - sourceNode = cg->getFirstNode(sourceStr); - } - 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(); - 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); - } - - if (!toNode) { - std::cerr << "Entry node '" << toName << "' not found in CG.\n"; - return 1; - } - - return !reachabilityAnalysis.existsPathBetween(sourceNode, toNode, true); - } - 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 index 31c74402..09a817ee 100644 --- a/tools/cgquery/CMakeLists.txt +++ b/tools/cgquery/CMakeLists.txt @@ -1,4 +1,14 @@ -add_executable(cgquery CGQuery.cpp) +project(CGQuery) + +add_executable( + cgquery + src/CGQuery.cpp + src/commands/DomCommand.cpp + src/commands/PostDomCommand.cpp + src/commands/ReachesCommand.cpp +) + +target_include_directories(cgquery PRIVATE include/) add_metacg(cgquery) add_cxxopts(cgquery) diff --git a/tools/cgquery/src/CGQuery.cpp b/tools/cgquery/src/CGQuery.cpp new file mode 100644 index 00000000..be2e7670 --- /dev/null +++ b/tools/cgquery/src/CGQuery.cpp @@ -0,0 +1,65 @@ +#include "commands/PostDomCommand.h" +#include "commands/ReachesCommand.h" +#include "commands/DomCommand.h" +#include "utils.h" + +#include "Callgraph.h" +#include "CgNode.h" +#include "CgTypes.h" +#include "io/MCGReader.h" + +#include + +#include +#include +#include + + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Usage: cgquery [options] \n"; + return ExitCode::Usage; + } + + std::string command = argv[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"; + std::cout << " dom Query dominators of a node for a given entry node\n"; + std::cout << " postdom Query postdominators of a node for a given exit node\n"; + std::cout << " help Show this help"; + return ExitCode::Success; + } + + 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 ExitCode::Usage; + } + + // 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]); + } + + std::unique_ptr cmd; + + if (command == "reaches") { + cmd = std::make_unique(); + } else if (command == "dom") { + cmd = std::make_unique(); + + } if (command == "postdom") { + cmd = std::make_unique(); + } else { + std::cerr << "Unknown command " << command << "." << std::endl; + return ExitCode::Usage; + } + + return cmd->run(args); +} + From 3be92e6fb60b9252e40355cf3215c0492bfe88bb Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 15:59:32 +0100 Subject: [PATCH 4/9] Added license header --- tools/cgquery/include/commands/Command.h | 5 +++++ tools/cgquery/include/commands/DomCommand.h | 5 +++++ tools/cgquery/include/commands/PostDomCommand.h | 5 +++++ tools/cgquery/include/commands/ReachesCommand.h | 5 +++++ tools/cgquery/include/utils.h | 5 +++++ tools/cgquery/src/CGQuery.cpp | 11 +++++------ tools/cgquery/src/commands/DomCommand.cpp | 5 +++++ tools/cgquery/src/commands/PostDomCommand.cpp | 5 +++++ tools/cgquery/src/commands/ReachesCommand.cpp | 5 +++++ 9 files changed, 45 insertions(+), 6 deletions(-) diff --git a/tools/cgquery/include/commands/Command.h b/tools/cgquery/include/commands/Command.h index 874dddc8..14cd496f 100644 --- a/tools/cgquery/include/commands/Command.h +++ b/tools/cgquery/include/commands/Command.h @@ -1,3 +1,8 @@ +/** +* File: Command.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #pragma once #include "Callgraph.h" #include diff --git a/tools/cgquery/include/commands/DomCommand.h b/tools/cgquery/include/commands/DomCommand.h index 710f85fc..ac885c87 100644 --- a/tools/cgquery/include/commands/DomCommand.h +++ b/tools/cgquery/include/commands/DomCommand.h @@ -1,3 +1,8 @@ +/** +* File: DomCommand.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #pragma once #include "Command.h" diff --git a/tools/cgquery/include/commands/PostDomCommand.h b/tools/cgquery/include/commands/PostDomCommand.h index ae23a422..2215140d 100644 --- a/tools/cgquery/include/commands/PostDomCommand.h +++ b/tools/cgquery/include/commands/PostDomCommand.h @@ -1,3 +1,8 @@ +/** +* File: PostDomCommand.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #pragma once #include "Command.h" diff --git a/tools/cgquery/include/commands/ReachesCommand.h b/tools/cgquery/include/commands/ReachesCommand.h index 79957d72..e699f30c 100644 --- a/tools/cgquery/include/commands/ReachesCommand.h +++ b/tools/cgquery/include/commands/ReachesCommand.h @@ -1,3 +1,8 @@ +/** +* File: ReachesCommand.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #pragma once #include "Command.h" diff --git a/tools/cgquery/include/utils.h b/tools/cgquery/include/utils.h index aa5f5e36..1ecb7734 100644 --- a/tools/cgquery/include/utils.h +++ b/tools/cgquery/include/utils.h @@ -1,3 +1,8 @@ +/** +* File: utils.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #pragma once #include "Callgraph.h" #include "CgNode.h" diff --git a/tools/cgquery/src/CGQuery.cpp b/tools/cgquery/src/CGQuery.cpp index be2e7670..ad2bd690 100644 --- a/tools/cgquery/src/CGQuery.cpp +++ b/tools/cgquery/src/CGQuery.cpp @@ -1,15 +1,14 @@ +/** +* File: CGQuery.h +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #include "commands/PostDomCommand.h" #include "commands/ReachesCommand.h" #include "commands/DomCommand.h" #include "utils.h" -#include "Callgraph.h" -#include "CgNode.h" -#include "CgTypes.h" -#include "io/MCGReader.h" - #include - #include #include #include diff --git a/tools/cgquery/src/commands/DomCommand.cpp b/tools/cgquery/src/commands/DomCommand.cpp index 071f55f6..1e8e9974 100644 --- a/tools/cgquery/src/commands/DomCommand.cpp +++ b/tools/cgquery/src/commands/DomCommand.cpp @@ -1,3 +1,8 @@ +/** +* File: DomCommand.cpp +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #include "commands/DomCommand.h" #include "cxxopts.hpp" #include "DominatorAnalysis.h" diff --git a/tools/cgquery/src/commands/PostDomCommand.cpp b/tools/cgquery/src/commands/PostDomCommand.cpp index 90abc14c..689bae09 100644 --- a/tools/cgquery/src/commands/PostDomCommand.cpp +++ b/tools/cgquery/src/commands/PostDomCommand.cpp @@ -1,3 +1,8 @@ +/** +* File: PostDomCommand.cpp +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #include "commands/PostDomCommand.h" #include "cxxopts.hpp" #include "DominatorAnalysis.h" diff --git a/tools/cgquery/src/commands/ReachesCommand.cpp b/tools/cgquery/src/commands/ReachesCommand.cpp index bb9b9b7c..6c89a6b6 100644 --- a/tools/cgquery/src/commands/ReachesCommand.cpp +++ b/tools/cgquery/src/commands/ReachesCommand.cpp @@ -1,3 +1,8 @@ +/** +* File: ReachesCommand.cpp +* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at +* https://github.com/tudasc/metacg/LICENSE.txt +*/ #include "commands/ReachesCommand.h" #include "cxxopts.hpp" #include "ReachabilityAnalysis.h" From 15803b7999373e45d514a505b2c4a26c970d4441 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 16:00:01 +0100 Subject: [PATCH 5/9] [NFC] Reformat --- graph/include/DominatorAnalysis.h | 83 +++++----- tools/cgquery/include/commands/Command.h | 11 +- tools/cgquery/include/commands/DomCommand.h | 12 +- .../cgquery/include/commands/PostDomCommand.h | 12 +- .../cgquery/include/commands/ReachesCommand.h | 12 +- tools/cgquery/include/utils.h | 48 ++---- tools/cgquery/src/CGQuery.cpp | 102 ++++++------ tools/cgquery/src/commands/DomCommand.cpp | 116 +++++++------- tools/cgquery/src/commands/PostDomCommand.cpp | 114 +++++++------- tools/cgquery/src/commands/ReachesCommand.cpp | 147 +++++++++--------- 10 files changed, 310 insertions(+), 347 deletions(-) diff --git a/graph/include/DominatorAnalysis.h b/graph/include/DominatorAnalysis.h index c92d3fb4..b7dea5ef 100644 --- a/graph/include/DominatorAnalysis.h +++ b/graph/include/DominatorAnalysis.h @@ -1,21 +1,17 @@ #ifndef METACG_DOMINATORANALYSIS_H -#define METACG_DOMINATORANALYSIS_H - +#define METACG_DOMINATORANALYSIS_H +#include #include +#include #include #include -#include -#include namespace metacg::analysis { -enum class TraverseDir { - Forward, - Backward -}; +enum class TraverseDir { Forward, Backward }; -template +template struct DomData { using NodeSet = std::unordered_set; const NodeT* node{nullptr}; @@ -23,20 +19,20 @@ struct DomData { bool initialized{false}; }; -template +template using DomAnalysisResult = std::unordered_map>; -template +template DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode); -template +template Container unordered_intersection(const Container& a, const Container& b) { -// std::set orderedA(a.begin(), a.end()); -// std::set orderedB(b.begin(), b.end()); + // std::set orderedA(a.begin(), a.end()); + // std::set orderedB(b.begin(), b.end()); Container result; -// std::set_intersection(orderedA.begin(), orderedA.end(), -// orderedB.begin(), orderedB.end(), -// std::inserter(result, result.begin())); + // std::set_intersection(orderedA.begin(), orderedA.end(), + // orderedB.begin(), orderedB.end(), + // std::inserter(result, result.begin())); for (auto& item : a) { if (std::find(b.begin(), b.end(), item) != b.end()) { result.insert(item); @@ -46,8 +42,9 @@ Container unordered_intersection(const Container& a, const Container& b) { } // Adopted from https://stackoverflow.com/questions/25505868/the-intersection-of-multiple-sorted-arrays -template -typename DomData::NodeSet intersection (const std::vector &nodes, const DomAnalysisResult& DomMap) { +template +typename DomData::NodeSet intersection(const std::vector& nodes, + const DomAnalysisResult& DomMap) { if (nodes.empty()) return {}; @@ -57,36 +54,36 @@ typename DomData::NodeSet intersection (const std::vector & for (std::size_t i = 1; i < nodes.size(); ++i) { auto& currSet = DomMap.at(nodes[i]).Doms; - //LOG_STATUS("Intersecting " << dumpNodeSet("a", last_intersection) << " and " << dumpNodeSet("b", currSet) << "\n"); + // LOG_STATUS("Intersecting " << dumpNodeSet("a", last_intersection) << " and " << dumpNodeSet("b", currSet) << + // "\n"); // Note: std::set_intersection does not work with unordered_set. curr_intersection = unordered_intersection(last_intersection, currSet); -// std::set_intersection(last_intersection.begin(), last_intersection.end(), -// currSet.begin(), currSet.end(), -// std::inserter(curr_intersection, curr_intersection.begin())); - //LOG_STATUS("Result of intersection" << dumpNodeSet("", curr_intersection) << "\n"); + // std::set_intersection(last_intersection.begin(), last_intersection.end(), + // currSet.begin(), currSet.end(), + // std::inserter(curr_intersection, curr_intersection.begin())); + // LOG_STATUS("Result of intersection" << dumpNodeSet("", curr_intersection) << "\n"); std::swap(last_intersection, curr_intersection); curr_intersection.clear(); } return last_intersection; } -template +template DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode) { + auto incoming = [&](const NodeT* n) { + if constexpr (dir == TraverseDir::Forward) + return graph.getCallers(*n); + else + return graph.getCallees(*n); + }; - auto incoming = [&](const NodeT* n) { - if constexpr (dir == TraverseDir::Forward) - return graph.getCallers(*n); - else - return graph.getCallees(*n); - }; - - auto outgoing = [&](const NodeT* n) { - if constexpr (dir == TraverseDir::Backward) - return graph.getCallers(*n); - else - return graph.getCallees(*n); - }; + auto outgoing = [&](const NodeT* n) { + if constexpr (dir == TraverseDir::Backward) + return graph.getCallers(*n); + else + return graph.getCallees(*n); + }; using DomDataT = DomData; @@ -95,8 +92,7 @@ DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode) std::deque workQueue{&DomMap[&exitNode]}; auto addToQueue = [&workQueue](DomDataT* const data) { - if (std::find(workQueue.begin(), workQueue.end(), data) == - workQueue.end()) { + if (std::find(workQueue.begin(), workQueue.end(), data) == workQueue.end()) { workQueue.push_back(data); } }; @@ -115,7 +111,7 @@ DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode) typename DomDataT::NodeSet DomNew = intersection(initializedCallees, DomMap); DomNew.insert(nodeData.node); - //LOG_STATUS("New Doms " << dumpNodeSet(nodeData.node->getName(), DomNew) << "\n"); + // LOG_STATUS("New Doms " << dumpNodeSet(nodeData.node->getName(), DomNew) << "\n"); if (!nodeData.initialized || nodeData.Doms != DomNew) { nodeData.Doms = std::move(DomNew); @@ -125,17 +121,16 @@ DomAnalysisResult computeDoms(const GraphT& graph, const NodeT& exitNode) for (auto* callerPtr : outs) { auto& data = DomMap[callerPtr]; if (!data.node) { - data.node = callerPtr; + data.node = callerPtr; } addToQueue(&data); } - } } while (!workQueue.empty()); return DomMap; } -} +} // namespace metacg::analysis #endif diff --git a/tools/cgquery/include/commands/Command.h b/tools/cgquery/include/commands/Command.h index 14cd496f..2d5572c1 100644 --- a/tools/cgquery/include/commands/Command.h +++ b/tools/cgquery/include/commands/Command.h @@ -1,8 +1,8 @@ /** -* File: Command.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: Command.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #pragma once #include "Callgraph.h" #include @@ -12,6 +12,7 @@ struct Command { virtual ~Command() = default; virtual int run(const std::vector& args) = 0; virtual std::string name() const = 0; -protected: + + protected: metacg::Callgraph* cg; }; diff --git a/tools/cgquery/include/commands/DomCommand.h b/tools/cgquery/include/commands/DomCommand.h index ac885c87..199508a3 100644 --- a/tools/cgquery/include/commands/DomCommand.h +++ b/tools/cgquery/include/commands/DomCommand.h @@ -1,12 +1,12 @@ /** -* File: DomCommand.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: DomCommand.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #pragma once #include "Command.h" struct DomCommand : Command { - int run(const std::vector& args) override; - std::string name() const override { return "dom"; }; + int run(const std::vector& args) override; + std::string name() const override { return "dom"; }; }; diff --git a/tools/cgquery/include/commands/PostDomCommand.h b/tools/cgquery/include/commands/PostDomCommand.h index 2215140d..cf7c98c3 100644 --- a/tools/cgquery/include/commands/PostDomCommand.h +++ b/tools/cgquery/include/commands/PostDomCommand.h @@ -1,12 +1,12 @@ /** -* File: PostDomCommand.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: PostDomCommand.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #pragma once #include "Command.h" struct PostDomCommand : Command { - int run(const std::vector& args) override; - std::string name() const override { return "postdom"; }; + int run(const std::vector& args) override; + std::string name() const override { return "postdom"; }; }; diff --git a/tools/cgquery/include/commands/ReachesCommand.h b/tools/cgquery/include/commands/ReachesCommand.h index e699f30c..986a3fdd 100644 --- a/tools/cgquery/include/commands/ReachesCommand.h +++ b/tools/cgquery/include/commands/ReachesCommand.h @@ -1,12 +1,12 @@ /** -* File: ReachesCommand.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: ReachesCommand.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #pragma once #include "Command.h" struct ReachesCommand : Command { - int run(const std::vector& args) override; - std::string name() const override { return "reaches"; } + int run(const std::vector& args) override; + std::string name() const override { return "reaches"; } }; diff --git a/tools/cgquery/include/utils.h b/tools/cgquery/include/utils.h index 1ecb7734..26d45f89 100644 --- a/tools/cgquery/include/utils.h +++ b/tools/cgquery/include/utils.h @@ -1,8 +1,8 @@ /** -* File: utils.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: utils.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #pragma once #include "Callgraph.h" #include "CgNode.h" @@ -11,36 +11,22 @@ #include #include -enum ExitCode { - Success = 0, - Error = 1, - Usage = 2 -}; +enum ExitCode { Success = 0, Error = 1, Usage = 2 }; -inline bool isNumber(const std::string& s) -{ - return !s.empty() && - std::all_of(s.begin(), s.end(), - [](unsigned char c) { return std::isdigit(c); }); +inline bool isNumber(const std::string& s) { + return !s.empty() && std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); }); } -inline metacg::CgNode* findNode( - metacg::Callgraph* cg, - const std::string& spec, - const std::string& role) -{ - metacg::CgNode* node = nullptr; +inline metacg::CgNode* findNode(metacg::Callgraph* cg, const std::string& spec, const std::string& role) { + metacg::CgNode* node = nullptr; - if (isNumber(spec)) { - node = cg->getNode(std::stoul(spec)); - } else { - if (cg->countNodes(spec) > 1) { - metacg::MCGLogger::logWarn( - role + " name '" + spec + - "' is not unique; using first match."); - } - node = cg->getFirstNode(spec); + if (isNumber(spec)) { + node = cg->getNode(std::stoul(spec)); + } else { + if (cg->countNodes(spec) > 1) { + metacg::MCGLogger::logWarn(role + " name '" + spec + "' is not unique; using first match."); } - return node; + node = cg->getFirstNode(spec); + } + return node; } - diff --git a/tools/cgquery/src/CGQuery.cpp b/tools/cgquery/src/CGQuery.cpp index ad2bd690..b0b5e3c7 100644 --- a/tools/cgquery/src/CGQuery.cpp +++ b/tools/cgquery/src/CGQuery.cpp @@ -1,11 +1,11 @@ /** -* File: CGQuery.h -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: CGQuery.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ +#include "commands/DomCommand.h" #include "commands/PostDomCommand.h" #include "commands/ReachesCommand.h" -#include "commands/DomCommand.h" #include "utils.h" #include @@ -13,52 +13,50 @@ #include #include - int main(int argc, char** argv) { - if (argc < 2) { - std::cerr << "Usage: cgquery [options] \n"; - return ExitCode::Usage; - } - - std::string command = argv[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"; - std::cout << " dom Query dominators of a node for a given entry node\n"; - std::cout << " postdom Query postdominators of a node for a given exit node\n"; - std::cout << " help Show this help"; - return ExitCode::Success; - } - - 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 ExitCode::Usage; - } - - // 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]); - } - - std::unique_ptr cmd; - - if (command == "reaches") { - cmd = std::make_unique(); - } else if (command == "dom") { - cmd = std::make_unique(); - - } if (command == "postdom") { - cmd = std::make_unique(); - } else { - std::cerr << "Unknown command " << command << "." << std::endl; - return ExitCode::Usage; - } - - return cmd->run(args); + if (argc < 2) { + std::cerr << "Usage: cgquery [options] \n"; + return ExitCode::Usage; + } + + std::string command = argv[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"; + std::cout << " dom Query dominators of a node for a given entry node\n"; + std::cout << " postdom Query postdominators of a node for a given exit node\n"; + std::cout << " help Show this help"; + return ExitCode::Success; + } + + 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 ExitCode::Usage; + } + + // 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]); + } + + std::unique_ptr cmd; + + if (command == "reaches") { + cmd = std::make_unique(); + } else if (command == "dom") { + cmd = std::make_unique(); + } + if (command == "postdom") { + cmd = std::make_unique(); + } else { + std::cerr << "Unknown command " << command << "." << std::endl; + return ExitCode::Usage; + } + + return cmd->run(args); } - diff --git a/tools/cgquery/src/commands/DomCommand.cpp b/tools/cgquery/src/commands/DomCommand.cpp index 1e8e9974..2f22547b 100644 --- a/tools/cgquery/src/commands/DomCommand.cpp +++ b/tools/cgquery/src/commands/DomCommand.cpp @@ -1,80 +1,74 @@ /** -* File: DomCommand.cpp -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: DomCommand.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #include "commands/DomCommand.h" -#include "cxxopts.hpp" #include "DominatorAnalysis.h" +#include "cxxopts.hpp" #include "utils.h" -int DomCommand::run(const std::vector& args) { - cxxopts::Options options("cgquery dom" , "Dominator analysis"); - options.add_options() - ("n,node", "Target node", cxxopts::value()) - ("e, entry", "Entry node", cxxopts::value()) - ("input", "Call graph file", cxxopts::value()) - ("h,help", "Print help"); - - options.parse_positional({"input"}); - options.positional_help(""); +int DomCommand::run(const std::vector& args) { + cxxopts::Options options("cgquery dom", "Dominator analysis"); + options.add_options()("n,node", "Target node", cxxopts::value())( + "e, entry", "Entry node", cxxopts::value())("input", "Call graph file", + cxxopts::value())("h,help", "Print help"); - try { - auto result = options.parse(static_cast(args.size()), args.data()); + options.parse_positional({"input"}); + options.positional_help(""); - if (result.count("help")) { - std::cout << options.help() << "\n"; - return ExitCode::Success; - } + try { + auto result = options.parse(static_cast(args.size()), args.data()); - if (!result.count("node") || !result.count("entry")) { - std::cerr << "Specify target node and entry node.\n"; - std::cerr << options.help(); - return ExitCode::Success; - } + if (result.count("help")) { + std::cout << options.help() << "\n"; + return ExitCode::Success; + } - std::string cg_name = result["input"].as(); - auto fs = metacg::io::FileSource(cg_name); - auto reader = metacg::io::createReader(fs); - std::unique_ptr cg = reader->read(); + if (!result.count("node") || !result.count("entry")) { + std::cerr << "Specify target node and entry node.\n"; + std::cerr << options.help(); + return ExitCode::Success; + } - std::string nodeName = result["node"].as(); - std::string refName = result["entry"].as(); + std::string cg_name = result["input"].as(); + auto fs = metacg::io::FileSource(cg_name); + auto reader = metacg::io::createReader(fs); + std::unique_ptr cg = reader->read(); - auto* targetNodePtr = findNode(cg.get(), nodeName, "Target node"); - auto* refNodePtr = findNode(cg.get(), refName, "Entry node"); + std::string nodeName = result["node"].as(); + std::string refName = result["entry"].as(); - if (!targetNodePtr) { - std::cerr << "Node '" << nodeName << "' not found in CG. \n" << std::endl; - return ExitCode::Error; - } - if (!refNodePtr) { - std::cerr << "Node '" << refName << "' not found in CG. \n" << std::endl; - return ExitCode::Error; - } + auto* targetNodePtr = findNode(cg.get(), nodeName, "Target node"); + auto* refNodePtr = findNode(cg.get(), refName, "Entry node"); - std::unordered_map> dom = - metacg::analysis::computeDoms< - metacg::CgNode, - metacg::Callgraph, - metacg::analysis::TraverseDir::Forward>(*cg, *refNodePtr); + if (!targetNodePtr) { + std::cerr << "Node '" << nodeName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } + if (!refNodePtr) { + std::cerr << "Node '" << refName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } - const auto& data = dom[targetNodePtr]; - if (!data.initialized) { - std::cerr << "Dominator analysis not initialized for node '" << nodeName << "'\n"; - return ExitCode::Error; - } + std::unordered_map> dom = + metacg::analysis::computeDoms( + *cg, *refNodePtr); - for (const auto* dPtr : data.Doms) { - std::cout << dPtr->getFunctionName() << "\n"; - } - } - catch (const cxxopts::exceptions::exception& e) { - std::cerr << "Error: " << e.what() << "\n"; - std::cerr << options.help() << "\n"; - return ExitCode::Error; + const auto& data = dom[targetNodePtr]; + if (!data.initialized) { + std::cerr << "Dominator analysis not initialized for node '" << nodeName << "'\n"; + return ExitCode::Error; } - return ExitCode::Success; + for (const auto* dPtr : data.Doms) { + std::cout << dPtr->getFunctionName() << "\n"; + } + } catch (const cxxopts::exceptions::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + std::cerr << options.help() << "\n"; + return ExitCode::Error; + } + return ExitCode::Success; } diff --git a/tools/cgquery/src/commands/PostDomCommand.cpp b/tools/cgquery/src/commands/PostDomCommand.cpp index 689bae09..7b80869d 100644 --- a/tools/cgquery/src/commands/PostDomCommand.cpp +++ b/tools/cgquery/src/commands/PostDomCommand.cpp @@ -1,80 +1,74 @@ /** -* File: PostDomCommand.cpp -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: PostDomCommand.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #include "commands/PostDomCommand.h" -#include "cxxopts.hpp" #include "DominatorAnalysis.h" +#include "cxxopts.hpp" #include "utils.h" - int PostDomCommand::run(const std::vector& args) { - cxxopts::Options options("cgquery dom" , "Postdominator analysis"); - options.add_options() - ("n,node", "Target node", cxxopts::value()) - ("e,exit", "Exit node", cxxopts::value()) - ("input", "Call graph file", cxxopts::value()) - ("h,help", "Print help"); + cxxopts::Options options("cgquery dom", "Postdominator analysis"); + options.add_options()("n,node", "Target node", cxxopts::value())( + "e,exit", "Exit node", cxxopts::value())("input", "Call graph file", cxxopts::value())( + "h,help", "Print help"); - options.parse_positional({"input"}); - options.positional_help(""); + options.parse_positional({"input"}); + options.positional_help(""); - try { - auto result = options.parse(static_cast(args.size()), args.data()); + try { + auto result = options.parse(static_cast(args.size()), args.data()); - if (result.count("help")) { - std::cout << options.help() << "\n"; - return ExitCode::Success; - } - - if (!result.count("node") || !result.count("exit")) { - std::cerr << "Specify target node and entry node.\n"; - std::cerr << options.help(); - return ExitCode::Success; - } + if (result.count("help")) { + std::cout << options.help() << "\n"; + return ExitCode::Success; + } - std::string cg_name = result["input"].as(); - auto fs = metacg::io::FileSource(cg_name); - auto reader = metacg::io::createReader(fs); - std::unique_ptr cg = reader->read(); + if (!result.count("node") || !result.count("exit")) { + std::cerr << "Specify target node and entry node.\n"; + std::cerr << options.help(); + return ExitCode::Success; + } - std::string nodeName = result["node"].as(); - std::string refName = result["exit"].as(); + std::string cg_name = result["input"].as(); + auto fs = metacg::io::FileSource(cg_name); + auto reader = metacg::io::createReader(fs); + std::unique_ptr cg = reader->read(); - auto* targetNodePtr = findNode(cg.get(), nodeName, "Target node"); - auto* refNodePtr = findNode(cg.get(), refName, "Exit node"); + std::string nodeName = result["node"].as(); + std::string refName = result["exit"].as(); - if (!targetNodePtr) { - std::cerr << "Node '" << nodeName << "' not found in CG. \n" << std::endl; - return ExitCode::Error; - } - if (!refNodePtr) { - std::cerr << "Node '" << refName << "' not found in CG. \n" << std::endl; - return ExitCode::Error; - } + auto* targetNodePtr = findNode(cg.get(), nodeName, "Target node"); + auto* refNodePtr = findNode(cg.get(), refName, "Exit node"); - std::unordered_map> dom = - metacg::analysis::computeDoms< - metacg::CgNode, - metacg::Callgraph, - metacg::analysis::TraverseDir::Backward>(*cg, *refNodePtr); + if (!targetNodePtr) { + std::cerr << "Node '" << nodeName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } + if (!refNodePtr) { + std::cerr << "Node '" << refName << "' not found in CG. \n" << std::endl; + return ExitCode::Error; + } - const auto& data = dom[targetNodePtr]; - if (!data.initialized) { - std::cerr << "Postdominator analysis not initialized for node '" << nodeName << "'\n"; - return ExitCode::Error; - } + std::unordered_map> dom = + metacg::analysis::computeDoms( + *cg, *refNodePtr); - for (const auto* dPtr : data.Doms) { - std::cout << dPtr->getFunctionName() << "\n"; - } + const auto& data = dom[targetNodePtr]; + if (!data.initialized) { + std::cerr << "Postdominator analysis not initialized for node '" << nodeName << "'\n"; + return ExitCode::Error; } - catch (const cxxopts::exceptions::exception& e) { - std::cerr << "Error: " << e.what() << "\n"; - std::cerr << options.help() << "\n"; - return ExitCode::Error; + + for (const auto* dPtr : data.Doms) { + std::cout << dPtr->getFunctionName() << "\n"; } + } catch (const cxxopts::exceptions::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + std::cerr << options.help() << "\n"; + return ExitCode::Error; + } - return ExitCode::Success; + return ExitCode::Success; } diff --git a/tools/cgquery/src/commands/ReachesCommand.cpp b/tools/cgquery/src/commands/ReachesCommand.cpp index 6c89a6b6..3a34accb 100644 --- a/tools/cgquery/src/commands/ReachesCommand.cpp +++ b/tools/cgquery/src/commands/ReachesCommand.cpp @@ -1,84 +1,79 @@ /** -* File: ReachesCommand.cpp -* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at -* https://github.com/tudasc/metacg/LICENSE.txt -*/ + * File: ReachesCommand.cpp + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #include "commands/ReachesCommand.h" -#include "cxxopts.hpp" -#include "ReachabilityAnalysis.h" #include "CgNode.h" +#include "ReachabilityAnalysis.h" +#include "cxxopts.hpp" #include "io/MCGReader.h" #include "utils.h" int ReachesCommand::run(const std::vector& args) { - 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 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(""); - - try { - auto result = options.parse(static_cast(args.size()), args.data()); - - if (result.count("help")) { - std::cout << options.help() << "\n"; - return ExitCode::Success; - } - - std::string cg_name = result["input"].as(); - auto fs = metacg::io::FileSource(cg_name); - auto reader = metacg::io::createReader(fs); - std::unique_ptr cg = reader->read(); - - if (!result.count("source")) { - std::cerr << "Please specify the source node" << std::endl; - std::cout << options.help() << std::endl; - return ExitCode::Usage; - } - - - auto sourceStr = result["source"].as(); - metacg::CgNode* sourceNode = nullptr; - - sourceNode = findNode(cg.get(), sourceStr, "Source node"); - - if (!sourceNode) { - std::cerr << "Node '" << result["source"].as() << "' not found in CG.\n"; - return ExitCode::Error; - } - - metacg::analysis::ReachabilityAnalysis reachabilityAnalysis(cg.get()); - - if (result.count("to")) { - std::string toName = result["to"].as(); - metacg::CgNode* toNode = findNode(cg.get(), toName, "To node"); - - if (!toNode) { - std::cerr << "Entry node '" << toName << "' not found in CG.\n"; - return ExitCode::Error; - } - - return !reachabilityAnalysis.existsPathBetween(sourceNode, toNode, true); - } - else { - auto reachableNodes = reachabilityAnalysis.getReachableNodesFrom(sourceNode, true); - std::cout << "Reachable nodes:\n"; - - for (const auto n : reachableNodes) { - std::cout << n->getFunctionName() << '\n'; - } - - return ExitCode::Success; - } - } - catch (const cxxopts::exceptions::exception& e) { - std::cerr << "Error: " << e.what() << "\n"; - std::cerr << options.help() << "\n"; - return ExitCode::Error; - } + 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 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(""); + + try { + auto result = options.parse(static_cast(args.size()), args.data()); + + if (result.count("help")) { + std::cout << options.help() << "\n"; + return ExitCode::Success; + } + + std::string cg_name = result["input"].as(); + auto fs = metacg::io::FileSource(cg_name); + auto reader = metacg::io::createReader(fs); + std::unique_ptr cg = reader->read(); + + if (!result.count("source")) { + std::cerr << "Please specify the source node" << std::endl; + std::cout << options.help() << std::endl; + return ExitCode::Usage; + } + + auto sourceStr = result["source"].as(); + metacg::CgNode* sourceNode = nullptr; + + sourceNode = findNode(cg.get(), sourceStr, "Source node"); + + if (!sourceNode) { + std::cerr << "Node '" << result["source"].as() << "' not found in CG.\n"; + return ExitCode::Error; + } + + metacg::analysis::ReachabilityAnalysis reachabilityAnalysis(cg.get()); + + if (result.count("to")) { + std::string toName = result["to"].as(); + metacg::CgNode* toNode = findNode(cg.get(), toName, "To node"); + + if (!toNode) { + std::cerr << "Entry node '" << toName << "' not found in CG.\n"; + return ExitCode::Error; + } + + return !reachabilityAnalysis.existsPathBetween(sourceNode, toNode, true); + } else { + auto reachableNodes = reachabilityAnalysis.getReachableNodesFrom(sourceNode, true); + std::cout << "Reachable nodes:\n"; + + for (const auto n : reachableNodes) { + std::cout << n->getFunctionName() << '\n'; + } + + return ExitCode::Success; + } + } catch (const cxxopts::exceptions::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + std::cerr << options.help() << "\n"; + return ExitCode::Error; + } } - From c16deaa842ccc456f098c62a257de54dcb6f2339 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 16:05:24 +0100 Subject: [PATCH 6/9] [BUG] Add missing else --- tools/cgquery/src/CGQuery.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/cgquery/src/CGQuery.cpp b/tools/cgquery/src/CGQuery.cpp index b0b5e3c7..a83d4268 100644 --- a/tools/cgquery/src/CGQuery.cpp +++ b/tools/cgquery/src/CGQuery.cpp @@ -50,8 +50,7 @@ int main(int argc, char** argv) { cmd = std::make_unique(); } else if (command == "dom") { cmd = std::make_unique(); - } - if (command == "postdom") { + } else if (command == "postdom") { cmd = std::make_unique(); } else { std::cerr << "Unknown command " << command << "." << std::endl; From ff31b16d4dd9714e6e9abce405c4245d296a2cc0 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 16:07:04 +0100 Subject: [PATCH 7/9] Add missing license header --- graph/include/DominatorAnalysis.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/graph/include/DominatorAnalysis.h b/graph/include/DominatorAnalysis.h index b7dea5ef..72b05df7 100644 --- a/graph/include/DominatorAnalysis.h +++ b/graph/include/DominatorAnalysis.h @@ -1,3 +1,8 @@ +/** + * File: DominatorAnalysis.h + * License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ #ifndef METACG_DOMINATORANALYSIS_H #define METACG_DOMINATORANALYSIS_H From dc48dbb56f9e5db9fe8714a862465be36cc76207 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 22 Jan 2026 16:22:33 +0100 Subject: [PATCH 8/9] Improve error messages --- tools/cgquery/src/commands/DomCommand.cpp | 3 ++- tools/cgquery/src/commands/PostDomCommand.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/cgquery/src/commands/DomCommand.cpp b/tools/cgquery/src/commands/DomCommand.cpp index 2f22547b..a67a9892 100644 --- a/tools/cgquery/src/commands/DomCommand.cpp +++ b/tools/cgquery/src/commands/DomCommand.cpp @@ -57,7 +57,8 @@ int DomCommand::run(const std::vector& args) { const auto& data = dom[targetNodePtr]; if (!data.initialized) { - std::cerr << "Dominator analysis not initialized for node '" << nodeName << "'\n"; + std::cerr << "Node '" << nodeName << "' is not reachable from entry node '" << refName + << "' in dominator analysis.\n"; return ExitCode::Error; } diff --git a/tools/cgquery/src/commands/PostDomCommand.cpp b/tools/cgquery/src/commands/PostDomCommand.cpp index 7b80869d..26524560 100644 --- a/tools/cgquery/src/commands/PostDomCommand.cpp +++ b/tools/cgquery/src/commands/PostDomCommand.cpp @@ -57,7 +57,8 @@ int PostDomCommand::run(const std::vector& args) { const auto& data = dom[targetNodePtr]; if (!data.initialized) { - std::cerr << "Postdominator analysis not initialized for node '" << nodeName << "'\n"; + std::cerr << "Node '" << nodeName << "' is not reachable from entry node '" << refName + << "' in post-dominator analysis.\n"; return ExitCode::Error; } From 24e1abbe53b643bf674bfc1fd097dc89ebd7e5a2 Mon Sep 17 00:00:00 2001 From: "silas.martens" Date: Thu, 29 Jan 2026 14:24:53 +0100 Subject: [PATCH 9/9] Add DominatorAnalysis tests --- graph/test/unit/CMakeLists.txt | 1 + graph/test/unit/DominatorAnalysisTest.cpp | 414 ++++++++++++++++++++++ 2 files changed, 415 insertions(+) create mode 100644 graph/test/unit/DominatorAnalysisTest.cpp diff --git a/graph/test/unit/CMakeLists.txt b/graph/test/unit/CMakeLists.txt index 0cf20f97..a060f51f 100644 --- a/graph/test/unit/CMakeLists.txt +++ b/graph/test/unit/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable( libtests CGNodeTests.cpp + DominatorAnalysisTest.cpp DotIOTest.cpp GlobalMDTest.cpp LoggingTest.cpp diff --git a/graph/test/unit/DominatorAnalysisTest.cpp b/graph/test/unit/DominatorAnalysisTest.cpp new file mode 100644 index 00000000..29411b2d --- /dev/null +++ b/graph/test/unit/DominatorAnalysisTest.cpp @@ -0,0 +1,414 @@ +/** + * File: DominatorAnalysisTest.cpp + * License: Part of the metacg project. Licensed under BSD 3 clause license. See LICENSE.txt file at + * https://github.com/tudasc/metacg/LICENSE.txt + */ + +#include "gtest/gtest.h" + +#include "DominatorAnalysis.h" +#include "MCGManager.h" + +namespace { +using namespace metacg::analysis; + +static const char* const mainS = "main"; +static const char* const C1 = "child1"; +static const char* const C2 = "child2"; +static const char* const C3 = "child3"; +static const char* const C4 = "child4"; +static const char* const C5 = "child5"; +static const char* const C6 = "child6"; +static const char* const EXIT = "exit"; + +class DominatorAnalysisTest : public ::testing::Test { + protected: + void SetUp() override { + metacg::loggerutil::getLogger(); + auto& mcgm = metacg::graph::MCGManager::get(); + mcgm.resetManager(); + mcgm.addToManagedGraphs("testgraph", std::make_unique()); + } + + metacg::Callgraph* getGraph() { + auto& mcgm = metacg::graph::MCGManager::get(); + return mcgm.getCallgraph(); + } + void fillGraph(metacg::Callgraph* graph) { + graph->insert(mainS); + graph->insert(C1); + graph->insert(C2); + graph->insert(C3); + graph->insert(C4); + graph->insert(C5); + graph->insert(C6); + graph->insert(EXIT); + } +}; + +TEST_F(DominatorAnalysisTest, Dom_SingleNode) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + auto main = cg->getFirstNode(mainS); + + auto doms = computeDoms(*cg, *main); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main)); +} +TEST_F(DominatorAnalysisTest, Dom_LinearCallChain) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto doms = computeDoms( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main)); + + auto& doms_node1 = doms[node1].Doms; + ASSERT_TRUE(doms_node1.size() == 2); + ASSERT_TRUE(doms_node1.count(main)); + ASSERT_TRUE(doms_node1.count(node1)); + + auto& doms_node2 = doms[node2].Doms; + ASSERT_TRUE(doms_node2.size() == 3); + ASSERT_TRUE(doms_node2.count(main)); + ASSERT_TRUE(doms_node2.count(node1)); + ASSERT_TRUE(doms_node2.count(node2)); + + auto& doms_exit = doms[exit].Doms; + ASSERT_TRUE(doms_exit.size() == 4); + ASSERT_TRUE(doms_exit.count(main)); + ASSERT_TRUE(doms_exit.count(node1)); + ASSERT_TRUE(doms_exit.count(node2)); + ASSERT_TRUE(doms_exit.count(exit)); +} + +TEST_F(DominatorAnalysisTest, Dom_BranchAndJoin) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(mainS, C3)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + ASSERT_TRUE(cg->addEdge(C3, C2)); + + auto doms = computeDoms( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main)); + + auto& doms_node1 = doms[node1].Doms; + ASSERT_TRUE(doms_node1.size() == 2); + ASSERT_TRUE(doms_node1.count(main)); + ASSERT_TRUE(doms_node1.count(node1)); + + auto& doms_node2 = doms[node2].Doms; + ASSERT_TRUE(doms_node2.size() == 2); + ASSERT_TRUE(doms_node2.count(main)); + ASSERT_FALSE(doms_node2.count(node1)); + ASSERT_FALSE(doms_node2.count(node3)); + ASSERT_TRUE(doms_node2.count(node2)); + + auto& doms_node3 = doms[node3].Doms; + ASSERT_TRUE(doms_node3.size() == 2); + ASSERT_TRUE(doms_node3.count(main)); + ASSERT_TRUE(doms_node3.count(node3)); + + auto& doms_exit = doms[exit].Doms; + ASSERT_TRUE(doms_exit.size() == 3); + ASSERT_TRUE(doms_exit.count(main)); + ASSERT_FALSE(doms_exit.count(node1)); + ASSERT_TRUE(doms_exit.count(node2)); + ASSERT_FALSE(doms_exit.count(node3)); + ASSERT_TRUE(doms_exit.count(exit)); +} + +TEST_F(DominatorAnalysisTest, Dom_UnreachableNodes) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + + auto doms = computeDoms( + *cg, *cg->getFirstNode(mainS)); + + ASSERT_TRUE(doms.find(cg->getFirstNode(C2)) == doms.end()); +} + +TEST_F(DominatorAnalysisTest, Dom_Recursion) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C1)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto doms = computeDoms( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + auto& doms_main = doms[main].Doms; + ASSERT_TRUE(doms_main.size() == 1); + ASSERT_TRUE(doms_main.count(main)); + + auto& doms_node1 = doms[node1].Doms; + ASSERT_TRUE(doms_node1.size() == 2); + ASSERT_TRUE(doms_node1.count(main)); + ASSERT_TRUE(doms_node1.count(node1)); + + auto& doms_node2 = doms[node2].Doms; + ASSERT_TRUE(doms_node2.size() == 3); + ASSERT_TRUE(doms_node2.count(main)); + ASSERT_TRUE(doms_node2.count(node1)); + ASSERT_TRUE(doms_node2.count(node2)); + + auto& doms_exit = doms[exit].Doms; + ASSERT_TRUE(doms_exit.size() == 4); + ASSERT_TRUE(doms_exit.count(main)); + ASSERT_TRUE(doms_exit.count(node1)); + ASSERT_TRUE(doms_exit.count(node2)); + ASSERT_TRUE(doms_exit.count(exit)); +} + +TEST_F(DominatorAnalysisTest, Dom_MultiNodeCycle) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + ASSERT_TRUE(cg->addEdge(C3, C1)); + ASSERT_TRUE(cg->addEdge(C3, EXIT)); + + auto doms = computeDoms( + *cg, *cg->getFirstNode(mainS)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(doms[main].Doms.count(main)); + + ASSERT_TRUE(doms[node1].Doms.count(main)); + ASSERT_TRUE(doms[node1].Doms.count(node1)); + + ASSERT_TRUE(doms[node2].Doms.count(main)); + ASSERT_TRUE(doms[node2].Doms.count(node1)); + ASSERT_TRUE(doms[node2].Doms.count(node2)); + + ASSERT_TRUE(doms[node3].Doms.count(main)); + ASSERT_TRUE(doms[node3].Doms.count(node1)); + ASSERT_TRUE(doms[node3].Doms.count(node2)); + ASSERT_TRUE(doms[node3].Doms.count(node3)); + + ASSERT_TRUE(doms[exit].Doms.count(main)); + ASSERT_TRUE(doms[exit].Doms.count(node1)); + ASSERT_TRUE(doms[exit].Doms.count(node2)); + ASSERT_TRUE(doms[exit].Doms.count(node3)); + ASSERT_TRUE(doms[exit].Doms.count(exit)); +} + +TEST_F(DominatorAnalysisTest, PostDom_SingleNode) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + auto exitNode = cg->getFirstNode(EXIT); + + auto pdoms = computeDoms(*cg, *exitNode); + + auto& pdoms_exit = pdoms[exitNode].Doms; + ASSERT_TRUE(pdoms_exit.size() == 1); + ASSERT_TRUE(pdoms_exit.count(exitNode)); +} + +TEST_F(DominatorAnalysisTest, PostDom_LinearCallChain) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto pdoms = computeDoms( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2)); + ASSERT_TRUE(pdoms[node2].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1)); + ASSERT_TRUE(pdoms[node1].Doms.count(node2)); + ASSERT_TRUE(pdoms[node1].Doms.count(exit)); + + ASSERT_TRUE(pdoms[main].Doms.count(main)); + ASSERT_TRUE(pdoms[main].Doms.count(node1)); + ASSERT_TRUE(pdoms[main].Doms.count(node2)); + ASSERT_TRUE(pdoms[main].Doms.count(exit)); +} + +TEST_F(DominatorAnalysisTest, PostDom_BranchAndJoin) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(mainS, C3)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + ASSERT_TRUE(cg->addEdge(C3, C2)); + + auto pdoms = computeDoms( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2)); + ASSERT_TRUE(pdoms[node2].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1)); + ASSERT_TRUE(pdoms[node1].Doms.count(node2)); + ASSERT_TRUE(pdoms[node1].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node3].Doms.count(node3)); + ASSERT_TRUE(pdoms[node3].Doms.count(node2)); + ASSERT_TRUE(pdoms[node3].Doms.count(exit)); + + ASSERT_TRUE(pdoms[main].Doms.count(main)); + ASSERT_FALSE(pdoms[main].Doms.count(node1)); + ASSERT_TRUE(pdoms[main].Doms.count(node2)); + ASSERT_FALSE(pdoms[main].Doms.count(node3)); + ASSERT_TRUE(pdoms[main].Doms.count(exit)); +} + +TEST_F(DominatorAnalysisTest, PostDom_UnreachableNodes) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + + auto pdoms = computeDoms( + *cg, *cg->getFirstNode(EXIT)); + + ASSERT_TRUE(pdoms.find(cg->getFirstNode(C2)) == pdoms.end()); +} + +TEST_F(DominatorAnalysisTest, PostDom_Recursion) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C1)); + ASSERT_TRUE(cg->addEdge(C2, EXIT)); + + auto pdoms = computeDoms( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2)); + ASSERT_TRUE(pdoms[node2].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1)); + ASSERT_TRUE(pdoms[node1].Doms.count(node2)); + ASSERT_TRUE(pdoms[node1].Doms.count(exit)); + + ASSERT_TRUE(pdoms[main].Doms.count(main)); + ASSERT_TRUE(pdoms[main].Doms.count(node1)); + ASSERT_TRUE(pdoms[main].Doms.count(node2)); + ASSERT_TRUE(pdoms[main].Doms.count(exit)); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit)); +} + +TEST_F(DominatorAnalysisTest, PostDom_MultiNodeCycle) { + auto cg = getGraph(); + ASSERT_TRUE(cg != nullptr); + fillGraph(cg); + + ASSERT_TRUE(cg->addEdge(mainS, C1)); + ASSERT_TRUE(cg->addEdge(C1, C2)); + ASSERT_TRUE(cg->addEdge(C2, C3)); + ASSERT_TRUE(cg->addEdge(C3, C1)); + ASSERT_TRUE(cg->addEdge(C3, EXIT)); + + auto pdoms = computeDoms( + *cg, *cg->getFirstNode(EXIT)); + + auto main = cg->getFirstNode(mainS); + auto node1 = cg->getFirstNode(C1); + auto node2 = cg->getFirstNode(C2); + auto node3 = cg->getFirstNode(C3); + auto exit = cg->getFirstNode(EXIT); + + ASSERT_TRUE(pdoms[exit].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node3].Doms.count(node3)); + ASSERT_TRUE(pdoms[node3].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node2].Doms.count(node2)); + ASSERT_TRUE(pdoms[node2].Doms.count(node3)); + ASSERT_TRUE(pdoms[node2].Doms.count(exit)); + + ASSERT_TRUE(pdoms[node1].Doms.count(node1)); + ASSERT_TRUE(pdoms[node1].Doms.count(node2)); + ASSERT_TRUE(pdoms[node1].Doms.count(node3)); + ASSERT_TRUE(pdoms[node1].Doms.count(exit)); + + ASSERT_TRUE(pdoms[main].Doms.count(main)); + ASSERT_TRUE(pdoms[main].Doms.count(node1)); + ASSERT_TRUE(pdoms[main].Doms.count(node2)); + ASSERT_TRUE(pdoms[main].Doms.count(node3)); + ASSERT_TRUE(pdoms[main].Doms.count(exit)); +} + +} // namespace