@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
131 lines • 6.03 kB
JavaScript
"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