UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

133 lines 7.97 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 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