@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
168 lines • 8.29 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertFnArguments = convertFnArguments;
exports.convertFnArgument = convertFnArgument;
exports.processAllArguments = processAllArguments;
exports.patchFunctionCall = patchFunctionCall;
const info_1 = require("../../../../info");
const processor_1 = require("../../../../processor");
const model_1 = require("../../../../../r-bridge/lang-4.x/ast/model/model");
const r_function_call_1 = require("../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const identifier_1 = require("../../../../environments/identifier");
const overwrite_1 = require("../../../../environments/overwrite");
const resolve_by_name_1 = require("../../../../environments/resolve-by-name");
const type_1 = require("../../../../../r-bridge/lang-4.x/ast/model/type");
const vertex_1 = require("../../../../graph/vertex");
const edge_1 = require("../../../../graph/edge");
const r_argument_1 = require("../../../../../r-bridge/lang-4.x/ast/model/nodes/r-argument");
function forceVertexArgumentValueReferences(rootId, value, graph, env) {
const valueVertex = graph.getVertex(value.entryPoint);
if (!valueVertex) {
return;
}
// link read if it is function definition directly and reference the exit point
if (valueVertex.tag === vertex_1.VertexType.FunctionDefinition) {
for (const exit of valueVertex.exitPoints) {
graph.addEdge(rootId, exit.nodeId, edge_1.EdgeType.Reads);
}
}
else if (valueVertex.tag !== vertex_1.VertexType.Value) {
for (const exit of value.exitPoints) {
graph.addEdge(rootId, exit.nodeId, edge_1.EdgeType.Reads);
}
}
const containedSubflowIn = graph.verticesOfType(vertex_1.VertexType.FunctionDefinition)
.flatMap(([, info]) => info.subflow.in);
// try to resolve them against the current environment
for (const l of [value.in, containedSubflowIn]) {
for (const ref of l) {
if (ref.name) {
const refId = ref.nodeId;
const resolved = (0, resolve_by_name_1.resolveByName)(ref.name, env, ref.type) ?? [];
for (const resolve of resolved) {
graph.addEdge(refId, resolve.nodeId, edge_1.EdgeType.Reads);
}
}
}
}
}
/**
* Converts function arguments into function argument references for a function call vertex.
* Please be aware, that the ids here are those inferred from the AST, not from the dataflow graph!
* This function also works after the arguments were unpacked, e.g., by {@link tryUnpackNoNameArg}.
* @see convertFnArgument
*/
function convertFnArguments(args) {
return args.map(convertFnArgument);
}
/**
* Transforms a function argument into a function argument reference for a function call vertex.
* Please be aware, that the ids here are those inferred from the AST, not from the dataflow graph!
*/
function convertFnArgument(arg) {
if (arg === r_function_call_1.EmptyArgument) {
return r_function_call_1.EmptyArgument;
}
else if (!arg.name || arg.type !== type_1.RType.Argument) {
return { nodeId: arg.info.id, cds: undefined, type: identifier_1.ReferenceType.Argument };
}
else {
return {
nodeId: arg.info.id,
valueId: arg.value?.info.id,
name: arg.name.content,
cds: undefined,
type: identifier_1.ReferenceType.Argument
};
}
}
/**
* Processes all arguments for a function call, updating the given final graph and environment.
*/
function processAllArguments({ functionName, args, data, finalGraph, functionRootId, forceArgs = [], patchData }) {
let finalEnv = functionName.environment;
// arg env contains the environments with other args defined
let argEnv = functionName.environment;
const callArgs = [];
const processedArguments = [];
const remainingReadInArgs = [];
let i = -1;
for (const arg of args) {
i++;
data = patchData?.(data, i) ?? data;
if (arg === r_function_call_1.EmptyArgument) {
callArgs.push(r_function_call_1.EmptyArgument);
processedArguments.push(undefined);
continue;
}
const processed = (0, processor_1.processDataflowFor)(arg, { ...data, environment: argEnv });
if (r_argument_1.RArgument.isWithValue(arg) && (forceArgs === 'all' || forceArgs[i]) && !model_1.RConstant.is(arg.value)) {
forceVertexArgumentValueReferences(functionRootId, processed, processed.graph, argEnv);
}
processedArguments.push(processed);
finalEnv = (0, overwrite_1.overwriteEnvironment)(finalEnv, processed.environment);
finalGraph.mergeWith(processed.graph);
// resolve reads within argument, we resolve before adding the `processed.environment` to avoid cyclic dependencies
for (const l of [processed.in, processed.unknownReferences]) {
for (const ingoing of l) {
// check if it is called directly
const inId = ingoing.nodeId;
const refType = finalGraph.getVertex(inId)?.tag === vertex_1.VertexType.FunctionCall ? identifier_1.ReferenceType.Function : identifier_1.ReferenceType.Unknown;
const tryToResolve = ingoing.name ? (0, resolve_by_name_1.resolveByName)(ingoing.name, argEnv, refType) : undefined;
if (tryToResolve === undefined) {
remainingReadInArgs.push(ingoing);
}
else {
/* maybe all targets are not definitely of the current scope and should be still kept */
let assumeItMayHaveAHigherTarget = true;
for (const resolved of tryToResolve) {
if ((0, identifier_1.isReferenceType)(resolved.type, identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.BuiltInConstant)) {
continue;
}
else if ((0, info_1.happensInEveryBranch)(resolved.cds)) {
assumeItMayHaveAHigherTarget = false;
}
finalGraph.addEdge(inId, resolved.nodeId, edge_1.EdgeType.Reads);
}
if (assumeItMayHaveAHigherTarget) {
remainingReadInArgs.push(ingoing);
}
}
}
}
argEnv = (0, overwrite_1.overwriteEnvironment)(argEnv, processed.environment);
if (arg.type !== type_1.RType.Argument || !arg.name) {
callArgs.push({ nodeId: processed.entryPoint, cds: undefined, type: identifier_1.ReferenceType.Argument });
}
else {
callArgs.push({ nodeId: processed.entryPoint, valueId: arg.value?.info.id, name: arg.name.content, cds: undefined, type: identifier_1.ReferenceType.Argument });
}
finalGraph.addEdge(functionRootId, processed.entryPoint, edge_1.EdgeType.Argument);
}
return { finalEnv, callArgs, remainingReadInArgs, processedArguments };
}
/**
* Patches a function call vertex into the given dataflow graph.
* This is mostly useful for built-in processors that have custom argument processing.
* Otherwise, rely on {@link processKnownFunctionCall} instead.
*/
function patchFunctionCall({ nextGraph, rootId, name, data, argumentProcessResult, origin, link }) {
nextGraph.addVertex({
tag: vertex_1.VertexType.FunctionCall,
id: rootId,
name: name.content,
environment: data.environment,
/* will be overwritten accordingly */
onlyBuiltin: false,
cds: data.cds,
args: argumentProcessResult.map(arg => arg === undefined ? r_function_call_1.EmptyArgument : { nodeId: arg.entryPoint, cds: undefined, call: undefined, type: identifier_1.ReferenceType.Argument }),
origin: [origin],
link
}, data.ctx.env.makeCleanEnv(), !nextGraph.hasVertex(rootId) || nextGraph.isRoot(rootId), true);
for (const arg of argumentProcessResult) {
if (arg) {
nextGraph.addEdge(rootId, arg.entryPoint, edge_1.EdgeType.Argument);
}
}
}
//# sourceMappingURL=common.js.map