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..c35e8c75 --- /dev/null +++ b/tools/cgquery/CGQuery.cpp @@ -0,0 +1,134 @@ +#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 new file mode 100644 index 00000000..31c74402 --- /dev/null +++ b/tools/cgquery/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(cgquery CGQuery.cpp) + +add_metacg(cgquery) +add_cxxopts(cgquery) 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.