UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

100 lines 4.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertCfgSatisfiesProperties = assertCfgSatisfiesProperties; const set_1 = require("../util/collections/set"); const log_1 = require("../util/log"); const simple_visitor_1 = require("./simple-visitor"); /** * The collection of properties that can be checked on a control flow graph. */ const CfgProperties = { 'single-entry-and-exit': checkSingleEntryAndExit, 'has-entry-and-exit': hasEntryAndExit, 'entry-reaches-all': checkEntryReachesAll, 'exit-reaches-all': checkExitIsReachedByAll, /* currently not satisfied for function calls 'at-most-one-in-fd': c => checkFdIOCount(c, 'in', 'at-most', 1), 'exactly-one-in-fd': c => checkFdIOCount(c, 'in', 'exact', 1), 'at-most-one-out-fd': c => checkFdIOCount(c, 'out', 'at-most', 1), 'exactly-one-out-fd': c => checkFdIOCount(c, 'out', 'exact', 1), */ 'no-direct-fd-cycles': c => checkNoDirectCycles(c, 0 /* CfgEdgeType.Fd */), 'no-direct-cd-cycles': c => checkNoDirectCycles(c, 1 /* CfgEdgeType.Cd */), }; function checkSingleEntryAndExit(cfg) { return new Set(cfg.entryPoints).size === 1 && new Set(cfg.exitPoints).size === 1 && new Set(cfg.breaks).size === 0 && new Set(cfg.returns).size === 0 && new Set(cfg.nexts).size === 0; } function hasEntryAndExit(cfg) { return cfg.entryPoints.every(e => cfg.graph.hasVertex(e)) && cfg.exitPoints.every(e => cfg.graph.hasVertex(e)); } function checkReachFrom(label, cfg, start, collect) { if (start === undefined) { return false; } const collected = new Set(); collect(cfg.graph, [start], node => { collected.add(node); }); // we only require the roots to be there const allVertices = cfg.graph.rootIds(); const diff = (0, set_1.setMinus)(allVertices, collected); if (diff.size > 0) { log_1.log.error(`Unreachable vertices from ${label}:`, diff); return false; } return true; } function checkExitIsReachedByAll(cfg) { return checkReachFrom('exit', cfg, cfg.exitPoints[0], simple_visitor_1.visitCfgInReverseOrder); } function checkEntryReachesAll(cfg) { return checkReachFrom('entry', cfg, cfg.entryPoints[0], simple_visitor_1.visitCfgInOrder); } function _checkFdIOCount(cfg, dir, type, limit) { const counts = new Map(); for (const [from, targets] of cfg.graph.edges()) { for (const [to, edge] of targets) { const important = dir === 'in' ? to : from; if (edge.label === 0 /* CfgEdgeType.Fd */) { counts.set(important, (counts.get(important) ?? 0) + 1); } } } const check = type === 'exact' ? (a) => a === limit : (a) => a <= limit; for (const [node, count] of counts) { if (type === 'exact' && (cfg.entryPoints.includes(node) || cfg.exitPoints.includes(node) || !cfg.graph.rootIds().has(node))) { continue; // skip entry and exit points, they do not have to satisfy this } if (!check(count)) { log_1.log.error(`Node ${node} has ${count} ${dir} edges, expected ${type} ${limit}`); return false; } } return true; } function checkNoDirectCycles(cfg, type) { for (const [from, targets] of cfg.graph.edges()) { for (const [to, edge] of targets) { if (edge.label === type && to === from) { log_1.log.error(`Node ${from} has a direct cycle with ${to}`); return false; } } } return true; } /** * Check if the given CFG satisfies all properties. * @param cfg - The control flow graph to check. * @param excludeProperties - If provided, exclude the given properties, otherwise this checks all properties. */ function assertCfgSatisfiesProperties(cfg, excludeProperties) { for (const [propName, prop] of Object.entries(CfgProperties)) { if ((!excludeProperties || !excludeProperties.includes(propName)) && !prop(cfg)) { return propName; } } return true; } //# sourceMappingURL=cfg-properties.js.map