@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
203 lines • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Quoted = void 0;
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 built_in_proc_name_1 = require("../../../../environments/built-in-proc-name");
const edge_1 = require("../../../../graph/edge");
const graph_1 = require("../../../../graph/graph");
const vertex_1 = require("../../../../graph/vertex");
const linker_1 = require("../../../linker");
const nse_1 = require("./nse");
const deferred_1 = require("./deferred");
const vertex_2 = require("../../../../graph/vertex");
/** Calls capturing a language object. */
const CapturingProcessors = [built_in_proc_name_1.BuiltInProcName.Quote];
/** Calls capturing an expression as a promise, forced at some later read of the variable they bind. */
const DelayingCalls = new Set(['delayedAssign']);
/** Calls evaluating one. */
const EvaluatingProcessors = [built_in_proc_name_1.BuiltInProcName.Eval];
/** How a value reaches its use, `DefinedByOnCall` being the hop from a parameter to its argument. */
const ValueFlow = edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.DefinedByOnCall | edge_1.EdgeType.Returns;
function hasOrigin(vertex, of) {
return vertex.origin !== 'unnamed' && vertex.origin.some(o => of.includes(o));
}
/**
* A language object reads nothing where it is written and everything where it reaches `eval`, with the bindings
* in effect there. Working on the finished graph makes assignments, branches, loops, and calls one traversal.
* @example
* ```ts
* Quoted.capturedBy(graph, id); // the expression a `quote(...)` holds on to
* Quoted.sourcesOf(graph, id); // every expression the value at `id` may hold
* ```
*/
exports.Quoted = {
name: 'Quoted',
/** The expression a capturing call holds on to. */
capturedBy(graph, id) {
const vertex = graph.getVertex(id);
return vertex_1.FunctionCallVertex.is(vertex) && hasOrigin(vertex, CapturingProcessors) ? capturedArgumentOf(graph, id) : undefined;
},
/** Every expression the value at `id` may hold, with the call that captured it. Several is normal. */
sourcesOf(graph, id) {
const found = [];
const seen = new Set([id]);
const pending = [id];
while (pending.length > 0) {
const current = pending.pop();
const captured = exports.Quoted.capturedBy(graph, current);
if (captured !== undefined) {
found.push({ expr: captured, at: current });
continue;
}
for (const [target, edge] of graph.outgoingEdges(current) ?? graph_1.NoEdges) {
if (edge_1.DfEdge.includesType(edge, ValueFlow) && !seen.has(target)) {
seen.add(target);
pending.push(target);
}
}
}
return found;
},
/** Links every name in `expr` against `environment` and hands back what stays unresolved. */
evaluateIn: linker_1.linkExpressionIn,
/**
* The finishing pass over a complete graph: it settles what a call really evaluates, which the call itself
* could not know. A capture reaches the `eval` that forces it, a promise reaches the bindings it may be
* forced against, and a masked name the caller binds after all loses its mark.
*/
finalize(graph, idMap, controlFlow) {
let names = undefined;
let cfg = null;
const cfgOnce = () => (cfg === null ? (cfg = controlFlow()) : cfg);
const masking = [];
for (const [id, vertex] of graph.verticesOfType(vertex_1.VertexType.FunctionCall)) {
const masks = nse_1.Nse.dropResolvedMask(graph, id, vertex.name);
if (masks !== undefined) {
masking.push(masks);
}
if (DelayingCalls.has(identifier_1.Identifier.getName(vertex.name))) {
const promise = capturedArgumentOf(graph, id);
const binding = promise === undefined ? undefined : boundBy(graph, id);
if (promise !== undefined) {
names ??= deferred_1.Deferred.indexOf(graph, idMap);
const flow = binding === undefined ? undefined : cfgOnce();
const sites = flow === undefined || binding === undefined ? undefined : deferred_1.Deferred.forcedAt(graph, binding, flow);
deferred_1.Deferred.link(graph, promise, names, idMap, flow !== undefined && sites?.length && binding !== undefined ? { cfg: flow, sites, binding } : undefined);
}
}
else if (vertex.environment !== undefined && hasOrigin(vertex, EvaluatingProcessors) && !evaluatesElsewhere(idMap.get(id))) {
names ??= deferred_1.Deferred.indexOf(graph, idMap);
resolveEvaluation(graph, vertex, vertex.environment, idMap, names, cfgOnce());
}
else {
for (const escaped of escapingArguments(graph, id)) {
names ??= deferred_1.Deferred.indexOf(graph, idMap);
deferred_1.Deferred.link(graph, escaped, names, idMap);
}
}
}
/* only once every mark has settled: the read a column gets makes it look bound from here on */
nse_1.Nse.linkMasksToData(graph, masking);
}
};
/** The name a delaying call binds, which is the definition its reads have to go through. */
function boundBy(graph, id) {
for (const [target, edge] of graph.ingoingEdges(id) ?? graph_1.NoEdges) {
if (edge_1.DfEdge.includesType(edge, edge_1.EdgeType.DefinedBy) && vertex_1.VariableDefinitionVertex.is(graph.getVertex(target))) {
return target;
}
}
return undefined;
}
/** The argument a call takes but does not evaluate, which is the one it captured. */
function capturedArgumentOf(graph, id) {
for (const [target, edge] of graph.outgoingEdges(id) ?? graph_1.NoEdges) {
if (edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Argument) && edge_1.DfEdge.includesType(edge, edge_1.EdgeType.NonStandardEvaluation)) {
return target;
}
}
return undefined;
}
/** Links a capture handed to an evaluating call, in that call's scope. */
function resolveEvaluation(graph, call, environment, idMap, names, cfg) {
const id = call.id;
const argument = call.args.find(a => !graph_1.FunctionArgument.isEmpty(a));
const reference = argument === undefined ? undefined : unwrapArgument(graph_1.FunctionArgument.getReference(argument), idMap);
const sources = reference === undefined ? [] : exports.Quoted.sourcesOf(graph, reference);
for (const { expr, at } of sources) {
const open = (0, linker_1.linkExpressionIn)(graph, expr, environment, idMap);
/* R falls through to the enclosing scope for names the evaluating frame does not bind */
const enclosing = open.length > 0 ? graph.getVertex(at)?.environment : undefined;
if (enclosing !== undefined) {
(0, linker_1.linkInputs)(open, enclosing, [], graph, false);
}
graph.addEdge(id, expr, edge_1.EdgeType.Returns);
deferred_1.Deferred.publish(graph, expr, names, idMap, id, cfg);
/* the capture said "not evaluated here", and being handed to `eval` settles that it is */
nse_1.Nse.unmark(graph, at);
}
if (sources.length > 0) {
forgetUnknownSideEffect(graph, id);
}
}
/**
* The arguments of `id` whose promise outlives the call: the callee hands back a closure and never reads the
* parameter outside it, so R forces them wherever that closure is called, not here. A parameter read in the
* body itself (what `force` is for) is settled during the call and stays where it is.
*/
function* escapingArguments(graph, id) {
for (const [target, edge] of graph.outgoingEdges(id) ?? graph_1.NoEdges) {
if (!edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Calls)) {
continue;
}
const callee = graph.getVertex(target);
if (!vertex_2.FunctionDefinitionVertex.is(callee)) {
continue;
}
for (const exit of callee.exitPoints) {
const closure = graph.getVertex(exit.nodeId);
if (!vertex_2.FunctionDefinitionVertex.is(closure)) {
continue;
}
for (const key of Object.keys(callee.params)) {
const parameter = node_id_1.NodeId.normalize(key);
let escapes = true;
for (const [reader, edge] of graph.ingoingEdges(parameter) ?? graph_1.NoEdges) {
if (edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Reads) && !closure.subflow.graph.has(reader)) {
escapes = false;
break;
}
}
if (!escapes) {
continue;
}
for (const [argument, edge] of graph.outgoingEdges(parameter) ?? graph_1.NoEdges) {
if (edge_1.DfEdge.includesType(edge, edge_1.EdgeType.DefinedByOnCall)) {
yield argument;
}
}
}
}
}
}
/** The value an argument reference points at. */
function unwrapArgument(reference, idMap) {
const node = reference === undefined ? undefined : idMap.get(reference);
return node?.type === type_1.RType.Argument ? node.value?.info.id : reference;
}
/** `eval(expr, envir)` runs elsewhere, so the bindings here say nothing. */
function evaluatesElsewhere(call) {
return r_function_call_1.RFunctionCall.isNamed(call) && call.arguments.length > 1;
}
/** Drops the marker the call carries while we cannot say what it evaluates. */
function forgetUnknownSideEffect(graph, id) {
for (const unknown of graph.unknownSideEffects) {
if (graph_1.UnknownSideEffect.id(unknown) === id) {
graph.unknownSideEffects.delete(unknown);
}
}
}
//# sourceMappingURL=quoted.js.map