UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

181 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processExpressionList = processExpressionList; const info_1 = require("../../../../../info"); const processor_1 = require("../../../../../processor"); const linker_1 = require("../../../../linker"); const assert_1 = require("../../../../../../util/assert"); const unpack_argument_1 = require("../argument/unpack-argument"); const common_1 = require("../common"); const environment_1 = require("../../../../../environments/environment"); const graph_1 = require("../../../../../graph/graph"); const identifier_1 = require("../../../../../environments/identifier"); const resolve_by_name_1 = require("../../../../../environments/resolve-by-name"); const edge_1 = require("../../../../../graph/edge"); const vertex_1 = require("../../../../../graph/vertex"); const scoping_1 = require("../../../../../environments/scoping"); const built_in_1 = require("../../../../../environments/built-in"); const overwrite_1 = require("../../../../../environments/overwrite"); const logger_1 = require("../../../../../logger"); const log_1 = require("../../../../../../util/log"); const dotDotDotAccess = /^\.\.\d+$/; function linkReadNameToWriteIfPossible(read, environments, listEnvironments, remainingRead, nextGraph) { const readName = read.name && dotDotDotAccess.test(read.name) ? '...' : read.name; const probableTarget = readName ? (0, resolve_by_name_1.resolveByName)(readName, environments, read.type) : undefined; // record if at least one has not been defined if (probableTarget === undefined || probableTarget.some(t => !listEnvironments.has(t.nodeId) || !(0, info_1.happensInEveryBranch)(t.controlDependencies))) { const has = remainingRead.get(readName); if (has) { if (!has?.some(h => h.nodeId === read.nodeId && h.name === read.name && h.controlDependencies === read.controlDependencies)) { has.push(read); } } else { remainingRead.set(readName, [read]); } } // keep it, for we have no target, as read-ids are unique within the same fold, this should work for same links // we keep them if they are defined outside the current parent and maybe throw them away later if (probableTarget === undefined) { return; } for (const target of probableTarget) { // we can stick with maybe even if readId.attribute is always nextGraph.addEdge(read, target, edge_1.EdgeType.Reads); if ((read.type === identifier_1.ReferenceType.Function || read.type === identifier_1.ReferenceType.BuiltInFunction) && ((0, built_in_1.isBuiltIn)(target.definedAt))) { nextGraph.addEdge(read, target, edge_1.EdgeType.Calls); } } } function processNextExpression(currentElement, environment, listEnvironments, remainingRead, nextGraph) { // all inputs that have not been written until now are read! for (const read of [...currentElement.in, ...currentElement.unknownReferences]) { linkReadNameToWriteIfPossible(read, environment, listEnvironments, remainingRead, nextGraph); } } function updateSideEffectsForCalledFunctions(calledEnvs, inputEnvironment, nextGraph) { for (const { functionCall, called } of calledEnvs) { const callDependencies = nextGraph.getVertex(functionCall, true)?.cds; for (const calledFn of called) { (0, assert_1.guard)(calledFn.tag === vertex_1.VertexType.FunctionDefinition, 'called function must be a function definition'); // only merge the environments they have in common let environment = calledFn.environment; while (environment.level > inputEnvironment.level) { environment = (0, scoping_1.popLocalEnvironment)(environment); } // update alle definitions to be defined at this function call let current = environment.current; let hasUpdate = false; while (current !== undefined && current.id !== environment_1.BuiltInEnvironment.id) { for (const definitions of current.memory.values()) { for (const def of definitions) { if (!(0, built_in_1.isBuiltIn)(def.definedAt)) { hasUpdate = true; nextGraph.addEdge(def.nodeId, functionCall, edge_1.EdgeType.SideEffectOnCall); } } } current = current.parent; } if (hasUpdate) { // we update all definitions to be linked with the corresponding function call inputEnvironment = (0, overwrite_1.overwriteEnvironment)(inputEnvironment, environment, callDependencies); } } } return inputEnvironment; } function processExpressionList(name, args, rootId, data) { const expressions = args.map(e => (0, unpack_argument_1.unpackArgument)(e)); (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `[expr list] with ${expressions.length} expressions`); let { environment } = data; // used to detect if a "write" happens within the same expression list const listEnvironments = new Set(); const remainingRead = new Map(); const nextGraph = new graph_1.DataflowGraph(data.completeAst.idMap); let out = []; const exitPoints = []; let expressionCounter = 0; const processedExpressions = []; let defaultReturnExpr = undefined; for (const expression of expressions) { (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `processing expression ${++expressionCounter} of ${expressions.length}`); if (expression === undefined) { processedExpressions.push(undefined); continue; } // use the current environments for processing data = { ...data, environment: environment }; const processed = (0, processor_1.processDataflowFor)(expression, data); processedExpressions.push(processed); nextGraph.mergeWith(processed.graph); defaultReturnExpr = processed; // if the expression contained next or break anywhere before the next loop, the "overwrite" should be an "append", because we do not know if the rest is executed // update the environments for the next iteration with the previous writes if (exitPoints.length > 0) { processed.out = (0, environment_1.makeAllMaybe)(processed.out, nextGraph, processed.environment, true); processed.in = (0, environment_1.makeAllMaybe)(processed.in, nextGraph, processed.environment, false); processed.unknownReferences = (0, environment_1.makeAllMaybe)(processed.unknownReferences, nextGraph, processed.environment, false); } (0, info_1.addNonDefaultExitPoints)(exitPoints, processed.exitPoints); out = out.concat(processed.out); (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `expression ${expressionCounter} of ${expressions.length} has ${processed.unknownReferences.length} unknown nodes`); processNextExpression(processed, environment, listEnvironments, remainingRead, nextGraph); environment = exitPoints.length > 0 ? (0, overwrite_1.overwriteEnvironment)(environment, processed.environment) : processed.environment; const calledEnvs = (0, linker_1.linkFunctionCalls)(nextGraph, data.completeAst.idMap, processed.graph); // if the called function has global redefinitions, we have to keep them within our environment environment = updateSideEffectsForCalledFunctions(calledEnvs, environment, nextGraph); for (const { nodeId } of processed.out) { listEnvironments.add(nodeId); } /** if at least built-one of the exit points encountered happens unconditionally, we exit here (dead code)! */ if ((0, info_1.alwaysExits)(processed)) { /* if there is an always-exit expression, there is no default return active anymore */ defaultReturnExpr = undefined; break; } } logger_1.dataflowLogger.trace(`expression list exits with ${remainingRead.size} remaining read names`); if (defaultReturnExpr) { exitPoints.push({ type: 0 /* ExitPointType.Default */, nodeId: defaultReturnExpr.entryPoint, controlDependencies: data.controlDependencies }); } const ingoing = [...remainingRead.values()].flat(); const rootNode = data.completeAst.idMap.get(rootId); const withGroup = rootNode?.grouping; if (withGroup) { ingoing.push({ nodeId: rootId, name: name.content, controlDependencies: data.controlDependencies, type: identifier_1.ReferenceType.Function }); (0, common_1.patchFunctionCall)({ nextGraph, rootId, name, data, argumentProcessResult: processedExpressions, origin: 'builtin:expression-list' }); nextGraph.addEdge(rootId, (0, built_in_1.builtInId)('{'), edge_1.EdgeType.Reads | edge_1.EdgeType.Calls); // process all exit points as potential returns: for (const exit of exitPoints) { if (exit.type === 1 /* ExitPointType.Return */ || exit.type === 0 /* ExitPointType.Default */) { nextGraph.addEdge(rootId, exit.nodeId, edge_1.EdgeType.Returns); } } } const meId = withGroup ? rootId : (processedExpressions.find(assert_1.isNotUndefined)?.entryPoint ?? rootId); return { /* no active nodes remain, they are consumed within the remaining read collection */ unknownReferences: [], in: ingoing, out, environment: environment, graph: nextGraph, /* if we have no group, we take the last evaluated expr */ entryPoint: meId, exitPoints: withGroup ? [{ nodeId: rootId, type: 0 /* ExitPointType.Default */, controlDependencies: data.controlDependencies }] : exitPoints }; } //# sourceMappingURL=built-in-expression-list.js.map