UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

173 lines 6.85 kB
"use strict"; 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