UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

131 lines 6.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processStopIfNot = processStopIfNot; const known_call_handling_1 = require("../known-call-handling"); const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const logger_1 = require("../../../../../logger"); const defaultmap_1 = require("../../../../../../util/collections/defaultmap"); const alias_tracking_1 = require("../../../../../eval/resolve/alias-tracking"); const general_1 = require("../../../../../eval/values/general"); const assert_1 = require("../../../../../../util/assert"); const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type"); const identifier_1 = require("../../../../../environments/identifier"); const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name"); /** * Processes a built-in 'stopifnot' function call. * This is special in that it may take a number of boolean expressions either via `...` or * via `exprs` for which each expression is now evaluated individually: * @example * ```r * stopifnot(exprs = { * all.equal(pi, 3.1415927) * 2 < 2 * all(1:10 < 12) * "a" < "b" * }) * ``` */ function processStopIfNot(name, args, rootId, data) { const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: built_in_proc_name_1.BuiltInProcName.StopIfNot }).information; if (args.length === 0) { logger_1.dataflowLogger.warn(`stopifnot (${identifier_1.Identifier.toString(name.content)}) has no argument, assuming trivially true and skipping`); return res; } // R only allows ... or exprs or exprObject, not all, but we over-approximate and collect all, given that they are after '...' // we can safely extract named args by full name const argMap = new defaultmap_1.DefaultMap(() => []); for (const arg of args) { const name = (arg === r_function_call_1.EmptyArgument ? undefined : arg.name)?.content; if (name === 'exprObject' || name === 'exprs' || name === 'local') { argMap.get(name).push(arg); } else { argMap.get('...').push(arg); } } const localArgs = argMap.get('local'); const localArg = localArgs.length > 0 ? localArgs.at(-1) : undefined; const resolveArgs = { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables, ctx: data.ctx }; // we collect all control dependencies from: all '...', all expressions in 'exprs', and 'exprObject' const ids = collectIdsForControl(argMap, data); if (localArg !== undefined && localArg !== r_function_call_1.EmptyArgument) { const localVal = (0, alias_tracking_1.resolveIdToValue)(localArg?.value?.info.id, resolveArgs); const alwaysTrue = (0, general_1.valueSetGuard)(localVal)?.elements.every(d => d.type === 'logical' && d.value === true) ?? false; if (!alwaysTrue) { logger_1.dataflowLogger.warn(`stopifnot (${identifier_1.Identifier.toString(name.content)}) with non-true 'local' argument is not yet supported, over-approximate`); const cds = (data.cds ?? []).concat(Array.from(ids).map(r => ({ id: r, when: false }))); res.exitPoints.push({ type: 4 /* ExitPointType.Error */, nodeId: rootId, cds: cds }); return res; } } const cds = []; for (const id of ids) { const val = (0, alias_tracking_1.resolveIdToValue)(id, resolveArgs); const alwaysFalse = (0, general_1.valueSetGuard)(val)?.elements.every(d => d.type === 'logical' && d.value === false) ?? false; if (alwaysFalse) { // we know that this fails *always* res.exitPoints.push(data.cds ? { type: 4 /* ExitPointType.Error */, nodeId: rootId, cds: data.cds } : { type: 4 /* ExitPointType.Error */, nodeId: rootId }); return res; } const alwaysTrue = (0, general_1.valueSetGuard)(val)?.elements.every(d => d.type === 'logical' && d.value === true) ?? false; if (!alwaysTrue) { cds.push({ id: id, when: false // only trigger if it is false }); } } if (cds.length === 0) { logger_1.dataflowLogger.warn(`stopifnot (${identifier_1.Identifier.toString(name.content)}) has no unknown expressions to evaluate, assuming trivially true and skipping`); return res; } res.exitPoints.push({ type: 4 /* ExitPointType.Error */, nodeId: rootId, cds: (data.cds ?? []).concat(cds) }); return res; } /** Generator so we can early exit on first always-false */ function* collectIdsForControl(argMap, data) { yield* argMap.get('...') .map(a => a === r_function_call_1.EmptyArgument ? undefined : a.value?.info.id) .filter(assert_1.isNotUndefined); const exprs = argMap.get('exprs'); if (exprs.length > 0) { const exprsArg = exprs.at(-1); if (exprsArg !== r_function_call_1.EmptyArgument && exprsArg?.value?.info.id) { const elem = data.completeAst.idMap.get(exprsArg.value?.info.id); if (elem?.type === type_1.RType.ExpressionList) { for (const expr of elem.children) { yield expr.info.id; } } else { yield exprsArg.value?.info.id; } } } const exprObjectArgs = argMap.get('exprObject'); if (exprObjectArgs.length > 0) { const exprObjectArg = exprObjectArgs.at(-1); if (exprObjectArg !== r_function_call_1.EmptyArgument && exprObjectArg?.value?.info.id) { yield exprObjectArg.value?.info.id; } } } //# sourceMappingURL=built-in-stop-if-not.js.map