@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
173 lines • 6.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataflowInformation = void 0;
exports.negateControlDependency = negateControlDependency;
exports.doesExitPointPropagateCalls = doesExitPointPropagateCalls;
exports.addNonDefaultExitPoints = addNonDefaultExitPoints;
exports.overwriteExitPoints = overwriteExitPoints;
exports.happensInEveryBranch = happensInEveryBranch;
exports.happensInEveryBranchSet = happensInEveryBranchSet;
exports.alwaysExits = alwaysExits;
exports.filterOutLoopExitPoints = filterOutLoopExitPoints;
exports.diffControlDependency = diffControlDependency;
exports.diffControlDependencies = diffControlDependencies;
const graph_1 = require("./graph/graph");
const assert_1 = require("../util/assert");
/**
* Negates the given control dependency (i.e., flips the `when` flag).
* This keeps undefined `when` values intact as undefined.
*/
function negateControlDependency(cd) {
return {
...cd,
when: cd.when === undefined ? undefined : !cd.when,
};
}
/**
* Checks whether the given exit point type propagates calls (i.e., whether it aborts the current function execution).
*/
function doesExitPointPropagateCalls(type) {
return type === 4 /* ExitPointType.Error */;
}
/**
* Adds all non-default exit points to the existing list and updates the `invertExitCds` accordingly.
*/
function addNonDefaultExitPoints(existing, invertExitCds, activeCds, add) {
const toAdd = add.filter(({ type }) => type !== 0 /* ExitPointType.Default */);
if (toAdd.length === 0) {
return;
}
const invertedCds = toAdd.flatMap(e => e.cds?.filter(icd => !activeCds?.some(e => e.id === icd.id && e.when === icd.when)).map(negateControlDependency)).filter(assert_1.isNotUndefined);
existing.push(...toAdd);
for (const icd of invertedCds) {
if (!invertExitCds.some(e => e.id === icd.id && e.when === icd.when)) {
invertExitCds.push(icd);
}
}
}
/**
* Overwrites the existing exit points with the given ones, taking care of cds.
*/
function overwriteExitPoints(existing, replace) {
const replaceCds = replace.flatMap(e => e.cds);
if (replaceCds.length === 0 || replaceCds.includes(undefined) || happensInEveryBranch(replaceCds.filter(e => e !== undefined))) {
return replace;
}
return existing.concat(replace);
}
/**
* Helper object for {@link DataflowInformation}
*/
exports.DataflowInformation = {
name: 'DataflowInformation',
/**
* Initializes an empty {@link DataflowInformation} object with the given entry point and data.
* This is to be used as a "starting point" when processing leaf nodes during the dataflow extraction.
* @see {@link DataflowInformation}
*/
initialize(entryPoint, data) {
return {
unknownReferences: [],
in: [],
out: [],
environment: data.environment,
graph: new graph_1.DataflowGraph(undefined),
entryPoint,
exitPoints: [{ nodeId: entryPoint, type: 0 /* ExitPointType.Default */ }],
hooks: []
};
},
/**
* Type guard to check whether the given information is a {@link DataflowInformation}.
*/
is(info) {
return typeof info === 'object' && info !== null && 'entryPoint' in info && 'exitPoints' in info && 'hooks' in info;
}
};
/**
* Checks whether the given control dependencies are exhaustive (i.e. if for every control dependency on a boolean,
* the list contains a dependency on the `true` and on the `false` case).
* @see {@link happensInEveryBranchSet} - for the set-based version
*/
function happensInEveryBranch(cds) {
/* this happens only when we have no idea and require more analysis */
return cds === undefined || (cds.length !== 0 && coversSet(cds));
}
function coversSet(cds) {
const trues = new Set();
const falses = new Set();
for (const { id, when } of cds) {
if (when) {
trues.add(id);
}
else if (when === false) {
falses.add(id);
}
}
return trues.symmetricDifference(falses).size === 0;
}
/**
* Checks whether the given control dependencies are exhaustive (i.e. if for every control dependency on a boolean,
* the list contains a dependency on the `true` and on the `false` case).
* @see {@link happensInEveryBranch} - for the array-based version
*/
function happensInEveryBranchSet(cds) {
return cds === undefined || (cds.size !== 0 && coversSet(cds));
}
/**
* Checks whether the given dataflow information always exits (i.e., if there is a non-default exit point in every branch).
* @see {@link ExitPoint} - for the different types of exit points
*/
function alwaysExits(data) {
let cds = [];
for (const e of data.exitPoints) {
if (e.type !== 0 /* ExitPointType.Default */) {
if (e.cds === undefined) {
return true;
}
cds = cds.concat(e.cds);
}
}
return happensInEveryBranch(cds);
}
/**
* Filters out exit points which end their cascade within a loop.
*/
function filterOutLoopExitPoints(exitPoints) {
return exitPoints.filter(({ type }) => type !== 2 /* ExitPointType.Break */ && type !== 3 /* ExitPointType.Next */);
}
/**
* Calculates the difference between two control dependencies.
*/
function diffControlDependency(a, b, info) {
if (a === undefined || b === undefined) {
if (a !== b) {
info.report.addComment(`${info.position}Different control dependencies. ${info.leftname}: ${JSON.stringify(a)} vs. ${info.rightname}: ${JSON.stringify(b)}`);
}
return;
}
if (a.id !== b.id) {
info.report.addComment(`${info.position}Different control dependency ids. ${info.leftname}: ${JSON.stringify(a.id)} vs. ${info.rightname}: ${JSON.stringify(b.id)}`);
}
if (a.when !== b.when) {
info.report.addComment(`${info.position}Different control dependency when (id: ${JSON.stringify(a.id)}). ${info.leftname}: ${a.when} vs. ${info.rightname}: ${b.when}`);
}
}
/**
* Calculates the difference between two lists of control dependencies.
*/
function diffControlDependencies(a, b, info) {
if (a === undefined || b === undefined) {
if (a !== b) {
info.report.addComment(`${info.position}Different control dependencies: ${JSON.stringify(a)} vs. ${JSON.stringify(b)}`);
}
return;
}
if (a.length !== b.length) {
info.report.addComment(`${info.position}Different control dependency lengths: ${a.length} (${JSON.stringify(a)}) vs. ${b.length} (${JSON.stringify(b)})`);
}
for (let i = 0; i < a.length; ++i) {
diffControlDependency(a[i], b[i], { ...info, position: `${info.position}Control dependency at index: ${i}: ` });
}
}
//# sourceMappingURL=info.js.map