UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

302 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processAssignment = processAssignment; exports.markAsAssignment = markAsAssignment; const known_call_handling_1 = require("../known-call-handling"); const log_1 = require("../../../../../../util/log"); const unpack_argument_1 = require("../argument/unpack-argument"); const process_named_call_1 = require("../../../process-named-call"); const make_argument_1 = require("../argument/make-argument"); const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type"); const logger_1 = require("../../../../../logger"); const identifier_1 = require("../../../../../environments/identifier"); const overwrite_1 = require("../../../../../environments/overwrite"); const retriever_1 = require("../../../../../../r-bridge/retriever"); const vertex_1 = require("../../../../../graph/vertex"); const define_1 = require("../../../../../environments/define"); const edge_1 = require("../../../../../graph/edge"); const resolve_by_name_1 = require("../../../../../environments/resolve-by-name"); const containers_1 = require("../../../../../../util/containers"); const config_1 = require("../../../../../../config"); const named_call_handling_1 = require("../named-call-handling"); const built_in_1 = require("../../../../../environments/built-in"); const unknown_side_effect_1 = require("../../../../../graph/unknown-side-effect"); const alias_tracking_1 = require("../../../../../eval/resolve/alias-tracking"); function toReplacementSymbol(target, prefix, superAssignment) { return { type: type_1.RType.Symbol, info: target.info, /* they are all mapped to `<-` in R, but we mark super as well */ content: `${prefix}${superAssignment ? '<<-' : '<-'}`, lexeme: target.lexeme, location: target.location, namespace: undefined }; } function getEffectiveOrder(config, args) { return config.swapSourceAndTarget ? [args[1], args[0]] : args; } function findRootAccess(node) { let current = node; while (current.type === type_1.RType.Access) { current = current.accessed; } if (current.type === type_1.RType.Symbol) { return current; } else { return undefined; } } function tryReplacementPassingIndices(rootId, functionName, data, name, args, indices) { const resolved = (0, resolve_by_name_1.resolveByName)(functionName.content, data.environment, identifier_1.ReferenceType.Function) ?? []; // yield for unsupported pass along! if (resolved.length !== 1 || resolved[0].type !== identifier_1.ReferenceType.BuiltInFunction) { return (0, process_named_call_1.processAsNamedCall)(functionName, data, name, args); } const info = built_in_1.BuiltInProcessorMapper['builtin:replacement']({ type: type_1.RType.Symbol, info: functionName.info, content: name, lexeme: functionName.lexeme, location: functionName.location, namespace: undefined }, (0, make_argument_1.wrapArgumentsUnnamed)(args, data.completeAst.idMap), functionName.info.id, data, { ...(resolved[0].config ?? {}), activeIndices: indices, assignRootId: rootId }); (0, named_call_handling_1.markAsOnlyBuiltIn)(info.graph, functionName.info.id); return info; } /** * Processes an assignment, i.e., `<target> <- <source>`. * Handling it as a function call \`&lt;-\` `(<target>, <source>)`. * This includes handling of replacement functions (e.g., `names(x) <- ...` as \`names&lt;-\` `(x, ...)`). */ function processAssignment(name, /* we expect them to be ordered in the sense that we have (source, target): `<source> <- <target>` */ args, rootId, data, config) { if (!config.mayHaveMoreArgs && args.length !== 2) { logger_1.dataflowLogger.warn(`Assignment ${name.content} has something else than 2 arguments, skipping`); return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs, origin: 'default' }).information; } const effectiveArgs = getEffectiveOrder(config, args); const { target, source } = extractSourceAndTarget(effectiveArgs); if (target === undefined || source === undefined) { logger_1.dataflowLogger.warn(`Assignment ${name.content} has an undefined target or source, skipping`); return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs, origin: 'default' }).information; } const { type, named } = target; if (!config.targetVariable && type === type_1.RType.Symbol) { const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, reverseOrder: !config.swapSourceAndTarget, forceArgs: config.forceArgs, origin: 'builtin:assignment' }); return processAssignmentToSymbol({ ...config, nameOfAssignmentFunction: name.content, source, target, args: getEffectiveOrder(config, res.processedArguments), rootId, data, information: res.information, }); } else if (config.canBeReplacement && type === type_1.RType.FunctionCall && named) { /* as replacement functions take precedence over the lhs fn-call (i.e., `names(x) <- ...` is independent from the definition of `names`), we do not have to process the call */ logger_1.dataflowLogger.debug(`Assignment ${name.content} has a function call as target ==> replacement function ${target.lexeme}`); const replacement = toReplacementSymbol(target, target.functionName.content, config.superAssignment ?? false); return tryReplacementPassingIndices(rootId, replacement, data, replacement.content, [...target.arguments, source], config.indicesCollection); } else if (config.canBeReplacement && type === type_1.RType.Access) { logger_1.dataflowLogger.debug(`Assignment ${name.content} has an access-type node as target ==> replacement function ${target.lexeme}`); const replacement = toReplacementSymbol(target, target.operator, config.superAssignment ?? false); return tryReplacementPassingIndices(rootId, replacement, data, replacement.content, [(0, make_argument_1.toUnnamedArgument)(target.accessed, data.completeAst.idMap), ...target.access, source], config.indicesCollection); } else if (type === type_1.RType.Access) { const rootArg = findRootAccess(target); if (rootArg) { const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: [rootArg, source], rootId, data, reverseOrder: !config.swapSourceAndTarget, forceArgs: config.forceArgs, origin: 'builtin:assignment' }); return processAssignmentToSymbol({ ...config, nameOfAssignmentFunction: name.content, source, target: rootArg, args: getEffectiveOrder(config, res.processedArguments), rootId, data, information: res.information, }); } } else if (type === type_1.RType.String) { return processAssignmentToString(target, args, name, rootId, data, config, source); } logger_1.dataflowLogger.warn(`Assignment ${name.content} has an unknown target type ${target.type} => unknown impact`); const info = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: effectiveArgs, rootId, data, forceArgs: config.forceArgs, origin: 'builtin:assignment' }).information; (0, unknown_side_effect_1.handleUnknownSideEffect)(info.graph, info.environment, rootId); return info; } function extractSourceAndTarget(args) { const source = (0, unpack_argument_1.unpackArgument)(args[1], false); const target = (0, unpack_argument_1.unpackArgument)(args[0], false); return { source, target }; } /** * Promotes the ingoing/unknown references of target (an assignment) to definitions */ function produceWrittenNodes(rootId, target, referenceType, data, makeMaybe, value) { return [...target.in, ...target.unknownReferences].map(ref => ({ ...ref, type: referenceType, definedAt: rootId, controlDependencies: data.controlDependencies ?? (makeMaybe ? [] : undefined), value: value })); } function processAssignmentToString(target, args, name, rootId, data, config, source) { const symbol = { type: type_1.RType.Symbol, info: target.info, content: (0, retriever_1.removeRQuotes)(target.lexeme), lexeme: target.lexeme, location: target.location, namespace: undefined }; // treat first argument to Symbol const mappedArgs = config.swapSourceAndTarget ? [args[0], { ...args[1], value: symbol }] : [{ ...args[0], value: symbol }, args[1]]; const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: mappedArgs, rootId, data, reverseOrder: !config.swapSourceAndTarget, forceArgs: config.forceArgs, origin: 'builtin:assignment' }); return processAssignmentToSymbol({ ...config, nameOfAssignmentFunction: name.content, source, target: symbol, args: getEffectiveOrder(config, res.processedArguments), rootId, data, information: res.information }); } function checkTargetReferenceType(source, sourceInfo) { const vert = sourceInfo.graph.getVertex(source.info.id, true); switch (vert?.tag) { case vertex_1.VertexType.FunctionDefinition: return identifier_1.ReferenceType.Function; case vertex_1.VertexType.Use: case vertex_1.VertexType.FunctionCall: return identifier_1.ReferenceType.Unknown; default: return identifier_1.ReferenceType.Variable; } } /** * Consider a call like `x <- v` * @param information - the information to define the assignment within * @param nodeToDefine - `x` * @param sourceIds - `v` * @param rootIdOfAssignment - `<-` * @param config - configuration for the assignment processing */ function markAsAssignment(information, nodeToDefine, sourceIds, rootIdOfAssignment, config) { if ((0, config_1.getConfig)().solver.pointerTracking) { let indicesCollection = undefined; if (sourceIds.length === 1) { // support for tracking indices. // Indices were defined for the vertex e.g. a <- list(c = 1) or a$b <- list(c = 1) indicesCollection = information.graph.getVertex(sourceIds[0])?.indicesCollection; // support assignment of container e.g. container1 <- container2 // defined indices are passed if (!indicesCollection) { const node = information.graph.idMap?.get(sourceIds[0]); if (node && node.type === type_1.RType.Symbol) { indicesCollection = (0, containers_1.resolveIndicesByName)(node.lexeme, information.environment); } } } // Indices defined by replacement operation e.g. $<- if (config?.indicesCollection !== undefined) { // If there were indices stored in the vertex, then a container was defined // and assigned to the index of another container e.g. a$b <- list(c = 1) if (indicesCollection) { indicesCollection = (0, containers_1.addSubIndicesToLeafIndices)(config.indicesCollection, indicesCollection); } else { // No indices were defined for the vertex e.g. a$b <- 2 indicesCollection = config.indicesCollection; } } nodeToDefine.indicesCollection ??= indicesCollection; } information.environment = (0, define_1.define)(nodeToDefine, config?.superAssignment, information.environment); information.graph.setDefinitionOfVertex(nodeToDefine); if (!config?.quoteSource) { for (const sourceId of sourceIds) { information.graph.addEdge(nodeToDefine, sourceId, edge_1.EdgeType.DefinedBy); } } information.graph.addEdge(nodeToDefine, rootIdOfAssignment, edge_1.EdgeType.DefinedBy); // kinda dirty, but we have to remove existing read edges for the symbol, added by the child const out = information.graph.outgoingEdges(nodeToDefine.nodeId); for (const [id, edge] of (out ?? [])) { edge.types &= ~edge_1.EdgeType.Reads; if (edge.types === 0) { out?.delete(id); } } } /** * Helper function whenever it is known that the _target_ of an assignment is a (single) symbol (i.e. `x <- ...`, but not `names(x) <- ...`). */ function processAssignmentToSymbol(config) { const { nameOfAssignmentFunction, source, args: [targetArg, sourceArg], target, rootId, data, information, makeMaybe, quoteSource } = config; const referenceType = checkTargetReferenceType(source, sourceArg); const aliases = (0, alias_tracking_1.getAliases)([source.info.id], information.graph, information.environment); const writeNodes = produceWrittenNodes(rootId, targetArg, referenceType, data, makeMaybe ?? false, aliases); if (writeNodes.length !== 1 && log_1.log.settings.minLevel <= 4 /* LogLevel.Warn */) { log_1.log.warn(`Unexpected write number in assignment: ${JSON.stringify(writeNodes)}`); } // we drop the first arg which we use to pass along arguments :D const readFromSourceWritten = sourceArg.out.slice(1); const readTargets = [ { nodeId: rootId, name: nameOfAssignmentFunction, controlDependencies: data.controlDependencies, type: identifier_1.ReferenceType.Function }, ...sourceArg.unknownReferences, ...sourceArg.in, ...targetArg.in.filter(i => i.nodeId !== target.info.id), ...readFromSourceWritten ]; information.environment = (0, overwrite_1.overwriteEnvironment)(targetArg.environment, sourceArg.environment); // install assigned variables in environment for (const write of writeNodes) { markAsAssignment(information, write, [source.info.id], rootId, config); } information.graph.addEdge(rootId, targetArg.entryPoint, edge_1.EdgeType.Returns); if (quoteSource) { information.graph.addEdge(rootId, source.info.id, edge_1.EdgeType.NonStandardEvaluation); } return { ...information, unknownReferences: [], entryPoint: rootId, in: readTargets, out: [...writeNodes, ...readFromSourceWritten] }; } //# sourceMappingURL=built-in-assignment.js.map