@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
149 lines • 7.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.slicerLogger = void 0;
exports.staticSlice = staticSlice;
exports.updatePotentialAddition = updatePotentialAddition;
const assert_1 = require("../../util/assert");
const log_1 = require("../../util/log");
const visiting_queue_1 = require("./visiting-queue");
const slice_call_1 = require("./slice-call");
const vertex_1 = require("../../dataflow/graph/vertex");
const edge_1 = require("../../dataflow/graph/edge");
const df_helper_1 = require("../../dataflow/graph/df-helper");
const slice_direction_1 = require("../../util/slice-direction");
const model_1 = require("../../r-bridge/lang-4.x/ast/model/model");
exports.slicerLogger = log_1.log.getSubLogger({ name: 'slicer' });
/**
* This returns the ids to include in the static slice of the given type, 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 ctx - The analyzer context used for slicing.
* @param info - The dataflow information used for slicing.
* @param idMap - The mapping from node ids to their information in the AST.
* @param ids - The seed ids to slice with. Must be at least one.
* @param direction - The direction to slice in.
* @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 staticSlice(ctx, info, { idMap }, ids, direction, threshold = 75, cache) {
(0, assert_1.guard)(ids.length > 0, 'must have at least one seed id to calculate slice');
let { graph } = info;
if (direction === slice_direction_1.SliceDirection.Forward) {
graph = df_helper_1.Dataflow.invertGraph(graph, ctx.env.makeCleanEnv());
}
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 = ctx.env.makeCleanEnv();
const basePrint = ctx.env.getCleanEnvFingerprint();
for (const startId of ids) {
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, info, queue, ctx);
}
const ret = (0, slice_call_1.handleReturns)(id, queue, currentEdges, baseEnvFingerprint, baseEnvironment);
if (ret) {
continue;
}
}
for (const [target, e] of currentEdges) {
const t = (0, edge_1.shouldTraverseEdge)(e);
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);
}
}
}
if (ctx.config.solver.slicer?.autoExtend) {
return { ...queue.status(), slicedFor: ids, result: extendSlices(queue.status().result, idMap) };
}
else {
return { ...queue.status(), slicedFor: ids };
}
}
function extendSlices(results, ast) {
const res = new Set();
for (const id of results) {
res.add(id);
let parent = ast.get(id);
while (parent && parent.info.role !== "root" /* RoleInParent.Root */ && parent.info.role !== "el-c" /* RoleInParent.ExpressionListChild */) {
parent = parent.info.parent ? ast.get(parent.info.parent) : undefined;
}
if (!parent) {
continue; // no parent, no need to extend
}
for (const id of model_1.RNode.collectAllIds(parent)) {
res.add(id);
}
}
return res;
}
/**
* Updates the potential addition for the given target node in the visiting queue.
* This describes vertices that might be added *if* another path reaches them.
*/
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