@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
150 lines • 7.59 kB
JavaScript
;
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