UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

168 lines 8.29 kB
"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