UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

149 lines 6.12 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 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