@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
214 lines • 8.94 kB
JavaScript
"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