@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
167 lines • 7.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CallTargets = void 0;
exports.satisfiesCallTargets = satisfiesCallTargets;
exports.getValueOfArgument = getValueOfArgument;
exports.identifyLinkToLastCallRelation = identifyLinkToLastCallRelation;
exports.identifyLinkToLastCallRelationSync = identifyLinkToLastCallRelationSync;
const node_id_1 = require("../../../r-bridge/lang-4.x/ast/model/processing/node-id");
const graph_1 = require("../../../dataflow/graph/graph");
const simple_visitor_1 = require("../../../control-flow/simple-visitor");
const vertex_1 = require("../../../dataflow/graph/vertex");
const edge_1 = require("../../../dataflow/graph/edge");
const resolve_by_name_1 = require("../../../dataflow/environments/resolve-by-name");
const identifier_1 = require("../../../dataflow/environments/identifier");
const assert_1 = require("../../../util/assert");
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
const cascade_action_1 = require("./cascade-action");
const cfg_kind_1 = require("../../../project/cfg-kind");
var CallTargets;
(function (CallTargets) {
/** call targets a function that is not defined locally in the script (e.g., the call targets a library function) */
CallTargets["OnlyGlobal"] = "global";
/** call targets a function that is defined locally or globally, but must include a global function */
CallTargets["MustIncludeGlobal"] = "must-include-global";
/** call targets a function that is defined locally */
CallTargets["OnlyLocal"] = "local";
/** call targets a function that is defined locally or globally, but must include a local function */
CallTargets["MustIncludeLocal"] = "must-include-local";
/** call targets a function that is defined locally or globally */
CallTargets["Any"] = "any";
})(CallTargets || (exports.CallTargets = CallTargets = {}));
/**
* Determines whether the given function call node satisfies the specified call target condition.
*/
function satisfiesCallTargets(info, graph, callTarget) {
const outgoing = graph.outgoingEdges(info.id);
if (outgoing === undefined) {
return 'no';
}
const callTargets = outgoing.entries()
.filter(([, e]) => edge_1.DfEdge.includesType(e, edge_1.EdgeType.Calls))
.map(([t]) => t)
.toArray();
let builtIn = false;
if (info.environment !== undefined) {
/*
* for performance and scoping reasons, flowR will not identify the global linkage,
* including any potential built-in mapping.
*/
const reResolved = (0, resolve_by_name_1.resolveByName)(info.name, info.environment, identifier_1.ReferenceType.Unknown);
if (reResolved?.some(t => node_id_1.NodeId.isBuiltIn(t.definedAt))) {
builtIn = true;
}
}
else {
/* if we have a call with an unbound environment,
* this only happens if we are sure of built-in relations and want to save references
*/
builtIn = true;
}
switch (callTarget) {
case CallTargets.Any:
return callTargets;
case CallTargets.OnlyGlobal:
if (callTargets.every(node_id_1.NodeId.isBuiltIn)) {
return builtIn ? ['built-in'] : [];
}
else {
return 'no';
}
case CallTargets.MustIncludeGlobal:
return builtIn || callTargets.length === 0 ? [...callTargets, 'built-in'] : 'no';
case CallTargets.OnlyLocal:
return !builtIn && callTargets.length > 0 ? callTargets : 'no';
case CallTargets.MustIncludeLocal:
if (callTargets.length > 0) {
return builtIn ? [...callTargets, 'built-in'] : callTargets;
}
else {
return 'no';
}
default:
(0, assert_1.assertUnreachable)(callTarget);
}
}
/**
* Gets the value node of the specified argument in the given function call, if it exists and matches the allowed types.
*/
function getValueOfArgument(graph, call, argument, additionalAllowedTypes) {
if (!call) {
return undefined;
}
const totalIndex = argument.name ? call.args.findIndex(arg => graph_1.FunctionArgument.hasName(arg, argument.name)) : -1;
let refAtIndex;
if (totalIndex < 0) {
const references = call.args.filter(graph_1.FunctionArgument.isPositional).map(graph_1.FunctionArgument.getReference);
refAtIndex = references[argument.index];
}
else {
const arg = call.args[totalIndex];
refAtIndex = graph_1.FunctionArgument.getReference(arg);
}
if (refAtIndex === undefined) {
return undefined;
}
let valueNode = graph.idMap?.get(refAtIndex);
if (valueNode?.type === type_1.RType.Argument) {
valueNode = valueNode.value;
}
if (valueNode) {
return !additionalAllowedTypes || additionalAllowedTypes.includes(valueNode.type) ? valueNode : undefined;
}
}
/**
* **Please refer to {@link identifyLinkToRelation}.**
*
* Identifies nodes that link to the last call of a specified function from a given starting node in the control flow graph.
* If you pass on `knownCalls` (e.g., produced by {@link getCallsInCfg}), this will only respect the functions
* listed there and ignore any other calls. This can be also used to speed up the process if you already have
* the known calls available.
* @see {@link identifyLinkToLastCallRelationSync} for the synchronous version.
*/
async function identifyLinkToLastCallRelation(from, analyzer, l, knownCalls) {
const graph = (await analyzer.dataflow()).graph;
const cfg = (await analyzer.controlflow([], cfg_kind_1.CfgKind.WithDataflow)).graph;
return identifyLinkToLastCallRelationSync(from, cfg, graph, l, knownCalls);
}
/**
* Synchronous version of {@link identifyLinkToLastCallRelation}.
*/
function identifyLinkToLastCallRelationSync(from, cfg, graph, { callName, cascadeIf, ignoreIf }, knownCalls) {
if (ignoreIf?.(from, graph)) {
return [];
}
const found = [];
const cNameCheck = callName instanceof RegExp ? ({ name }) => callName.test(identifier_1.Identifier.getName(name))
: ({ name }) => callName(identifier_1.Identifier.getName(name));
const getVertex = knownCalls ?
(node) => knownCalls.get(node) :
(node) => {
const v = graph.getVertex(node);
return (0, vertex_1.isFunctionCallVertex)(v) ? v : undefined;
};
(0, simple_visitor_1.visitCfgInReverseOrder)(cfg, [from], node => {
/* we ignore the start id as it cannot be the last call */
if (node === from) {
return;
}
const vertex = getVertex(node);
if (vertex === undefined) {
return;
}
if (cNameCheck(vertex)) {
const act = cascadeIf ? cascadeIf(vertex, from, graph) : cascade_action_1.CascadeAction.Stop;
if (act === cascade_action_1.CascadeAction.Skip) {
return;
}
const tar = satisfiesCallTargets(vertex, graph, CallTargets.MustIncludeGlobal);
if (tar !== 'no') {
found.push(node);
}
return act === cascade_action_1.CascadeAction.Stop;
}
});
return found;
}
//# sourceMappingURL=identify-link-to-last-call-relation.js.map