@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
231 lines • 9.01 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataflowInformation = exports.ControlDependency = void 0;
exports.appendCds = appendCds;
exports.withCds = withCds;
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");
const model_1 = require("../r-bridge/lang-4.x/ast/model/model");
/** Whether the two control dependencies trigger on the same condition and branch. */
function sameControlDependency(a, b) {
return a.id === b.id && a.when === b.when;
}
/**
* Utility functions to work with {@link ControlDependency|control dependencies}.
*/
exports.ControlDependency = {
name: 'ControlDependency',
/** Whether the two trigger on the same condition and branch. */
same: sameControlDependency,
/** @see {@link appendCds} */
append: appendCds,
/** @see {@link withCds} */
with: withCds,
/** @see {@link negateControlDependency} */
negate: negateControlDependency,
/** @see {@link happensInEveryBranch} */
happensInEveryBranch,
/** @see {@link happensInEveryBranchSet} */
happensInEveryBranchSet,
/** Whether the dependency stems from a loop, so what it guards may happen more than once. */
isIterated(cd, idMap) {
return cd.byIteration === true || model_1.RLoopConstructs.loopConstructTypes.has(idMap?.get(cd.id)?.type);
},
/** The dependencies of `a` that `b` is not subject to as well. */
minus(a, b) {
return a.filter(cd => !b.some(other => sameControlDependency(other, cd)));
}
};
/** Appends the given control dependencies to `target`, skipping the ones that are already in there. */
function appendCds(target, toAdd) {
if (!toAdd) {
return;
}
for (const add of toAdd) {
if (!target.some(have => sameControlDependency(have, add))) {
target.push(add);
}
}
}
/** A copy of `base` with `toAdd` appended, skipping the control dependencies that are already in there. */
function withCds(base, toAdd) {
const result = base ? Array.from(base) : [];
appendCds(result, toAdd);
return result;
}
/**
* 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);
for (const ep of toAdd) {
existing.push(ep);
}
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) {
/* as deep as the surrounding branches, so the pairwise scan beats any set */
for (const { id, when } of cds) {
if (when !== true && when !== false) {
continue;
}
let counterpart = false;
for (const other of cds) {
if (other.id === id && other.when === !when) {
counterpart = true;
break;
}
}
if (!counterpart) {
return false;
}
}
return true;
}
/**
* 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