UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

214 lines 8.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.cfgAnalyzeDeadCode = cfgAnalyzeDeadCode; const control_flow_graph_1 = require("./control-flow-graph"); const logic_1 = require("../util/logic"); const semantic_cfg_guided_visitor_1 = require("./semantic-cfg-guided-visitor"); const vertex_1 = require("../dataflow/graph/vertex"); const graph_1 = require("../dataflow/graph/graph"); const node_value_1 = require("../dataflow/eval/resolve/node-value"); const built_in_proc_name_1 = require("../dataflow/environments/built-in-proc-name"); const log_1 = require("../util/log"); const r_function_call_1 = require("../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const r_value_1 = require("../dataflow/eval/values/r-value"); const simple_visitor_1 = require("./simple-visitor"); const convert_values_1 = require("../r-bridge/lang-4.x/convert-values"); const type_1 = require("../r-bridge/lang-4.x/ast/model/type"); class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.SemanticCfgGuidedVisitor { cachedConditions = new Map(); cachedStatements = new Map(); cachedSwitch = new Map(); jumpIsolated = new Set(); getValue(id) { const has = this.cachedConditions.get(id); if (has) { return has; } this.visitNode(id); return this.cachedConditions.get(id) ?? logic_1.Ternary.Maybe; } isUnconditionalJump(id) { if (this.jumpIsolated.has(id)) { return false; } const has = this.cachedStatements.get(id); if (!has) { this.visitNode(id); if (!this.cachedStatements.get(id)) { return false; } } return !this.inParameterDefault(id); } /** a parameter default is forced on access if at all, so a jump within it must not cut the function body */ inParameterDefault(id) { for (let node = this.getNormalizedAst(id); node !== undefined; node = this.getNormalizedAst(node.info.parent)) { if (node.type === type_1.RType.Parameter) { return true; } else if (node.type === type_1.RType.FunctionDefinition) { return false; } } return false; } unableToCalculateValue(id) { this.cachedConditions.set(id, logic_1.Ternary.Maybe); } storeDefiniteValue(id, value) { this.cachedConditions.set(id, value ? logic_1.Ternary.Always : logic_1.Ternary.Never); } startVisitor() { const cfg = this.config.controlFlow.graph; for (const [from, targets] of cfg.edges()) { for (const [target, edge] of targets) { if (control_flow_graph_1.CfgEdge.isControlDependency(edge)) { const cause = control_flow_graph_1.CfgEdge.unpackCause(edge); if (this.switchArmDefinitelyNotTaken(cause, from)) { cfg.removeEdge(from, target); continue; } const og = this.getValue(cause); const w = control_flow_graph_1.CfgEdge.unpackWhen(edge); if (og === logic_1.Ternary.Always && w === convert_values_1.RFalse) { cfg.removeEdge(from, target); } else if (og === logic_1.Ternary.Never && w === convert_values_1.RTrue) { cfg.removeEdge(from, target); } } else if (control_flow_graph_1.CfgEdge.isFlowDependency(edge) && this.isUnconditionalJump(target)) { // for each unconditional jump, we find the corresponding end/exit nodes and remove any flow edges for (const end of control_flow_graph_1.CfgVertex.getEnd(this.getCfgVertex(target)) ?? []) { for (const [target, edge] of cfg.ingoingEdges(end) ?? []) { if (control_flow_graph_1.CfgEdge.isFlowDependency(edge)) { cfg.removeEdge(target, end); } } } } } } } handleValuesFor(id, valueId) { if (this.cachedConditions.has(id)) { return; } const value = node_value_1.NodeValue.inGraph.soleOf(valueId, this.config.dfg, this.config.ctx, 'logical', { idMap: this.config.normalizedAst.idMap }); if (value === undefined || !(0, r_value_1.isValue)(value.value)) { this.unableToCalculateValue(id); return; } /* we should translate this to truthy later */ this.storeDefiniteValue(id, Boolean(value.value)); } handleWithCondition(data) { const id = data.call.id; if (data.condition === undefined || data.condition === r_function_call_1.EmptyArgument) { this.unableToCalculateValue(id); return; } this.handleValuesFor(id, typeof data.condition === 'object' ? data.condition.nodeId : data.condition); } onIfThenElseCall(data) { this.handleWithCondition(data); } onWhileLoopCall(data) { this.handleWithCondition(data); } onStopCall(data) { this.cachedStatements.set(data.call.id, true); } onStopIfNotCall(data) { if (this.cachedStatements.has(data.call.id)) { return; } const arg = this.getBoolArgValue(data); if (arg !== undefined) { this.cachedStatements.set(data.call.id, !arg); } } protectSubgraph(nodeId) { const start = this.getCfgVertex(nodeId); if (!start) { return; } (0, simple_visitor_1.visitCfgInOrder)(this.config.controlFlow.graph, [control_flow_graph_1.CfgVertex.getId(start)], n => { if (control_flow_graph_1.CfgVertex.getEnd(start)?.includes(n)) { return true; } this.jumpIsolated.add(n); return false; }); } onTryCall(data) { if (data.call.args.length < 1 || data.call.args[0] === r_function_call_1.EmptyArgument) { return; } this.protectSubgraph(data.call.args[0].nodeId); } switchArmDefinitelyNotTaken(cause, from) { const v = this.config.dfg.getVertex(cause); if (!vertex_1.FunctionCallVertex.is(v) || !v.origin.includes(built_in_proc_name_1.BuiltInProcName.Switch)) { return false; } const selected = this.selectedSwitchArm(v); if (selected === 'unknown') { return false; } const isArm = v.args.slice(1).some(a => graph_1.FunctionArgument.getReference(a) !== undefined && graph_1.FunctionArgument.getId(a) === from); return isArm && from !== selected.id; } selectedSwitchArm(v) { const cached = this.cachedSwitch.get(v.id); if (cached !== undefined) { return cached; } const result = this.computeSelectedSwitchArm(v); this.cachedSwitch.set(v.id, result); return result; } computeSelectedSwitchArm(v) { const target = node_value_1.NodeValue.inGraph.singleStringOf(graph_1.FunctionArgument.getReference(v.args[0]), this.config.dfg, this.config.ctx, { idMap: this.config.normalizedAst.idMap }); if (target === undefined) { return 'unknown'; } const arms = v.args.slice(1); for (let i = 0; i < arms.length; i++) { if (graph_1.FunctionArgument.getName(arms[i]) === target) { for (let j = i; j < arms.length; j++) { if (graph_1.FunctionArgument.getReference(arms[j]) !== undefined) { return { id: graph_1.FunctionArgument.getId(arms[j]) }; } } return { id: undefined }; } } let defaultId; let defaults = 0; for (const arm of arms) { if (arm !== r_function_call_1.EmptyArgument && !graph_1.FunctionArgument.isNamed(arm) && graph_1.FunctionArgument.getReference(arm) !== undefined) { defaults++; defaultId = graph_1.FunctionArgument.getId(arm); } } return defaults > 1 ? 'unknown' : { id: defaultId }; } } /** Breaks unsatisfiable control dependencies */ function cfgAnalyzeDeadCode(cfg, info) { if (!info.ast || !info.dfg) { log_1.log.warn('cfgAnalyzeDeadCode called without ast or dfg, skipping dead code analysis'); return cfg; } const visitor = new CfgConditionalDeadCodeRemoval({ controlFlow: cfg, normalizedAst: info.ast, dfg: info.dfg, ctx: info.ctx, defaultVisitingOrder: 'backward' }); visitor.start(); return cfg; } //# sourceMappingURL=cfg-dead-code.js.map