@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
149 lines • 6.12 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loopyFunctions = void 0;
exports.onlyLoopsOnce = onlyLoopsOnce;
const alias_tracking_1 = require("../dataflow/eval/resolve/alias-tracking");
const general_1 = require("../dataflow/eval/values/general");
const r_value_1 = require("../dataflow/eval/values/r-value");
const vertex_1 = require("../dataflow/graph/vertex");
const info_1 = require("../dataflow/info");
const r_function_call_1 = require("../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const assert_1 = require("../util/assert");
const control_flow_graph_1 = require("./control-flow-graph");
const semantic_cfg_guided_visitor_1 = require("./semantic-cfg-guided-visitor");
const built_in_proc_name_1 = require("../dataflow/environments/built-in-proc-name");
exports.loopyFunctions = new Set([built_in_proc_name_1.BuiltInProcName.ForLoop, built_in_proc_name_1.BuiltInProcName.WhileLoop, built_in_proc_name_1.BuiltInProcName.RepeatLoop]);
/**
* Checks whether a loop only loops once
* @param loop - nodeid of the loop to analyse
* @param dataflow - dataflow graph
* @param controlflow - control flow graph
* @param ast - normalized ast
* @param ctx - current flowr analyzer context
* @returns true if the given loop only iterates once
*/
function onlyLoopsOnce(loop, dataflow, controlflow, ast, ctx) {
const vertex = dataflow.getVertex(loop);
if (!vertex) {
return undefined;
}
(0, assert_1.guard)(vertex.tag === vertex_1.VertexType.FunctionCall, 'invalid vertex type for onlyLoopsOnce');
(0, assert_1.guard)(vertex.origin !== 'unnamed' && exports.loopyFunctions.has(vertex.origin[0]), 'onlyLoopsOnce can only be called with loops');
// 1. In case of for loop, check if vector has only one element
if (vertex.origin[0] === built_in_proc_name_1.BuiltInProcName.ForLoop) {
if (vertex.args.length < 2) {
return undefined;
}
const vectorOfLoop = vertex.args[1];
if (vectorOfLoop === r_function_call_1.EmptyArgument) {
return undefined;
}
const values = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(vectorOfLoop.nodeId, {
graph: dataflow,
idMap: dataflow.idMap,
resolve: ctx.config.solver.variables,
ctx: ctx
}));
if (values?.elements.length !== 1 || values.elements[0].type !== 'vector' || !(0, r_value_1.isValue)(values.elements[0].elements)) {
return undefined;
}
if (values.elements[0].elements.length === 1) {
return true;
}
}
// 2. Use CFG Visitor to determine if loop always exits after the first iteration
const visitor = new CfgSingleIterationLoopDetector(loop, {
controlFlow: controlflow,
normalizedAst: ast,
dfg: dataflow,
ctx: ctx,
defaultVisitingOrder: 'forward'
});
return visitor.loopsOnlyOnce();
}
class CfgSingleIterationLoopDetector extends semantic_cfg_guided_visitor_1.SemanticCfgGuidedVisitor {
loopCds = undefined;
encounteredLoopBreaker = false;
onlyLoopyOnce = false;
loopToCheck;
constructor(loop, config) {
super(config);
this.loopToCheck = loop;
}
getBoolArgValue(data) {
if (data.call.args.length !== 1 || data.call.args[0] === r_function_call_1.EmptyArgument) {
return undefined;
}
const values = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(data.call.args[0].nodeId, {
graph: this.config.dfg,
full: true,
idMap: this.config.normalizedAst.idMap,
resolve: this.config.ctx.config.solver.variables,
ctx: this.config.ctx
}));
if (values?.elements.length !== 1 || values.elements[0].type != 'logical' || !(0, r_value_1.isValue)(values.elements[0].value)) {
return undefined;
}
return Boolean(values.elements[0].value);
}
startVisitor(_) {
const g = this.config.controlFlow.graph;
const ingoing = (i) => g.ingoingEdges(i);
const exits = new Set(control_flow_graph_1.CfgVertex.getEnd(g.getVertex(this.loopToCheck)) ?? []);
(0, assert_1.guard)(exits.size !== 0, "Can't find end of loop");
const stack = [this.loopToCheck];
while (stack.length > 0) {
const current = stack.shift();
if (!this.visitNode(current)) {
continue;
}
if (!exits.has(current)) {
const next = ingoing(current) ?? [];
for (const [to] of next) {
stack.unshift(to);
}
}
this.onlyLoopyOnce ||= this.encounteredLoopBreaker && (0, info_1.happensInEveryBranch)(this.loopCds?.filter(c => !c.byIteration));
}
this.onlyLoopyOnce ||= this.encounteredLoopBreaker && (0, info_1.happensInEveryBranch)(this.loopCds?.filter(c => !c.byIteration));
}
app(cds) {
if (cds === undefined) {
return;
}
const filtered = cds.filter(c => c.id !== this.loopToCheck);
if (filtered.length > 0) {
if (this.loopCds === undefined) {
this.loopCds = filtered;
}
else {
this.loopCds = this.loopCds.concat(filtered);
}
}
}
onBreakCall(data) {
this.encounteredLoopBreaker = true;
this.app(data.call.cds);
}
onReturnCall(data) {
this.encounteredLoopBreaker = true;
this.app(data.call.cds);
}
onStopCall(data) {
this.encounteredLoopBreaker = true;
this.app(data.call.cds);
}
onStopIfNotCall(data) {
const arg = this.getBoolArgValue(data);
if (arg === false) {
this.encounteredLoopBreaker = true;
this.app(data.call.cds);
return;
}
}
loopsOnlyOnce() {
this.startVisitor([]);
return this.onlyLoopyOnce;
}
}
//# sourceMappingURL=useless-loop.js.map