@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
133 lines • 7.97 kB
JavaScript
"use strict";
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 edge_1 = require("../../../../../graph/edge");
const append_1 = require("../../../../../environments/append");
const identifier_1 = require("../../../../../environments/identifier");
const general_1 = require("../../../../../eval/values/general");
const alias_tracking_1 = require("../../../../../eval/resolve/alias-tracking");
const reference_to_maybe_1 = require("../../../../../environments/reference-to-maybe");
const linker_1 = require("../../../../linker");
const r_argument_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-argument");
const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name");
function getArguments(config, args) {
let condArg;
let thenArg;
let otherwiseArg;
if (config?.args) {
const params = {
[config.args.cond]: 'cond',
[config.args.yes]: 'yes',
[config.args.no]: 'no',
'...': '...'
};
const argMaps = (0, linker_1.pMatch)((0, common_1.convertFnArguments)(args), params);
condArg = (0, unpack_argument_1.unpackArg)(r_argument_1.RArgument.getWithId(args, argMaps.get('cond')?.[0]));
thenArg = (0, unpack_argument_1.unpackArg)(r_argument_1.RArgument.getWithId(args, argMaps.get('yes')?.[0]));
otherwiseArg = (0, unpack_argument_1.unpackArg)(r_argument_1.RArgument.getWithId(args, argMaps.get('no')?.[0]));
}
else {
[condArg, thenArg, otherwiseArg] = args.map(e => (0, unpack_argument_1.unpackArg)(e));
}
return { condArg, thenArg, otherwiseArg };
}
/**
* Processes an if-then-else built-in function call.
* For example, `if(cond) thenExpr else elseExpr` and `if(cond) thenExpr`.
* The arguments will be either `[cond, thenExpr]` or `[cond, thenExpr, elseExpr]`.
*/
function processIfThenElse(name, args, rootId, data, config) {
if (args.length !== 2 && args.length !== 3) {
logger_1.dataflowLogger.warn(`If-then-else ${identifier_1.Identifier.toString(name.content)} has something different from 2 or 3 arguments, skipping`);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: 'default' }).information;
}
const { condArg, thenArg, otherwiseArg } = getArguments(config, args);
if (condArg === undefined || thenArg === undefined) {
logger_1.dataflowLogger.warn(`If-then-else ${identifier_1.Identifier.toString(name.content)} has empty condition or then case in ${JSON.stringify(args)}, skipping`);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: 'default' }).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.cds?.slice();
// 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, alias_tracking_1.resolveIdToValue)(condArg?.info.id, { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables, ctx: data.ctx });
const conditionIsAlwaysFalse = (0, general_1.valueSetGuard)(values)?.elements.every(d => d.type === 'logical' && d.value === false) ?? false;
const conditionIsAlwaysTrue = (0, general_1.valueSetGuard)(values)?.elements.every(d => d.type === 'logical' && d.value === true) ?? false;
if (!conditionIsAlwaysFalse) {
then = (0, processor_1.processDataflowFor)(thenArg, data);
if (then.entryPoint) {
then.graph.addEdge(rootId, then.entryPoint, edge_1.EdgeType.Returns);
}
if (!conditionIsAlwaysTrue) {
makeThenMaybe = true;
}
}
let otherwise;
let makeOtherwiseMaybe = false;
if (otherwiseArg !== undefined && !conditionIsAlwaysTrue) {
data = { ...data, cds: originalDependency?.slice() };
otherwise = (0, processor_1.processDataflowFor)(otherwiseArg, data);
if (otherwise.entryPoint) {
otherwise.graph.addEdge(rootId, 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.concat(makeThenMaybe ? (0, reference_to_maybe_1.makeAllMaybe)(then?.in, nextGraph, finalEnvironment, false, cdTrue) : then?.in ?? [], makeOtherwiseMaybe ? (0, reference_to_maybe_1.makeAllMaybe)(otherwise?.in, nextGraph, finalEnvironment, false, cdFalse) : otherwise?.in ?? [], cond.unknownReferences, makeThenMaybe ? (0, reference_to_maybe_1.makeAllMaybe)(then?.unknownReferences, nextGraph, finalEnvironment, false, cdTrue) : then?.unknownReferences ?? [], makeOtherwiseMaybe ? (0, reference_to_maybe_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.concat((makeThenMaybe ? (0, reference_to_maybe_1.makeAllMaybe)(then?.out, nextGraph, finalEnvironment, true, cdTrue) : then?.out ?? []), (makeOtherwiseMaybe ? (0, reference_to_maybe_1.makeAllMaybe)(otherwise?.out, nextGraph, finalEnvironment, true, cdFalse) : otherwise?.out ?? []));
(0, common_1.patchFunctionCall)({
nextGraph,
rootId,
name,
data: { ...data, cds: originalDependency },
argumentProcessResult: [cond, then, otherwise],
origin: built_in_proc_name_1.BuiltInProcName.IfThenElse
});
// as an if always evaluates its condition, we add a 'reads'-edge
nextGraph.addEdge(rootId, cond.entryPoint, edge_1.EdgeType.Reads);
const exitPoints = (then?.exitPoints ?? []).map(e => ({ ...e, cds: makeThenMaybe ? [...data.cds ?? [], { id: rootId, when: true }] : e.cds }))
.concat((otherwise?.exitPoints ?? []).map(e => ({ ...e, cds: makeOtherwiseMaybe ? [...data.cds ?? [], { id: rootId, when: false }] : e.cds })));
return {
unknownReferences: [],
in: [{ nodeId: rootId, name: name.content, cds: originalDependency, type: identifier_1.ReferenceType.Function }, ...ingoing],
out: outgoing,
exitPoints,
entryPoint: rootId,
environment: finalEnvironment,
graph: nextGraph,
hooks: cond.hooks.concat(then?.hooks ?? [], otherwise?.hooks ?? []),
};
}
//# sourceMappingURL=built-in-if-then-else.js.map