UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

113 lines 6.26 kB
"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 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