UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

203 lines 10.3 kB
"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