UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

231 lines 9.01 kB
"use strict"; 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