@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
179 lines • 9.02 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processPurrrFormula = processPurrrFormula;
const known_call_handling_1 = require("../known-call-handling");
const linker_1 = require("../../../../linker");
const df_helper_1 = require("../../../../../graph/df-helper");
const vertex_1 = require("../../../../../graph/vertex");
const resolve_by_name_1 = require("../../../../../environments/resolve-by-name");
const identifier_1 = require("../../../../../environments/identifier");
const common_1 = require("../common");
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");
const model_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/model");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const edge_1 = require("../../../../../graph/edge");
const unpack_argument_1 = require("../argument/unpack-argument");
const logger_1 = require("../../../../../logger");
const graph_1 = require("../../../../../graph/graph");
function linkOnSymbol(rootId, filteredArgs, node, graph, data) {
// If the formula is a symbol naming a function, try to resolve it in the call environment.
try {
const defs = (0, resolve_by_name_1.resolveByName)(node.content, data.environment, identifier_1.ReferenceType.Function) ?? [];
graph.addEdge(rootId, node.info.id, edge_1.EdgeType.Calls);
for (const def of defs) {
// Mark the call as calling this target
graph.addEdge(rootId, def.nodeId, edge_1.EdgeType.Calls);
// If the target directly maps to a function definition AST, try to link arguments to parameters
let linked = data.completeAst.idMap.get(def.nodeId);
if (linked?.type !== type_1.RType.FunctionDefinition) {
for (const vid of def.value) {
const candidate = data.completeAst.idMap.get(vid);
if (candidate && candidate.type === type_1.RType.FunctionDefinition) {
linked = candidate;
// also mark that we call the resolved function-definition node
graph.addEdge(rootId, vid, edge_1.EdgeType.Calls);
break;
}
}
}
// we may find a candidate in the first check
if (linked?.type === type_1.RType.FunctionDefinition) {
try {
return (0, linker_1.linkArgumentsOnCall)(filteredArgs, linked.parameters, graph);
}
catch (e) {
logger_1.dataflowLogger.warn('Failed to link arguments to parameters for purr formula (symbol target), some bindings may be missing', { error: e });
}
}
}
}
catch (e) {
logger_1.dataflowLogger.warn('Failed to resolve symbol for purrr formula .f', { error: e });
}
}
/**
* Support for R's purr formula: `map(df, ~ .x + 1)`.
*/
function processPurrrFormula(name, args, rootId, data, config) {
const { information, callArgs } = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: built_in_proc_name_1.BuiltInProcName.PurrrFormula });
const params = {};
for (const key of Object.keys(config.args)) {
params[config.args[key].name] = config.args[key].name;
}
// formula parameter
params[config['.f'].name] = config['.f'].name;
params['...'] = '...';
const argMaps = (0, linker_1.pMatch)((0, common_1.convertFnArguments)(args), params);
const formulaArgId = argMaps.get(config['.f'].name)?.[0];
if (!formulaArgId) {
// nothing to do if we couldn't find the formula argument
return information;
}
const formulaArg = r_argument_1.RArgument.getWithId(args, formulaArgId);
const formulaNode = formulaArg ? (0, unpack_argument_1.unpackNonameArg)(formulaArg) : undefined;
if (!formulaNode) {
return information;
}
let argToParamMap = new Map();
// Prepare the list of call arguments to consider for linking (exclude the formula argument and ignored args)
const filteredCallArgs = [];
for (const arg of callArgs) {
const aid = graph_1.FunctionArgument.getId(arg);
if (aid === formulaArgId || aid === formulaNode.info.id || config.ignore?.includes(graph_1.FunctionArgument.getName(arg) ?? '')) {
continue;
}
filteredCallArgs.push(arg);
}
if (formulaNode.type === type_1.RType.FunctionDefinition) {
const fdef = formulaNode;
information.graph.addEdge(rootId, fdef.info.id, edge_1.EdgeType.Calls);
try {
argToParamMap = (0, linker_1.linkArgumentsOnCall)(filteredCallArgs, fdef.parameters, information.graph);
}
catch (e) {
logger_1.dataflowLogger.warn('Failed to link arguments to parameters for purr formula, some bindings may be missing', { error: e });
}
}
else if (formulaNode.type === type_1.RType.Symbol) {
linkOnSymbol(rootId, filteredCallArgs, formulaNode, information.graph, data);
}
else {
try {
df_helper_1.Dataflow.visitDfg(information.graph, formulaNode.info.id, (vtx) => {
if (vtx.tag === vertex_1.VertexType.FunctionCall) {
information.graph.addEdge(rootId, vtx.id, edge_1.EdgeType.Calls);
const targets = (0, linker_1.getAllFunctionCallTargets)(vtx.id, information.graph, vtx.environment);
for (const t of targets) {
information.graph.addEdge(rootId, t, edge_1.EdgeType.Calls);
const linked = data.completeAst.idMap.get(t);
if (linked && linked.type === type_1.RType.FunctionDefinition) {
try {
const map = (0, linker_1.linkArgumentsOnCall)(filteredCallArgs, linked.parameters, information.graph);
for (const [argId, paramId] of map.entries()) {
if (!argToParamMap.has(argId)) {
argToParamMap.set(argId, paramId);
}
}
}
catch (e) {
logger_1.dataflowLogger.warn('Failed to link arguments to parameters for purrr formula (list target)', { error: e });
}
}
}
return !vtx.origin.includes(built_in_proc_name_1.BuiltInProcName.List);
}
else if (vtx.tag === vertex_1.VertexType.Use) {
const node = data.completeAst.idMap.get(vtx.id);
if (node?.type === type_1.RType.Symbol) {
linkOnSymbol(rootId, filteredCallArgs, node, information.graph, data);
}
}
return false;
});
}
catch (e) {
logger_1.dataflowLogger.warn('Failed to traverse sub-dfg for purrr formula .f', { error: e });
}
}
const ignore = new Set(config.ignore ?? []);
model_1.RNode.visitAst(formulaNode, (node) => {
if (node.type === type_1.RType.Symbol) {
const sym = node;
const name = sym.content;
if (ignore.has(name)) {
return false;
}
const mappingKey = config.args[name]?.name;
if (mappingKey) {
const pid = (argMaps.get(mappingKey) ?? [])[0];
if (!pid) {
return false;
}
const arg = r_argument_1.RArgument.getWithId(args, pid);
const producedNode = arg ? (0, unpack_argument_1.unpackNonameArg)(arg) : undefined;
if (!producedNode) {
return false;
}
// If this argument was linked to a parameter on the call, skip adding edge
const resultId = producedNode.info.id;
if (!argToParamMap.has(resultId)) {
information.graph.addEdge(node.info.id, resultId, edge_1.EdgeType.Reads);
}
}
}
return false;
});
if (config.returnArg) {
const returnArgId = argMaps.get(config.returnArg)?.[0];
if (returnArgId) {
const returnArg = r_argument_1.RArgument.getWithId(args, returnArgId);
const producedNode = returnArg ? (0, unpack_argument_1.unpackNonameArg)(returnArg) : undefined;
if (producedNode) {
information.graph.addEdge(rootId, producedNode.info.id, edge_1.EdgeType.Returns);
}
}
}
return information;
}
//# sourceMappingURL=built-in-purrr-formula.js.map