UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

362 lines 16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DataflowGraphBuilder = void 0; exports.emptyGraph = emptyGraph; exports.getBuiltInSideEffect = getBuiltInSideEffect; const objects_1 = require("../../util/objects"); const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"); const graph_1 = require("./graph"); const vertex_1 = require("./vertex"); const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const edge_1 = require("./edge"); const default_builtin_config_1 = require("../environments/default-builtin-config"); const flowr_search_executor_1 = require("../../search/flowr-search-executor"); const assert_1 = require("../../util/assert"); const flowr_analyzer_context_1 = require("../../project/context/flowr-analyzer-context"); const built_in_proc_name_1 = require("../environments/built-in-proc-name"); /** * Creates an empty dataflow graph. * Should only be used in tests and documentation. */ function emptyGraph(cleanEnv, idMap) { return new DataflowGraphBuilder(cleanEnv, idMap); } /** * This DataflowGraphBuilder extends {@link DataflowGraph} with builder methods to * easily and compactly add vertices and edges to a dataflow graph. Its usage thus * simplifies writing tests for dataflow graphs. */ class DataflowGraphBuilder extends graph_1.DataflowGraph { defaultEnvironment; constructor(cleanEnv, idMap) { super(idMap); this.defaultEnvironment = cleanEnv ?? (0, flowr_analyzer_context_1.contextFromInput)('').env.makeCleanEnv(); } addVertexWithDefaultEnv(vertex, asRoot = true, overwrite = false) { super.addVertex(vertex, this.defaultEnvironment, asRoot, overwrite); return this; } /** * Adds a **vertex** for a **function definition** (V1). * @param id - AST node ID * @param subflow - Subflow data graph for the defined function. * @param exitPoints - Node IDs for exit point vertices. * @param info - Additional/optional properties. * @param asRoot - should the vertex be part of the root vertex set of the graph * (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition) */ defineFunction(id, exitPoints, subflow, info, asRoot = true) { return this.addVertexWithDefaultEnv({ tag: vertex_1.VertexType.FunctionDefinition, id: node_id_1.NodeId.normalize(id), params: Object.fromEntries(info?.readParams ?? []), subflow: { ...subflow, entryPoint: node_id_1.NodeId.normalize(subflow.entryPoint), graph: new Set([...subflow.graph].map(node_id_1.NodeId.normalize)), out: subflow.out.map(o => ({ ...o, nodeId: node_id_1.NodeId.normalize(o.nodeId), cds: o.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })) })), in: subflow.in.map(o => ({ ...o, nodeId: node_id_1.NodeId.normalize(o.nodeId), cds: o.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })) })), unknownReferences: subflow.unknownReferences.map(o => ({ ...o, nodeId: node_id_1.NodeId.normalize(o.nodeId), cds: o.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })) })), hooks: subflow.hooks ?? [], }, mode: info?.mode, exitPoints: exitPoints.map(e => typeof e === 'object' ? ({ ...e, nodeId: node_id_1.NodeId.normalize(e.nodeId), cds: e.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })) }) : ({ nodeId: node_id_1.NodeId.normalize(e), type: 0 /* ExitPointType.Default */, cds: undefined })), cds: info?.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })), environment: info?.environment, }, asRoot); } /** * Adds a **vertex** for a **function call** (V2). * @param id - AST node ID * @param name - Function name * @param args - Function arguments; may be empty * @param info - Additional/optional properties. * @param asRoot - should the vertex be part of the root vertex set of the graph * (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition) */ call(id, name, args, info, asRoot = true) { const onlyBuiltInAuto = info?.reads?.length === 1 && node_id_1.NodeId.isBuiltIn(info?.reads[0]); this.addVertexWithDefaultEnv({ tag: vertex_1.VertexType.FunctionCall, id: node_id_1.NodeId.normalize(id), name, args: args.map(a => a === r_function_call_1.EmptyArgument ? r_function_call_1.EmptyArgument : { ...a, nodeId: node_id_1.NodeId.normalize(a.nodeId), cds: undefined }), environment: (info?.onlyBuiltIn || onlyBuiltInAuto) ? undefined : info?.environment ?? this.defaultEnvironment, cds: info?.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })), onlyBuiltin: info?.onlyBuiltIn ?? onlyBuiltInAuto ?? false, origin: info?.origin ?? [(0, default_builtin_config_1.getDefaultProcessor)(name) ?? built_in_proc_name_1.BuiltInProcName.Function], link: info?.link }, asRoot); if (!info?.omitArgs) { this.addArgumentLinks(id, args); } if (info?.returns) { for (const ret of info.returns) { this.returns(id, ret); } } if (info?.reads) { for (const call of info.reads) { this.reads(id, call); } } return this; } /** automatically adds argument links if they do not already exist */ addArgumentLinks(id, args) { for (const arg of args) { if (graph_1.FunctionArgument.isEmpty(arg)) { continue; } if (graph_1.FunctionArgument.isPositional(arg)) { this.argument(id, arg.nodeId); if (typeof arg.nodeId === 'string' && arg.nodeId.endsWith('-arg')) { const withoutSuffix = arg.nodeId.slice(0, -4); this.reads(arg.nodeId, withoutSuffix); } } else if (!this.hasVertex(arg.nodeId, true)) { this.use(arg.nodeId, arg.name, { cds: arg.cds }); this.argument(id, arg.nodeId); } } } /** * Adds a **vertex** for a **variable definition** (V4). * @param id - AST node ID * @param name - Variable name * @param info - Additional/optional properties. * @param asRoot - Should the vertex be part of the root vertex set of the graph * (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition) */ defineVariable(id, name, info, asRoot = true) { this.addVertexWithDefaultEnv({ tag: vertex_1.VertexType.VariableDefinition, id: node_id_1.NodeId.normalize(id), name, cds: info?.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })), }, asRoot); if (info?.definedBy) { for (const def of info.definedBy) { this.definedBy(id, def); } } return this; } /** * Adds a **vertex** for **variable use** (V5). Intended for creating dataflow graphs as part of function tests. * @param id - AST node id * @param name - Variable name * @param info - Additional/optional properties; i.e., scope, when, or environment. * @param asRoot - should the vertex be part of the root vertex set of the graph * (i.e., be a valid entry point) or is it nested (e.g., as part of a function definition) */ use(id, name, info, asRoot = true) { return this.addVertexWithDefaultEnv((0, objects_1.deepMergeObject)({ tag: vertex_1.VertexType.Use, id: node_id_1.NodeId.normalize(id), name, cds: undefined, environment: undefined }, { ...info, cds: info?.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })) }), asRoot); } /** * Adds a **vertex** for a **constant value** (V6). * @param id - AST node ID * @param options - Additional/optional properties; * @param asRoot - should the vertex be part of the root vertex set of the graph * (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition) */ constant(id, options, asRoot = true) { return this.addVertexWithDefaultEnv({ tag: vertex_1.VertexType.Value, id: node_id_1.NodeId.normalize(id), cds: options?.cds?.map(c => ({ ...c, id: node_id_1.NodeId.normalize(c.id) })), environment: undefined }, asRoot); } edgeHelper(from, to, type) { if (Array.isArray(to)) { for (const t of to) { this.edgeHelper(from, t, type); } return this; } return this.addEdge(node_id_1.NodeId.normalize(from), node_id_1.NodeId.normalize(to), type); } async queryHelper(from, to, data, type) { let fromId; if ('nodeId' in from) { fromId = from.nodeId; } else { const result = (await (0, flowr_search_executor_1.runSearch)(from.query, data)).getElements(); (0, assert_1.guard)(result.length === 1, `from query result should yield exactly one node, but yielded ${result.length}`); fromId = result[0].node.info.id; } let toIds; if ('target' in to) { toIds = to.target; } else { const result = (await (0, flowr_search_executor_1.runSearch)(to.query, data)).getElements(); toIds = result.map(r => r.node.info.id); } return this.edgeHelper(fromId, toIds, type); } /** * Adds a **read edge**. * @param from - NodeId of the source vertex * @param to - Either a single or multiple target ids. * If you pass multiple this will construct a single edge for each of them. */ reads(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.Reads); } /** * Adds a **read edge** with a query for the from and/or to vertices. * @param from - Either a node id or a query to find the node id. * @param to - Either a node id or a query to find the node id. * @param input - The input to search in i.e. the dataflow graph. */ readsQuery(from, to, input) { return this.queryHelper(from, to, input, edge_1.EdgeType.Reads); } /** * Adds a **defined-by edge** with from as defined variable, and to * as a variable/function contributing to its definition. * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ definedBy(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.DefinedBy); } /** * Adds a **defined-by edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ definedByQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.DefinedBy); } /** * Adds a **call edge** with from as caller, and to as callee. * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ calls(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.Calls); } /** * Adds a **call edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ callsQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.Calls); } /** * Adds a **return edge** with from as function, and to as exit point. * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ returns(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.Returns); } /** * Adds a **return edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ returnsQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.Returns); } /** * Adds a **defines-on-call edge** with from as variable, and to as its definition * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ definesOnCall(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.DefinesOnCall); } /** * Adds a **defines-on-call edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ definesOnCallQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.DefinesOnCall); } /** * Adds a **defined-by-on-call edge** with from as definition, and to as variable. * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ definedByOnCall(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.DefinedByOnCall); } /** * Adds a **defined-by-on-call edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ definedByOnCallQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.DefinedByOnCall); } /** * Adds an **argument edge** with from as function call, and to as argument. * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ argument(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.Argument); } /** * Adds a **argument edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ argumentQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.Argument); } /** * Adds a **non-standard evaluation edge** with from as vertex, and to as vertex. * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ nse(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.NonStandardEvaluation); } /** * Adds a **non-standard evaluation edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ nseQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.NonStandardEvaluation); } /** * Adds a **side-effect-on-call edge** with from as vertex, and to as vertex. * @see {@link DataflowGraphBuilder#reads|reads} for parameters. */ sideEffectOnCall(from, to) { return this.edgeHelper(from, to, edge_1.EdgeType.SideEffectOnCall); } /** * Adds a **side-effect-on-call edge** with a query for the from and/or to vertices. * @see {@link DataflowGraphBuilder#readsQuery|readsQuery} for parameters. */ sideEffectOnCallQuery(from, to, data) { return this.queryHelper(from, to, data, edge_1.EdgeType.SideEffectOnCall); } /** * explicitly overwrite the root ids of the graph, * this is just an easier variant in case you working with a lot of functions this saves you a lot of `false` flags. */ overwriteRootIds(ids) { this.rootVertices = new Set(ids.map(node_id_1.NodeId.normalize)); return this; } } exports.DataflowGraphBuilder = DataflowGraphBuilder; /** * */ function getBuiltInSideEffect(name) { const got = default_builtin_config_1.DefaultBuiltinConfig.find(e => e.names.includes(name)); if (got?.type !== 'function') { return undefined; } return (got?.config).hasUnknownSideEffects; } //# sourceMappingURL=dataflowgraph-builder.js.map