UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

150 lines 7.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Deferred = void 0; 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 type_1 = require("../../../../../r-bridge/lang-4.x/ast/model/type"); const node_id_1 = require("../../../../../r-bridge/lang-4.x/ast/model/processing/node-id"); const identifier_1 = require("../../../../environments/identifier"); const retriever_1 = require("../../../../../r-bridge/retriever"); const edge_1 = require("../../../../graph/edge"); const vertex_1 = require("../../../../graph/vertex"); const happens_before_1 = require("../../../../../control-flow/happens-before"); const logic_1 = require("../../../../../util/logic"); const graph_1 = require("../../../../graph/graph"); /** Drops the read of `target` from `use`, for a binding another write has replaced. */ function dropRead(graph, use, target) { graph.removeEdgeType(use, target, edge_1.EdgeType.Reads); } function add(index, name, id) { const known = index.get(name); if (known) { known.push(id); } else { index.set(name, [id]); } } /** The names a deferred expression touches, each with the node touching it and whether it is written. */ function namesWithin(expr, graph, idMap) { const node = idMap.get(expr); const names = []; if (node === undefined) { return names; } const callees = new Set(); model_1.RNode.visitAst(node, inner => { if (r_function_call_1.RFunctionCall.isNamed(inner)) { callees.add(inner.functionName.info.id); return false; } else if (inner.type !== type_1.RType.Symbol || callees.has(inner.info.id)) { return false; } const vertex = graph.getVertex(inner.info.id); if (vertex_1.UseVertex.is(vertex) || vertex_1.VariableDefinitionVertex.is(vertex)) { names.push([inner.info.id, identifier_1.Identifier.getName(inner.content), vertex_1.VariableDefinitionVertex.is(vertex)]); } return false; }); return names; } /** * An expression R evaluates at a time we cannot pin down: the body a `delayedAssign` binds, forced at some * later read of the name, or a promise a closure carries past the call that created it. * * Since the moment is open, every binding the expression may meet is a candidate, and symmetrically so: * a name it reads may read any definition of that name, and a name it writes may be read by any use of it. * That is the may-analysis both directions ask for, so nothing the expression really depends on, and nothing * really depending on it, can be missed. */ exports.Deferred = { name: 'Deferred', /** Where each name is bound and read, built once and shared by every deferred expression in the graph. */ indexOf(graph, idMap) { const definitions = new Map(); const uses = new Map(); for (const [type, index] of [[vertex_1.VertexType.VariableDefinition, definitions], [vertex_1.VertexType.Use, uses]]) { for (const [id] of graph.verticesOfType(type)) { const name = (0, node_id_1.recoverName)(id, idMap); if (name !== undefined) { add(index, name, id); } } } return { definitions, uses }; }, /** * The reads that may be the one forcing `binding`: every read of it that no other read always precedes, * since an earlier read would already have forced it and cached the value. */ forcedAt(graph, binding, cfg) { const reads = []; for (const [reader, edge] of graph.ingoingEdges(binding) ?? graph_1.NoEdges) { if (edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Reads)) { reads.push(reader); } } return reads.filter(r => !reads.some(other => other !== r && (0, happens_before_1.happensBefore)(cfg, other, r) === logic_1.Ternary.Always)); }, /** * Links the writes an expression performs to the uses that may observe them, for a call evaluating the * expression at a point control flow can pin down (`eval`). The reads are settled by the evaluating call * itself, against the environment in effect there, so only this direction is left open. */ publish(graph, expr, index, idMap, at, cfg) { const within = namesWithin(expr, graph, idMap); const own = new Set(within.map(([id]) => id)); for (const [node, name, writes] of within) { if (!writes) { continue; } for (const use of index.uses.get(name) ?? []) { /* only a use the evaluation may reach can see what it wrote */ if (own.has(use) || (cfg !== undefined && (0, happens_before_1.happensBefore)(cfg, at, use) === logic_1.Ternary.Never)) { continue; } graph.addEdge(use, node, edge_1.EdgeType.Reads); } } }, /** * Links the expression rooted at `expr` to the bindings it may meet when it is evaluated. Given the reads * that may force it, control flow rules out what no force can reach; without them every binding stays a * candidate. */ link(graph, expr, index, idMap, forces) { const within = namesWithin(expr, graph, idMap); const own = new Set(within.map(([id]) => id)); /* a binding matters only if some force can see it, and a use only if some force can reach it */ const seenByAForce = (definition) => forces === undefined || forces.sites.some(site => (0, happens_before_1.happensBefore)(forces.cfg, definition, site) !== logic_1.Ternary.Never); /* the forcing read yields the promise's value, so only reads after it observe what the promise wrote */ const reachedByAForce = (use) => forces === undefined || (!forces.sites.includes(use) && forces.sites.some(site => (0, happens_before_1.happensBefore)(forces.cfg, site, use) !== logic_1.Ternary.Never)); /* `delayedAssign` names its variable with a string literal, so the recovered name still carries quotes */ const bindingName = forces === undefined ? undefined : (0, retriever_1.removeRQuotes)((0, node_id_1.recoverName)(forces.binding, idMap) ?? ''); for (const [node, name, writes] of within) { if (writes) { /* a write of the bound name replaces the binding, so a read it definitely reaches sees only it */ const shadows = forces !== undefined && name === bindingName && graph.getVertex(node)?.cds === undefined; for (const use of index.uses.get(name) ?? []) { if (!own.has(use) && reachedByAForce(use)) { graph.addEdge(use, node, edge_1.EdgeType.Reads); if (shadows && forces.sites.some(site => (0, happens_before_1.happensBefore)(forces.cfg, site, use) === logic_1.Ternary.Always)) { dropRead(graph, use, forces.binding); } } } } else { for (const definition of index.definitions.get(name) ?? []) { if (seenByAForce(definition)) { graph.addEdge(node, definition, edge_1.EdgeType.Reads); } } } } } }; //# sourceMappingURL=deferred.js.map