@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
100 lines • 4.14 kB
JavaScript
;
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