UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

366 lines 17.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processAssignmentLike = processAssignmentLike; 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 r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); 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 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"); const r_value_1 = require("../../../../../eval/values/r-value"); const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name"); 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: identifier_1.Identifier.mapName(prefix, n => n + (superAssignment ? '<<-' : '<-')), lexeme: target.lexeme, location: target.location }; } 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 tryReplacement(rootId, functionName, data, name, args) { 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[built_in_proc_name_1.BuiltInProcName.Replacement]({ type: type_1.RType.Symbol, info: functionName.info, content: name, lexeme: functionName.lexeme, location: functionName.location }, (0, make_argument_1.wrapArgumentsUnnamed)(args, data.completeAst.idMap), functionName.info.id, data, { ...resolved[0].config, assignRootId: rootId }); (0, named_call_handling_1.markAsOnlyBuiltIn)(info.graph, functionName.info.id); return info; } /** * In contrast to `processAssignment`, this function allows more flexible handling of assignment-like functions. */ function processAssignmentLike(name, /* we expect them to be ordered in the sense that we have (source, target): `<source> <- <target>` */ args, rootId, data, config) { const argsWithNames = new Map(); const argsWithoutNames = []; for (const arg of args) { const name = arg === r_function_call_1.EmptyArgument ? undefined : arg.name?.content; if (name === undefined) { argsWithoutNames.push(arg); } else { argsWithNames.set(name, arg); } } const source = argsWithNames.get(config.source.name) ?? (config.source.idx === undefined ? undefined : argsWithoutNames[config.source.idx]); const target = argsWithNames.get(config.target.name) ?? (config.target.idx === undefined ? undefined : argsWithoutNames[config.target.idx]); if (source && target) { args = [target, source]; } return processAssignment(name, args, rootId, data, { ...config, mayHaveMoreArgs: true }); } /** * 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 ${identifier_1.Identifier.toString(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 ${identifier_1.Identifier.toString(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 (type === type_1.RType.Symbol) { if (!config.targetVariable) { const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, reverseOrder: !config.swapSourceAndTarget, forceArgs: config.forceArgs, origin: built_in_proc_name_1.BuiltInProcName.Assignment }); return processAssignmentToSymbol({ ...config, nameOfAssignmentFunction: name.content, source, targetId: target.info.id, args: getEffectiveOrder(config, res.processedArguments), rootId, data, information: res.information, }); } else { // try to resolve the variable first const n = (0, alias_tracking_1.resolveIdToValue)(target.info.id, { environment: data.environment, resolve: data.ctx.config.solver.variables, idMap: data.completeAst.idMap, full: true, ctx: data.ctx }); if (n.type === 'set' && n.elements.length === 1 && n.elements[0].type === 'string') { const val = n.elements[0].value; if ((0, r_value_1.isValue)(val)) { const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, reverseOrder: !config.swapSourceAndTarget, forceArgs: config.forceArgs, origin: built_in_proc_name_1.BuiltInProcName.Assignment }); return processAssignmentToSymbol({ ...config, nameOfAssignmentFunction: name.content, source, targetId: target.info.id, targetName: val.str, 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 ${identifier_1.Identifier.toString(name.content)} has a function call as target ==> replacement function ${target.lexeme}`); const replacement = toReplacementSymbol(target, target.functionName.content, config.superAssignment ?? false); return tryReplacement(rootId, replacement, data, replacement.content, [...target.arguments, source]); } else if (config.canBeReplacement && type === type_1.RType.Access) { logger_1.dataflowLogger.debug(`Assignment ${identifier_1.Identifier.toString(name.content)} has an access-type node as target ==> replacement function ${target.lexeme}`); const replacement = toReplacementSymbol(target, target.operator, config.superAssignment ?? false); return tryReplacement(rootId, replacement, data, replacement.content, [(0, make_argument_1.toUnnamedArgument)(target.accessed, data.completeAst.idMap), ...target.access, source]); } 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: built_in_proc_name_1.BuiltInProcName.Assignment }); return processAssignmentToSymbol({ ...config, nameOfAssignmentFunction: name.content, source, targetId: rootArg.info.id, 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 ${identifier_1.Identifier.toString(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: built_in_proc_name_1.BuiltInProcName.Assignment }).information; (0, unknown_side_effect_1.handleUnknownSideEffect)(info.graph, info.environment, rootId); return info; } function extractSourceAndTarget(args) { const source = (0, unpack_argument_1.unpackArg)(args[1]); const target = (0, unpack_argument_1.unpackArg)(args[0]); return { source, target }; } /** * Promotes the ingoing/unknown references of target (an assignment) to definitions */ function produceWrittenNodes(rootId, target, referenceType, data, makeMaybe, value) { const written = []; for (const refs of [target.in, target.unknownReferences]) { for (const ref of refs) { written.push({ nodeId: ref.nodeId, name: ref.name, type: referenceType, definedAt: rootId, cds: data.cds ?? (makeMaybe ? [] : undefined), value }); } } return written; } 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, }; // 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: built_in_proc_name_1.BuiltInProcName.Assignment }); return processAssignmentToSymbol({ ...config, nameOfAssignmentFunction: name.content, source, targetId: symbol.info.id, args: getEffectiveOrder(config, res.processedArguments), rootId, data, information: res.information }); } function checkTargetReferenceType(sourceInfo, fnModes) { const vert = sourceInfo.graph.getVertex(sourceInfo.entryPoint); switch (vert?.tag) { case vertex_1.VertexType.FunctionDefinition: if (fnModes && fnModes.length > 0) { vert.mode ??= []; for (const m of fnModes) { if (!vert.mode.includes(m)) { vert.mode.push(m); } } } 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 data - The dataflow analysis fold backpack * @param assignmentConfig - configuration for the assignment processing */ function markAsAssignment(information, nodeToDefine, sourceIds, rootIdOfAssignment, data, assignmentConfig) { information.environment = (0, define_1.define)(nodeToDefine, assignmentConfig?.superAssignment, information.environment); information.graph.setDefinitionOfVertex(nodeToDefine, sourceIds); const nid = nodeToDefine.nodeId; if (!assignmentConfig?.quoteSource) { for (const sourceId of sourceIds) { information.graph.addEdge(nid, sourceId, edge_1.EdgeType.DefinedBy); } } information.graph.addEdge(nid, 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], targetId, targetName, rootId, data, information, makeMaybe, quoteSource } = config; const referenceType = checkTargetReferenceType(sourceArg, config.modesForFn); const useSourceIds = [sourceArg.graph.hasVertex(source.info.id) ? source.info.id : sourceArg.entryPoint]; const aliases = (0, alias_tracking_1.getAliases)(useSourceIds, information.graph, information.environment); const writeNodes = targetName ? [{ nodeId: targetId, name: targetName, type: referenceType, definedAt: rootId, cds: data.cds ?? (makeMaybe ? [] : undefined), value: aliases }] : 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, cds: data.cds, type: identifier_1.ReferenceType.Function } ].concat(sourceArg.unknownReferences, sourceArg.in, targetName ? targetArg.in : targetArg.in.filter(i => i.nodeId !== targetId), readFromSourceWritten); information.environment = (0, overwrite_1.overwriteEnvironment)(sourceArg.environment, targetArg.environment); // install assigned variables in environment for (const write of writeNodes) { markAsAssignment(information, write, useSourceIds, rootId, data, config); } information.graph.addEdge(rootId, targetArg.entryPoint, edge_1.EdgeType.Returns); if (quoteSource) { information.graph.addEdge(rootId, source.info.id, edge_1.EdgeType.NonStandardEvaluation); } else { // we read the source information.graph.addEdge(rootId, source.info.id, edge_1.EdgeType.Reads); } return { environment: information.environment, graph: information.graph, exitPoints: information.exitPoints, hooks: information.hooks, unknownReferences: [], entryPoint: rootId, in: readTargets, out: writeNodes.concat(readFromSourceWritten), }; } //# sourceMappingURL=built-in-assignment.js.map