@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
113 lines • 6.26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.processIfThenElse = processIfThenElse;
const processor_1 = require("../../../../../processor");
const info_1 = require("../../../../../info");
const known_call_handling_1 = require("../known-call-handling");
const common_1 = require("../common");
const unpack_argument_1 = require("../argument/unpack-argument");
const logger_1 = require("../../../../../logger");
const resolve_by_name_1 = require("../../../../../environments/resolve-by-name");
const edge_1 = require("../../../../../graph/edge");
const append_1 = require("../../../../../environments/append");
const identifier_1 = require("../../../../../environments/identifier");
const environment_1 = require("../../../../../environments/environment");
function processIfThenElse(name, args, rootId, data) {
if (args.length !== 2 && args.length !== 3) {
logger_1.dataflowLogger.warn(`If-then-else ${name.content} has something different from 2 or 3 arguments, skipping`);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
}
const [condArg, thenArg, otherwiseArg] = args.map(e => (0, unpack_argument_1.unpackArgument)(e));
if (condArg === undefined || thenArg === undefined) {
logger_1.dataflowLogger.warn(`If-then-else ${name.content} has empty condition or then case in ${JSON.stringify(args)}, skipping`);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
}
const cond = (0, processor_1.processDataflowFor)(condArg, data);
if ((0, info_1.alwaysExits)(cond)) {
logger_1.dataflowLogger.warn(`If-then-else ${rootId} forces exit in condition, skipping rest`);
return cond;
}
const originalDependency = data.controlDependencies;
// currently we update the cd afterward :sweat:
data = { ...data, environment: cond.environment };
let then;
let makeThenMaybe = false;
// we should defer this to the abstract interpretation
const values = (0, resolve_by_name_1.resolveValueOfVariable)(condArg?.lexeme, data.environment, data.completeAst.idMap);
const conditionIsAlwaysFalse = values?.every(d => d === false) ?? false;
const conditionIsAlwaysTrue = values?.every(d => d === true) ?? false;
if (!conditionIsAlwaysFalse) {
then = (0, processor_1.processDataflowFor)(thenArg, data);
if (then.entryPoint) {
then.graph.addEdge(name.info.id, then.entryPoint, edge_1.EdgeType.Returns);
}
if (!conditionIsAlwaysTrue) {
makeThenMaybe = true;
}
}
let otherwise;
let makeOtherwiseMaybe = false;
if (otherwiseArg !== undefined && !conditionIsAlwaysTrue) {
otherwise = (0, processor_1.processDataflowFor)(otherwiseArg, data);
if (otherwise.entryPoint) {
otherwise.graph.addEdge(name.info.id, otherwise.entryPoint, edge_1.EdgeType.Returns);
}
if (!conditionIsAlwaysFalse) {
makeOtherwiseMaybe = true;
}
}
const nextGraph = cond.graph.mergeWith(then?.graph).mergeWith(otherwise?.graph);
const thenEnvironment = then?.environment ?? cond.environment;
// if there is no "else" case, we have to recover whatever we had before as it may be not executed
let finalEnvironment;
if (conditionIsAlwaysFalse) {
finalEnvironment = otherwise ? otherwise.environment : cond.environment;
}
else if (conditionIsAlwaysTrue) {
finalEnvironment = thenEnvironment;
}
else {
finalEnvironment = (0, append_1.appendEnvironment)(thenEnvironment, otherwise ? otherwise.environment : cond.environment);
}
const cdTrue = { id: rootId, when: true };
const cdFalse = { id: rootId, when: false };
// again within an if-then-else we consider all actives to be read
const ingoing = [
...cond.in,
...(makeThenMaybe ? (0, environment_1.makeAllMaybe)(then?.in, nextGraph, finalEnvironment, false, cdTrue) : then?.in ?? []),
...(makeOtherwiseMaybe ? (0, environment_1.makeAllMaybe)(otherwise?.in, nextGraph, finalEnvironment, false, cdFalse) : otherwise?.in ?? []),
...cond.unknownReferences,
...(makeThenMaybe ? (0, environment_1.makeAllMaybe)(then?.unknownReferences, nextGraph, finalEnvironment, false, cdTrue) : then?.unknownReferences ?? []),
...(makeOtherwiseMaybe ? (0, environment_1.makeAllMaybe)(otherwise?.unknownReferences, nextGraph, finalEnvironment, false, cdFalse) : otherwise?.unknownReferences ?? []),
];
// we assign all with a maybe marker
// we do not merge even if they appear in both branches because the maybe links will refer to different ids
const outgoing = [
...cond.out,
...(makeThenMaybe ? (0, environment_1.makeAllMaybe)(then?.out, nextGraph, finalEnvironment, true, cdTrue) : then?.out ?? []),
...(makeOtherwiseMaybe ? (0, environment_1.makeAllMaybe)(otherwise?.out, nextGraph, finalEnvironment, true, cdFalse) : otherwise?.out ?? []),
];
(0, common_1.patchFunctionCall)({
nextGraph,
rootId,
name,
data: { ...data, controlDependencies: originalDependency },
argumentProcessResult: [cond, then, otherwise]
});
// as an if always evaluates its condition, we add a 'reads'-edge
nextGraph.addEdge(name.info.id, cond.entryPoint, edge_1.EdgeType.Reads);
const exitPoints = [
...(then?.exitPoints ?? []).map(e => ({ ...e, controlDependencies: makeThenMaybe ? [...data.controlDependencies ?? [], { id: rootId, when: true }] : e.controlDependencies })),
...(otherwise?.exitPoints ?? []).map(e => ({ ...e, controlDependencies: makeOtherwiseMaybe ? [...data.controlDependencies ?? [], { id: rootId, when: false }] : e.controlDependencies }))
];
return {
unknownReferences: [],
in: [{ nodeId: rootId, name: name.content, controlDependencies: originalDependency, type: identifier_1.ReferenceType.Function }, ...ingoing],
out: outgoing,
exitPoints,
entryPoint: rootId,
environment: finalEnvironment,
graph: nextGraph
};
}
//# sourceMappingURL=built-in-if-then-else.js.map