UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

150 lines 8.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.markAsOnlyBuiltIn = markAsOnlyBuiltIn; exports.processNamedCall = processNamedCall; const info_1 = require("../../../../info"); const known_call_handling_1 = require("./known-call-handling"); const append_1 = require("../../../../environments/append"); const node_id_1 = require("../../../../../r-bridge/lang-4.x/ast/model/processing/node-id"); const identifier_1 = require("../../../../environments/identifier"); const r_base_packages_1 = require("../../../../../util/r-base-packages"); const built_in_1 = require("../../../../environments/built-in"); const edge_1 = require("../../../../graph/edge"); const built_in_library_1 = require("./built-in/built-in-library"); const vertex_1 = require("../../../../graph/vertex"); const resolve_helper_1 = require("../../../../environments/resolve-helper"); const assert_1 = require("../../../../../util/assert"); function mergeInformation(info, newInfo) { if (info === undefined) { return newInfo; } return { unknownReferences: info.unknownReferences.concat(newInfo.unknownReferences), in: info.in.concat(newInfo.in), out: info.out.concat(newInfo.out), graph: info.graph.mergeWith(newInfo.graph), environment: (0, append_1.appendEnvironment)(info.environment, newInfo.environment), entryPoint: newInfo.entryPoint, exitPoints: info.exitPoints.concat(newInfo.exitPoints), hooks: info.hooks.concat(newInfo.hooks) }; } /** * The flowR definition of `pkg::fn` behind an export a signature database attached. Such an export binds the * name in a layer below the global environment, which hides the built-in environment underneath, and with it * how the call evaluates its arguments: without this lookup `library(dplyr); filter(df, id > 2)` loses the data * mask of `dplyr::filter`. Only that is recovered here. What a definition merely states about a call (its * properties, its signature) reaches every consumer through the export vertex already, so a definition saying * nothing about evaluation is left to the default processor, which keeps the call to one target. */ function builtInBehindExport(data, definition) { if (definition.type !== identifier_1.ReferenceType.Function || definition.name === undefined || !node_id_1.NodeId.isBuiltIn(definition.nodeId) || identifier_1.Identifier.getNamespace(definition.name) === undefined) { return undefined; } const known = data.ctx.env.builtInFunctionOf(definition.name); return (0, built_in_1.statesNonStandardEvaluation)(known) ? known : undefined; } /** * Marks the given function call node as only calling built-in functions. */ function markAsOnlyBuiltIn(graph, rootId, keepEnvironment = false) { const v = graph.getVertex(rootId); if (vertex_1.FunctionCallVertex.is(v)) { v.onlyBuiltin = true; if (!keepEnvironment) { v.environment = undefined; } } } /** * Processes a named function call within the dataflow analysis. * For example, `myFunction(arg1, arg2)`, resolves against the environment. */ function processNamedCall(name, args, rootId, data) { const resolved = resolve_helper_1.Resolve.byNameAndType(name.content, data.environment, identifier_1.ReferenceType.Function) ?? []; let defaultProcessor = resolved.length === 0; /* a call whose meaning a later pass looks up in its environment has to keep it */ const keepEnvironment = resolved.some(r => r.type === identifier_1.ReferenceType.BuiltInFunction && r.config?.keepEnvironment === true); /* if this call will be marked as built-in only (see below), its vertex needs no environment snapshot */ if (resolved.length > 0 && !keepEnvironment && resolved.every(r => r.type === identifier_1.ReferenceType.BuiltInFunction && typeof r.processor === 'function') && resolved.some(r => r.type === identifier_1.ReferenceType.BuiltInFunction && r.config?.libFn !== true)) { data = { ...data, builtInNoEnv: rootId }; } let information = undefined; let builtIn = false; /* a set, because an export and the built-in behind it are two ways to the same definition */ const toRun = new Set(); for (const resolvedFunction of resolved) { const own = resolvedFunction.type === identifier_1.ReferenceType.BuiltInFunction && typeof resolvedFunction.processor === 'function' ? resolvedFunction /* the call goes through the attached export, so it is not built-in only, but flowR's own definition of that export is what knows how to process it */ : builtInBehindExport(data, resolvedFunction); if (own === undefined) { defaultProcessor = true; continue; } builtIn ||= own === resolvedFunction && own.config?.libFn !== true; toRun.add(own); } for (const own of toRun) { information = mergeInformation(information, own.processor(name, args, rootId, data)); } if (defaultProcessor) { /* if we do not know where we land, we force! reuse `resolved`, data.environment did not change above */ const call = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: resolved.length > 0 ? undefined : 'all', origin: 'default' }); information = mergeInformation(information, call.information); } else if (information && builtIn) { // mark the function call as built in only markAsOnlyBuiltIn(information.graph, rootId, keepEnvironment); } // on demand: materialize the built-in vertex for any package export this call resolves to and, when we // already know the loading call here, link to it (a function-local export is invisible to the later // top-level linkMaterializedExportsToLoaders pass, so we must catch it at the call site) if (information) { const sigdb = data.ctx.config.solver.sigdb; for (const r of resolved) { if (r.type === identifier_1.ReferenceType.Function && r.nodeId !== undefined && node_id_1.NodeId.isBuiltIn(r.nodeId)) { (0, built_in_library_1.attachExportVertex)(information.graph, r.nodeId, data.environment, data.ctx, data.cds); const definedAt = r.definedAt; if (definedAt !== undefined && !node_id_1.NodeId.isBuiltIn(definedAt)) { information.graph.addEdge(r.nodeId, definedAt, edge_1.EdgeType.Reads | edge_1.EdgeType.Calls); // the call itself reads the library() load that made this export resolvable information.graph.addEdge(rootId, definedAt, edge_1.EdgeType.Reads); } // opt-in: a lightweight ref edge from the call to its sigdb-backed package function vertex if (sigdb.linkPackageCalls && node_id_1.NodeId.toPkgFn(r.nodeId) !== undefined) { information.graph.addEdge(rootId, r.nodeId, edge_1.EdgeType.Reads); } } } const ns = identifier_1.Identifier.getNamespace(name.content); // a call links to a database-unresolved `library(pkg)` recorded below the global env: `pkg::fn` names // its package, a bare call takes it from what flowR resolved it to, which is what ties // `filter(df, id > 2)` to the `library(dplyr)` that made it dplyr's const owners = ns !== undefined ? [ns] : resolved .map(r => r.type === identifier_1.ReferenceType.BuiltInFunction && r.name !== undefined ? identifier_1.Identifier.getNamespace(r.name) : undefined) .filter(assert_1.isNotUndefined); for (const owner of owners) { for (const loadNode of (0, built_in_library_1.loadNodesForNamespace)(data.environment, String(owner))) { information.graph.addEdge(rootId, loadNode, edge_1.EdgeType.Reads); } } // opt-in: link a bare base-R call to its sigdb function vertex (base-R qualification is edge-free otherwise); // same guards as Identifier.toQualified -- not namespaced, not resolving to a user definition if (sigdb.linkBaseRCalls && ns === undefined && !resolved.some(r => r.nodeId !== undefined && !node_id_1.NodeId.isBuiltIn(r.nodeId))) { const bare = identifier_1.Identifier.getName(name.content); const owner = (0, r_base_packages_1.baseRExportOwner)(bare); if (owner !== undefined) { const builtInId = node_id_1.NodeId.fromPkgFn(owner, bare); (0, built_in_library_1.attachExportVertex)(information.graph, builtInId, data.environment, data.ctx, data.cds); information.graph.addEdge(rootId, builtInId, edge_1.EdgeType.Reads); } } } return information ?? info_1.DataflowInformation.initialize(rootId, data); } //# sourceMappingURL=named-call-handling.js.map