UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

215 lines 9.61 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processAccess = processAccess; exports.symbolArgumentsToStrings = symbolArgumentsToStrings; const known_call_handling_1 = require("../known-call-handling"); const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const logger_1 = require("../../../../../logger"); const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type"); const edge_1 = require("../../../../../graph/edge"); const environment_1 = require("../../../../../environments/environment"); const built_in_1 = require("../../../../../environments/built-in"); const built_in_assignment_1 = require("./built-in-assignment"); const identifier_1 = require("../../../../../environments/identifier"); const vertex_1 = require("../../../../../graph/vertex"); const containers_1 = require("../../../../../../util/containers"); const config_1 = require("../../../../../../config"); function tableAssignmentProcessor(name, args, rootId, data, outInfo) { outInfo.definitionRootNodes.push(rootId); return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information; } /** * Processes different types of access operations. * * Example: * ```r * a[i] * a$foo * a[[i]] * a@foo * ``` */ function processAccess(name, args, rootId, data, config) { if (args.length < 2) { logger_1.dataflowLogger.warn(`Access ${name.content} has less than 2 arguments, skipping`); return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs }).information; } const head = args[0]; let fnCall; if (head === r_function_call_1.EmptyArgument) { // in this case we may be within a pipe fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs }); } else if (!config.treatIndicesAsString) { /* within an access operation which treats its fields, we redefine the table assignment ':=' as a trigger if this is to be treated as a definition */ // do we have a local definition that needs to be recovered? fnCall = processNumberBasedAccess(data, name, args, rootId, config, head); } else { fnCall = processStringBasedAccess(args, data, name, rootId, config); } const info = fnCall.information; if (head !== r_function_call_1.EmptyArgument) { info.graph.addEdge(name.info.id, fnCall.processedArguments[0]?.entryPoint ?? head.info.id, edge_1.EdgeType.Returns); } /* access always reads all of its indices */ for (const arg of fnCall.processedArguments) { if (arg) { info.graph.addEdge(name.info.id, arg.entryPoint, edge_1.EdgeType.Reads); } /* we include the read edges to the constant arguments as well so that they are included if necessary */ } return { ...info, /* * Keep active nodes in case of assignments etc. * We make them maybe as a kind of hack. * This way when using * ```ts * a[[1]] <- 3 * a[[2]] <- 4 * a * ``` * the read for a will use both accesses as potential definitions and not just the last one! */ unknownReferences: (0, environment_1.makeAllMaybe)(info.unknownReferences, info.graph, info.environment, false), entryPoint: rootId, /** it is, to be precise, the accessed element we want to map to maybe */ in: head === r_function_call_1.EmptyArgument ? info.in : info.in.map(ref => { if (ref.nodeId === head.value?.info.id) { return (0, environment_1.makeReferenceMaybe)(ref, info.graph, info.environment, false); } else { return ref; } }) }; } /** * Processes different types of number-based access operations. * * Example: * ```r * a[i] * a[[i]] * ``` */ function processNumberBasedAccess(data, name, args, rootId, config, head) { const existing = data.environment.current.memory.get(':='); const outInfo = { definitionRootNodes: [] }; data.environment.current.memory.set(':=', [{ type: identifier_1.ReferenceType.BuiltInFunction, definedAt: built_in_1.BuiltIn, controlDependencies: undefined, processor: (name, args, rootId, data) => tableAssignmentProcessor(name, args, rootId, data, outInfo), config: {}, name: ':=', nodeId: built_in_1.BuiltIn, }]); const fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs }); /* recover the environment */ if (existing !== undefined) { data.environment.current.memory.set(':=', existing); } if (head.value && outInfo.definitionRootNodes.length > 0) { (0, built_in_assignment_1.markAsAssignment)(fnCall.information, { type: identifier_1.ReferenceType.Variable, name: head.value.lexeme ?? '', nodeId: head.value.info.id, definedAt: rootId, controlDependencies: [] }, outInfo.definitionRootNodes, rootId); } if ((0, config_1.getConfig)().solver.pointerTracking) { referenceAccessedIndices(args, data, fnCall, rootId, true); } return fnCall; } function symbolArgumentsToStrings(args, firstIndexInclusive = 1, lastIndexInclusive = args.length - 1) { const newArgs = [...args]; // if the argument is a symbol, we convert it to a string for this perspective for (let i = firstIndexInclusive; i <= lastIndexInclusive; i++) { const arg = newArgs[i]; if (arg !== r_function_call_1.EmptyArgument && arg.value?.type === type_1.RType.Symbol) { newArgs[i] = { ...arg, value: { type: type_1.RType.String, info: arg.value.info, lexeme: arg.value.lexeme, location: arg.value.location, content: { quotes: 'none', str: arg.value.lexeme } } }; } } return newArgs; } /** * Processes different types of string-based access operations. * * Example: * ```r * a$foo * a@foo * ``` */ function processStringBasedAccess(args, data, name, rootId, config) { const newArgs = symbolArgumentsToStrings(args); const fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: newArgs, rootId, data, forceArgs: config.forceArgs }); if ((0, config_1.getConfig)().solver.pointerTracking) { referenceAccessedIndices(newArgs, data, fnCall, rootId, false); } return fnCall; } function referenceAccessedIndices(newArgs, data, fnCall, rootId, isIndexBasedAccess) { // Resolve access on the way up the fold const { accessedArg, accessArg } = (0, containers_1.getAccessOperands)(newArgs); if (accessedArg === undefined || accessArg === undefined) { return; } let accessedIndicesCollection; // If the accessedArg is a symbol, it's either a simple access or the base case of a nested access if (accessedArg.value?.type === type_1.RType.Symbol) { accessedIndicesCollection = (0, containers_1.resolveSingleIndex)(accessedArg, accessArg, data.environment, isIndexBasedAccess); } else { // Higher access call const underlyingAccessId = accessedArg.value?.info.id ?? -1; const vertex = fnCall.information.graph.getVertex(underlyingAccessId); const subIndices = vertex?.indicesCollection ?.flatMap(indices => indices.indices) ?.flatMap(index => index?.subIndices ?? []); if (subIndices) { accessedIndicesCollection = (0, containers_1.filterIndices)(subIndices, accessArg, isIndexBasedAccess); } } // Add indices to vertex afterward if (accessedIndicesCollection) { const vertex = fnCall.information.graph.getVertex(rootId); if (vertex) { vertex.indicesCollection = accessedIndicesCollection; } // When access has no access as parent, it's the top most const rootNode = data.completeAst.idMap.get(rootId); const parentNode = data.completeAst.idMap.get(rootNode?.info.parent ?? -1); if (parentNode?.type !== type_1.RType.Access) { // Only reference indices in top most access referenceIndices(accessedIndicesCollection, fnCall, rootId); } } } /** * Creates edges of type {@link EdgeType.Reads} to the accessed Indices and their sub-indices starting from * the node with {@link parentNodeId}. * * @param accessedIndicesCollection - All indices that were accessed by the access operation * @param fnCall - The {@link ProcessKnownFunctionCallResult} of the access operation * @param parentNodeId - {@link NodeId} of the parent from which the edge starts */ function referenceIndices(accessedIndicesCollection, fnCall, parentNodeId) { const accessedIndices = accessedIndicesCollection?.flatMap(indices => indices.indices); for (const accessedIndex of accessedIndices ?? []) { fnCall.information.graph.addEdge(parentNodeId, accessedIndex.nodeId, edge_1.EdgeType.Reads); const accessedSubIndices = (0, vertex_1.isParentContainerIndex)(accessedIndex) ? accessedIndex.subIndices : undefined; referenceIndices(accessedSubIndices, fnCall, accessedIndex.nodeId); } } //# sourceMappingURL=built-in-access.js.map