@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
139 lines • 6.11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CallTargets = void 0;
exports.satisfiesCallTargets = satisfiesCallTargets;
exports.getValueOfArgument = getValueOfArgument;
exports.identifyLinkToLastCallRelation = identifyLinkToLastCallRelation;
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 built_in_1 = require("../../../dataflow/environments/built-in");
const assert_1 = require("../../../util/assert");
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
const r_function_call_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const cascade_action_1 = require("./cascade-action");
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 = {}));
function satisfiesCallTargets(id, graph, callTarget) {
const callVertex = graph.getVertex(id, true);
if (callVertex === undefined || callVertex.tag !== vertex_1.VertexType.FunctionCall) {
return 'no';
}
const outgoing = graph.outgoingEdges(id);
if (outgoing === undefined) {
return 'no';
}
const callTargets = [...outgoing]
.filter(([, { types }]) => (0, edge_1.edgeIncludesType)(types, edge_1.EdgeType.Calls))
.map(([t]) => t);
let builtIn = false;
if (callVertex.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)(callVertex.name, callVertex.environment, identifier_1.ReferenceType.Unknown);
if (reResolved?.some(t => (0, built_in_1.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(built_in_1.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);
}
}
function getValueOfArgument(graph, call, argument, additionalAllowedTypes) {
if (!call) {
return undefined;
}
const totalIndex = argument.name ? call.args.findIndex(arg => arg !== r_function_call_1.EmptyArgument && arg.name === argument.name) : -1;
let refAtIndex;
if (totalIndex < 0) {
const references = call.args.filter(arg => arg !== r_function_call_1.EmptyArgument && !arg.name).map(graph_1.getReferenceOfArgument);
refAtIndex = references[argument.index];
}
else {
const arg = call.args[totalIndex];
refAtIndex = (0, graph_1.getReferenceOfArgument)(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;
}
}
function identifyLinkToLastCallRelation(from, cfg, graph, { callName, ignoreIf, cascadeIf }) {
if (ignoreIf?.(from, graph)) {
return [];
}
const found = [];
(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 = graph.getVertex(node, true);
if (vertex === undefined || vertex.tag !== vertex_1.VertexType.FunctionCall) {
return;
}
if (callName.test(vertex.name)) {
const act = cascadeIf ? cascadeIf(vertex, from, graph) : cascade_action_1.CascadeAction.Stop;
if (act === cascade_action_1.CascadeAction.Skip) {
return;
}
const tar = satisfiesCallTargets(vertex.id, graph, CallTargets.MustIncludeGlobal);
if (tar === 'no') {
return act === cascade_action_1.CascadeAction.Stop;
}
found.push(node);
return act === cascade_action_1.CascadeAction.Stop;
}
});
return found;
}
//# sourceMappingURL=identify-link-to-last-call-relation.js.map