UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

127 lines 5.4 kB
"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 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