@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
127 lines • 5.4 kB
JavaScript
;
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 semantic_cfg_guided_visitor_1 = require("./semantic-cfg-guided-visitor");
exports.loopyFunctions = new Set(['builtin:for-loop', 'builtin:while-loop', 'builtin:repeat-loop']);
/**
* 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 config - current flowr config
* @returns true if the given loop only iterates once
*/
function onlyLoopsOnce(loop, dataflow, controlflow, ast, config) {
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] === 'builtin:for-loop') {
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: config.solver.variables }));
if (values === undefined || 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,
flowrConfig: config,
defaultVisitingOrder: 'forward'
});
return visitor.loopsOnlyOnce();
}
class CfgSingleIterationLoopDetector extends semantic_cfg_guided_visitor_1.SemanticCfgGuidedVisitor {
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.flowrConfig.solver.variables }));
if (values === undefined || 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 n = (i) => g.ingoingEdges(i);
const exits = new Set(g.getVertex(this.loopToCheck)?.end ?? []);
(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 = n(current) ?? [];
for (const [to] of next) {
stack.unshift(to);
}
}
}
}
onDefaultFunctionCall(data) {
let stopsLoop = false;
const alwaysHappens = () => {
if (!data.call.cds ||
(data.call.cds.length === 1 && data.call.cds[0].id === this.loopToCheck)) {
return true;
}
const cds = data.call.cds.filter(d => d.id !== this.loopToCheck);
return (0, info_1.happensInEveryBranch)(cds);
};
switch (data.call.origin[0]) {
case 'builtin:return':
case 'builtin:stop':
case 'builtin:break':
stopsLoop = alwaysHappens();
break;
case 'builtin:stopifnot': {
const arg = this.getBoolArgValue(data);
if (arg !== undefined) {
stopsLoop = !arg && alwaysHappens();
}
break;
}
}
this.onlyLoopyOnce = this.onlyLoopyOnce || stopsLoop;
}
loopsOnlyOnce() {
this.startVisitor([]);
return this.onlyLoopyOnce;
}
}
//# sourceMappingURL=useless-loop.js.map