UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

163 lines 6.72 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 alias_tracking_1 = require("../dataflow/eval/resolve/alias-tracking"); const log_1 = require("../util/log"); const r_function_call_1 = require("../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const general_1 = require("../dataflow/eval/values/general"); 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"); class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.SemanticCfgGuidedVisitor { cachedConditions = new Map(); cachedStatements = new Map(); inTry = new Set(); invertedCfg; 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.inTry.has(id)) { return false; } const has = this.cachedStatements.get(id); if (has) { return has; } this.visitNode(id); return this.cachedStatements.get(id) ?? 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 og = this.getValue(control_flow_graph_1.CfgEdge.unpackCause(edge)); 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 values = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(valueId, { graph: this.config.dfg, full: true, idMap: this.config.normalizedAst.idMap, resolve: this.config.ctx.config.solver.variables, ctx: this.config.ctx, })); if (values === undefined || values.elements.length !== 1 || values.elements[0].type !== 'logical' || !(0, r_value_1.isValue)(values.elements[0].value)) { this.unableToCalculateValue(id); return; } /* we should translate this to truthy later */ this.storeDefiniteValue(id, Boolean(values.elements[0].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); } 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, ctx: this.config.ctx, })); 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); } 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); } } onTryCall(data) { if (data.call.args.length < 1 || data.call.args[0] === r_function_call_1.EmptyArgument) { return; } const body = this.getCfgVertex(data.call.args[0].nodeId); if (!body) { return; } (0, simple_visitor_1.visitCfgInOrder)(this.config.controlFlow.graph, [control_flow_graph_1.CfgVertex.getId(body)], n => { if (control_flow_graph_1.CfgVertex.getEnd(body)?.includes(n)) { return true; } this.inTry.add(n); return false; }); } } /** 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