UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

121 lines 6.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.slicerLogger = void 0; exports.staticSlicing = staticSlicing; exports.updatePotentialAddition = updatePotentialAddition; const assert_1 = require("../../util/assert"); const log_1 = require("../../util/log"); const fingerprint_1 = require("./fingerprint"); const visiting_queue_1 = require("./visiting-queue"); const slice_call_1 = require("./slice-call"); const parse_1 = require("../criterion/parse"); const environment_1 = require("../../dataflow/environments/environment"); const vertex_1 = require("../../dataflow/graph/vertex"); const edge_1 = require("../../dataflow/graph/edge"); const config_1 = require("../../config"); exports.slicerLogger = log_1.log.getSubLogger({ name: 'slicer' }); /** * This returns the ids to include in the static backward slice, when slicing with the given seed id's (must be at least one). * <p> * The returned ids can be used to {@link reconstructToCode|reconstruct the slice to R code}. * * @param graph - The dataflow graph to conduct the slicing on. * @param ast - The normalized AST of the code (used to get static nesting information of the lexemes in case of control flow dependencies that may have no effect on the slicing scope). * @param criteria - The criterias to slice on. * @param threshold - The maximum number of nodes to visit in the graph. If the threshold is reached, the slice will side with inclusion and drop its minimal guarantee. The limit ensures that the algorithm halts. * @param cache - A cache to store the results of the slice. If provided, the slice may use this cache to speed up the slicing process. */ function staticSlicing(graph, { idMap }, criteria, threshold = (0, config_1.getConfig)().solver.slicer?.threshold ?? 75, cache) { (0, assert_1.guard)(criteria.length > 0, 'must have at least one seed id to calculate slice'); const decodedCriteria = (0, parse_1.convertAllSlicingCriteriaToIds)(criteria, idMap); (0, log_1.expensiveTrace)(exports.slicerLogger, () => `calculating slice for ${decodedCriteria.length} seed criteria: ${decodedCriteria.map(s => JSON.stringify(s)).join(', ')}`); const queue = new visiting_queue_1.VisitingQueue(threshold, cache); let minNesting = Number.MAX_SAFE_INTEGER; const sliceSeedIds = new Set(); // every node ships the call environment which registers the calling environment { const emptyEnv = (0, environment_1.initializeCleanEnvironments)(); const basePrint = (0, fingerprint_1.envFingerprint)(emptyEnv); for (const { id: startId } of decodedCriteria) { queue.add(startId, emptyEnv, basePrint, false); // retrieve the minimum nesting of all nodes to only add control dependencies if they are "part" of the current execution minNesting = Math.min(minNesting, idMap.get(startId)?.info.nesting ?? minNesting); sliceSeedIds.add(startId); } /* additionally, * include all the implicit side effects that we have to consider as we are unable to narrow them down */ for (const id of graph.unknownSideEffects) { if (typeof id !== 'object') { /* otherwise, their target is just missing */ queue.add(id, emptyEnv, basePrint, true); } } } while (queue.nonEmpty()) { const current = queue.next(); const { baseEnvironment, id, onlyForSideEffects, envFingerprint: baseEnvFingerprint } = current; const currentInfo = graph.get(id, true); if (currentInfo === undefined) { exports.slicerLogger.warn(`id: ${id} must be in graph but can not be found, keep in slice to be sure`); continue; } const [currentVertex, currentEdges] = currentInfo; // we only add control dependencies iff 1) we are in different function call or 2) they have, at least, the same nesting as the slicing seed if (currentVertex.cds && currentVertex.cds.length > 0) { const topLevel = graph.isRoot(id) || sliceSeedIds.has(id); for (const cd of currentVertex.cds.filter(({ id }) => !queue.hasId(id))) { if (!topLevel || (idMap.get(cd.id)?.info.nesting ?? 0) >= minNesting) { queue.add(cd.id, baseEnvironment, baseEnvFingerprint, false); } } } if (!onlyForSideEffects) { if (currentVertex.tag === vertex_1.VertexType.FunctionCall && !currentVertex.onlyBuiltin) { (0, slice_call_1.sliceForCall)(current, currentVertex, graph, queue); } const ret = (0, slice_call_1.handleReturns)(id, queue, currentEdges, baseEnvFingerprint, baseEnvironment); if (ret) { continue; } } for (const [target, { types }] of currentEdges) { const t = (0, edge_1.shouldTraverseEdge)(types); switch (t) { case 0 /* TraverseEdge.Never */: continue; case 3 /* TraverseEdge.Always */: queue.add(target, baseEnvironment, baseEnvFingerprint, false); continue; case 2 /* TraverseEdge.OnlyIfBoth */: updatePotentialAddition(queue, id, target, baseEnvironment, baseEnvFingerprint); continue; case 1 /* TraverseEdge.SideEffect */: queue.add(target, baseEnvironment, baseEnvFingerprint, true); continue; default: (0, assert_1.assertUnreachable)(t); } } } return { ...queue.status(), decodedCriteria }; } function updatePotentialAddition(queue, id, target, baseEnvironment, envFingerprint) { const n = queue.potentialAdditions.get(target); if (n) { const [addedBy, { baseEnvironment, onlyForSideEffects }] = n; if (addedBy !== id) { queue.add(target, baseEnvironment, envFingerprint, onlyForSideEffects); queue.potentialAdditions.delete(target); } } else { queue.potentialAdditions.set(target, [id, { id: target, baseEnvironment, envFingerprint, onlyForSideEffects: false }]); } } //# sourceMappingURL=static-slicer.js.map