@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
163 lines • 6.72 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 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