UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

687 lines 32.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InputType = exports.InputTraceType = void 0; exports.classifyInput = classifyInput; const node_id_1 = require("../../../r-bridge/lang-4.x/ast/model/processing/node-id"); const input_types_1 = require("./input-types"); var input_types_2 = require("./input-types"); Object.defineProperty(exports, "InputTraceType", { enumerable: true, get: function () { return input_types_2.InputTraceType; } }); Object.defineProperty(exports, "InputType", { enumerable: true, get: function () { return input_types_2.InputType; } }); const graph_1 = require("../../../dataflow/graph/graph"); const objects_1 = require("../../../util/objects"); const vertex_1 = require("../../../dataflow/graph/vertex"); const df_helper_1 = require("../../../dataflow/graph/df-helper"); const edge_1 = require("../../../dataflow/graph/edge"); const identifier_1 = require("../../../dataflow/environments/identifier"); const assert_1 = require("../../../util/assert"); const arrays_1 = require("../../../util/collections/arrays"); const built_in_proc_name_1 = require("../../../dataflow/environments/built-in-proc-name"); const record_1 = require("../../../util/record"); const r_number_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-number"); const r_string_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-string"); const r_logical_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-logical"); const r_symbol_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol"); const convert_values_1 = require("../../../r-bridge/lang-4.x/convert-values"); const model_1 = require("../../../r-bridge/lang-4.x/ast/model/model"); const r_function_definition_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-definition"); /** how far a function handed to an entry point may be passed along before we give up resolving it */ const MaxFunctionResolveDepth = 4; function isConstantLike(type) { return type === input_types_1.InputType.Constant || type === input_types_1.InputType.DerivedConstant; } /** Returns the common value shared by all defined entries, or `undefined` if they disagree or all are `undefined`. */ function singleValue(values) { let result; let seen = false; for (const v of values) { if (v === undefined) { return undefined; } if (!seen) { result = v; seen = true; } else if (v !== result) { return undefined; } } return result; } /** * Accumulates types, control-dependency types, values, and purity while traversing origin * chains. Call {@link build} to produce the resulting {@link InputSource}. */ class ClassificationAccumulator { types = []; cds = []; values = []; allPure = true; merge(c) { this.types.push(...c.types); this.values.push(c.value); if (c.cds) { this.cds.push(...c.cds); } if (c.trace !== input_types_1.InputTraceType.Pure) { this.allPure = false; } } pushUnknown() { this.types.push(input_types_1.InputType.Unknown); this.values.push(undefined); } build(id) { const types = this.types.length === 0 ? [input_types_1.InputType.Unknown] : (0, arrays_1.uniqueArray)(this.types); const trace = this.allPure ? input_types_1.InputTraceType.Pure : input_types_1.InputTraceType.Alias; const src = { id, types, trace }; const cds = this.cds.length === 0 ? undefined : (0, arrays_1.uniqueArray)(this.cds); if (cds) { src.cds = cds; } if (types.every(isConstantLike)) { const v = singleValue(this.values); if (v !== undefined) { src.value = v; } } return src; } } class InputClassifier { dfg; config; cache = new Map(); fullDfg; /** the packages attached in the analyzed program, `undefined` if that is not known (then everything may match) */ packages; fullClassifier; declarationIndex; entryPointIndex; constructor(dfg, config, fullDfg, packages) { this.dfg = dfg; this.config = config; this.fullDfg = fullDfg; this.packages = packages; } matches(call, list) { return matchesList(call, list, this.packages); } /** whether the package the given entry needs is attached (unknown package information lets everything through) */ hasPackage(name) { return name === undefined || this.packages === undefined || this.packages.has(name); } /** * Returns the specification of the {@link LinkedInputObject|linked input object} the given id refers to * (e.g., shiny's `input`), or `undefined` if it refers to something else. */ matchLinkedObject(id) { const idMap = this.dfg.idMap; const node = idMap?.get(id); if (idMap === undefined || !r_symbol_1.RSymbol.is(node)) { return undefined; } // the framework may bind the object by position, in a function it is handed (`shinyApp(ui, server)`) const positional = this.boundByEntryPoint(node, idMap); if (positional !== undefined) { return positional; } return this.config.linkedObjects?.find(o => o.name === node.content && this.hasPackage(o.requires) && isBoundAsLinkedObject(node, o, idMap)); } /** * The object a framework binds at this symbol's position, when the function binding it is handed to one of the * {@link LinkedInputEntryPoint|entry points} - this is how R passes them, so the parameter names do not matter. */ boundByEntryPoint(node, idMap) { if (!this.config.linkedEntryPoints?.length) { return undefined; } for (const fn of enclosingFunctions(node, idMap)) { const index = fn.parameters.findIndex(p => p.name.content === node.content); if (index < 0) { continue; // not bound by this function, so keep looking outwards } const bound = this.entryPoints().get(fn.info.id)?.[index]; // the entry point names the object, so it keeps its fields and declarations no matter what the parameter is called return bound === undefined ? undefined : this.config.linkedObjects?.find(o => o.name === bound); } return undefined; } /** The function definitions handed to an {@link LinkedInputEntryPoint|entry point}, with how it binds their parameters. */ entryPoints() { if (this.entryPointIndex !== undefined) { return this.entryPointIndex; } const index = new Map(); this.entryPointIndex = index; for (const [, call] of (this.fullDfg ?? this.dfg).verticesOfType(vertex_1.VertexType.FunctionCall)) { for (const entry of this.config.linkedEntryPoints ?? []) { if (!this.matches(call, [entry.call])) { continue; } const handed = this.argumentReference(call, entry.argIdx, entry.argName); for (const fn of this.functionDefinitionsAt(handed)) { index.set(fn, entry.params); } } } return index; } /** the function definitions the given id may hold, be it one directly or a variable a definition was assigned to */ *functionDefinitionsAt(id, depth = 0) { const graph = this.fullDfg ?? this.dfg; const vtx = id === undefined || depth > MaxFunctionResolveDepth ? undefined : graph.getVertex(id); if (vtx === undefined) { return; } else if (vertex_1.FunctionDefinitionVertex.is(vtx)) { yield vtx.id; } else if (vertex_1.VariableDefinitionVertex.is(vtx)) { for (const source of vtx.source ?? []) { yield* this.functionDefinitionsAt(source, depth + 1); } } else { for (const origin of df_helper_1.Dataflow.origin(graph, vtx.id) ?? []) { if (origin.type === 0 /* OriginType.ReadVariableOrigin */ || origin.type === 1 /* OriginType.WriteVariableOrigin */ || origin.type === 2 /* OriginType.FunctionCallOrigin */) { yield* this.functionDefinitionsAt(origin.id, depth + 1); } } } } /** the id of the argument named `argName`, or of the one at `argIdx` if it is passed positionally */ argumentReference(call, argIdx, argName) { const named = call.args.find(a => graph_1.FunctionArgument.isNamed(a) && graph_1.FunctionArgument.getName(a) === argName); const arg = named ?? call.args[argIdx]; if (arg === undefined || graph_1.FunctionArgument.isEmpty(arg) || (named === undefined && graph_1.FunctionArgument.isNamed(arg))) { return undefined; } return graph_1.FunctionArgument.getReference(arg); } /** the linked object the id refers to, but only where reading the object as a whole already is an input */ matchWholeLinkedObject(id) { const obj = this.matchLinkedObject(id); return obj !== undefined && fieldIsInput(obj, undefined) ? obj : undefined; } /** * All declarations of framework entries in the program, keyed by object and entry name, built once on first use. * This is what links a read of `input$n` back to the `textInput("n", …)` defining it. */ declarations() { if (this.declarationIndex !== undefined) { return this.declarationIndex; } const index = new Map(); this.declarationIndex = index; const specs = this.config.linkedObjects?.filter(o => o.declaredBy !== undefined) ?? []; if (specs.length === 0) { return index; } for (const [, call] of (this.fullDfg ?? this.dfg).verticesOfType(vertex_1.VertexType.FunctionCall)) { for (const obj of specs) { const spec = obj.declaredBy; if (!this.matches(call, spec.calls)) { continue; } const name = this.argumentValue(call, spec.argIdx, spec.argName); if (typeof name === 'string') { const key = declarationKey(obj.name, name); index.set(key, [...(index.get(key) ?? []), call.id]); } } } return index; } /** the value of the argument named `argName`, or of the one at `argIdx` if it is passed positionally */ argumentValue(call, argIdx, argName) { const ref = this.argumentReference(call, argIdx, argName); const vtx = ref === undefined ? undefined : (this.fullDfg ?? this.dfg).getVertex(ref); return vtx === undefined ? undefined : this.classifyEntry(vtx).value; } isDefinedByOnCall(id) { return this.definedByOnCallTargets(id).length > 0; } /** the ids the given one is linked to by {@link EdgeType.DefinedByOnCall}, e.g. a parameter to the arguments it is bound to */ definedByOnCallTargets(id) { const out = (this.fullDfg ?? this.dfg).outgoingEdges(id) ?? new Map(); return out.entries().filter(([, e]) => edge_1.DfEdge.includesType(e, edge_1.EdgeType.DefinedByOnCall)).map(([to]) => to).toArray(); } /** * Classifies the given id against the full graph, for everything the reduced graph of the criterion cannot see * (the enclosing scopes and the callers of the function the criterion is in). */ classifyInFullGraph(id) { if (this.fullDfg === undefined || this.fullDfg === this.dfg) { return undefined; } this.fullClassifier ??= new InputClassifier(this.fullDfg, this.config, undefined, this.packages); const vtx = this.fullDfg.getVertex(id); return vtx ? this.fullClassifier.classifyEntry(vtx) : undefined; } extractConstantValue(id) { const node = this.dfg.idMap?.get(id); if (node === undefined) { return undefined; } if (r_number_1.RNumber.is(node)) { return node.content.num; } if (r_string_1.RString.is(node)) { return node.content.str; } if (r_logical_1.RLogical.is(node)) { return node.content; } if (r_symbol_1.RSymbol.is(node) && node.content === convert_values_1.RNull) { return null; } return undefined; } classifyEntry(vertex) { const cached = this.cache.get(vertex.id); if (cached) { return cached; } // insert temporary unknown to break cycles this.cache.set(vertex.id, { id: vertex.id, types: [input_types_1.InputType.Unknown], trace: input_types_1.InputTraceType.Unknown }); switch (vertex.tag) { case vertex_1.VertexType.Value: { const src = { id: vertex.id, types: [input_types_1.InputType.Constant], trace: input_types_1.InputTraceType.Unknown }; const v = this.extractConstantValue(vertex.id); if (v !== undefined) { src.value = v; } return this.classifyCdsAndReturn(vertex, src); } case vertex_1.VertexType.FunctionCall: return this.classifyFunctionCall(vertex); case vertex_1.VertexType.VariableDefinition: return this.classifyVariableDefinition(vertex); case vertex_1.VertexType.Use: return this.classifyVariable(vertex); default: return this.classifyCdsAndReturn(vertex, { id: vertex.id, types: [input_types_1.InputType.Unknown], trace: input_types_1.InputTraceType.Unknown }); } } /** * Accesses like `input$n` or `input[["n"]]` are reported as a single source of the accessed object, * carrying the accessed field as its {@link InputSource.name|name}. */ classifyLinkedObjectAccess(call) { if (!call.origin.includes(built_in_proc_name_1.BuiltInProcName.Access)) { return undefined; } const accessed = graph_1.FunctionArgument.isEmpty(call.args[0]) ? undefined : graph_1.FunctionArgument.getReference(call.args[0]); const linked = accessed === undefined ? undefined : this.matchLinkedObject(accessed); const field = this.accessedField(call); if (linked === undefined || !fieldIsInput(linked, field)) { return undefined; } const src = { id: call.id, types: [linked.type], trace: input_types_1.InputTraceType.Unknown }; if (field !== undefined) { src.name = field; const declaredAt = linked.declaredBy && this.declarations().get(declarationKey(linked.name, field)); if (declaredAt) { src.declaredAt = declaredAt; } } return src; } accessedField(call) { const arg = call.args[1]; if (arg === undefined || graph_1.FunctionArgument.isEmpty(arg)) { return undefined; } const ref = graph_1.FunctionArgument.getReference(arg); const node = ref === undefined ? undefined : this.dfg.idMap?.get(ref); if (r_string_1.RString.is(node)) { return node.content.str; } else if (r_symbol_1.RSymbol.is(node)) { return identifier_1.Identifier.getName(node.content); } return undefined; } classifyFunctionCall(call) { const linkedAccess = this.classifyLinkedObjectAccess(call); if (linkedAccess) { return this.classifyCdsAndReturn(call, linkedAccess); } if (call.origin.includes(built_in_proc_name_1.BuiltInProcName.ExpressionList)) { // `{ a; b }` evaluates to its last expression, just like in R const last = call.args.findLast(a => !graph_1.FunctionArgument.isEmpty(a)); const value = last === undefined ? undefined : graph_1.FunctionArgument.getReference(last); const vtx = value === undefined ? undefined : this.dfg.getVertex(value); if (vtx) { return this.classifyCdsAndReturn(call, { ...this.classifyEntry(vtx), id: call.id }); } } else if (call.origin.includes(built_in_proc_name_1.BuiltInProcName.IfThenElse) || call.origin.includes(built_in_proc_name_1.BuiltInProcName.WhileLoop)) { const condition = graph_1.FunctionArgument.getReference(call.args[0]); if (condition) { const vtx = this.dfg.getVertex(condition); if (vtx) { return this.classifyCdsAndReturn(call, this.classifyEntry(vtx)); } } } else if (call.origin.includes(built_in_proc_name_1.BuiltInProcName.ForLoop)) { const condition = graph_1.FunctionArgument.getReference(call.args[1]); if (condition) { const vtx = this.dfg.getVertex(condition); if (vtx) { return this.classifyCdsAndReturn(call, this.classifyEntry(vtx)); } } } else if (call.origin.includes(built_in_proc_name_1.BuiltInProcName.Get) && !(this.fullDfg ?? this.dfg).unknownSideEffects.has(node_id_1.NodeId.normalize(call.id))) { // a statically resolved `get("x")` yields the value of the retrieved variable, read via its first argument const ref = graph_1.FunctionArgument.getReference(call.args[0]); const vtx = ref === undefined ? undefined : this.dfg.getVertex(ref); if (vtx) { return this.classifyCdsAndReturn(call, { ...this.classifyEntry(vtx), id: call.id }); } } // a narrowing function returns a bounded value: either one of a specific argument's values (e.g. `match.arg` // -> its `choices`), or - with no bounding argument - a content-independent value like a count/index/logical for (const narrow of this.config.narrowing ?? []) { if (!this.matches(call, [narrow.call])) { continue; } if (narrow.argIdx === undefined) { return this.classifyCdsAndReturn(call, (0, objects_1.compactRecord)({ id: call.id, types: [input_types_1.InputType.DerivedConstant], trace: input_types_1.InputTraceType.Pure })); } const ref = this.argumentReference(call, narrow.argIdx, narrow.argName ?? ''); const vtx = ref === undefined ? undefined : this.dfg.getVertex(ref); if (vtx) { return this.classifyCdsAndReturn(call, { ...this.classifyEntry(vtx), id: call.id }); } } if (!this.matches(call, this.config.pure)) { const types = []; for (const type of record_1.Record.values(input_types_1.InputType)) { if (this.matches(call, this.config[type])) { types.push(type); } } // if a File-typed call reads from a temp path, replace File with TempFile if (types.includes(input_types_1.InputType.File) && !types.includes(input_types_1.InputType.TempFile)) { for (const arg of call.args) { if (graph_1.FunctionArgument.isEmpty(arg)) { continue; } const ref = graph_1.FunctionArgument.getReference(arg); if (ref === undefined) { continue; } const argVtx = this.dfg.getVertex(ref); if (argVtx && this.classifyEntry(argVtx).types.includes(input_types_1.InputType.TempFile)) { types.splice(types.indexOf(input_types_1.InputType.File), 1); types.push(input_types_1.InputType.TempFile); break; } } } if (types.length === 0) { // a call of something the code produced itself, like a shiny `reactive()`, yields what that produced const callee = this.classifyCallee(call); if (callee !== undefined) { return this.classifyCdsAndReturn(call, { ...callee, id: call.id }); } // if it is not pure, we cannot classify based on the inputs, in that case we do not know! types.push(input_types_1.InputType.Unknown); } return this.classifyCdsAndReturn(call, { id: call.id, types, trace: input_types_1.InputTraceType.Unknown }); } // Otherwise, classify by arguments; pure functions get Known/Pure handling const argTypes = []; const cdTypes = []; for (const arg of call.args) { if (graph_1.FunctionArgument.isEmpty(arg)) { continue; } const ref = graph_1.FunctionArgument.getReference(arg); if (ref === undefined) { argTypes.push(input_types_1.InputType.Unknown); continue; } const argVtx = this.dfg.getVertex(ref); if (!argVtx) { argTypes.push(input_types_1.InputType.Unknown); continue; } const classified = this.classifyEntry(argVtx); // collect all observed types from this argument argTypes.push(...classified.types); if (classified.cds) { cdTypes.push(...classified.cds); } } const cds = cdTypes.length > 0 ? (0, arrays_1.uniqueArray)(cdTypes) : undefined; // all arguments only contain constant-like types -> derived constant const allConstLike = argTypes.length > 0 && argTypes.every(isConstantLike); if (allConstLike) { return this.classifyCdsAndReturn(call, (0, objects_1.compactRecord)({ id: call.id, types: [input_types_1.InputType.DerivedConstant], trace: input_types_1.InputTraceType.Pure, cds })); } argTypes.push(input_types_1.InputType.DerivedConstant); return this.classifyCdsAndReturn(call, (0, objects_1.compactRecord)({ id: call.id, types: (0, arrays_1.uniqueArray)(argTypes), trace: input_types_1.InputTraceType.Known, cds })); } /** classifies what a call of a variable (e.g. a shiny reactive `n()`) yields, by what that variable holds */ classifyCallee(call) { for (const o of df_helper_1.Dataflow.origin(this.dfg, call.id) ?? []) { if (o.type !== 0 /* OriginType.ReadVariableOrigin */ && o.type !== 1 /* OriginType.WriteVariableOrigin */) { continue; } const vtx = this.dfg.getVertex(o.id); const classified = vtx ? this.classifyEntry(vtx) : this.classifyInFullGraph(o.id); if (classified !== undefined && !classified.types.includes(input_types_1.InputType.Unknown)) { return classified; } } return undefined; } classifyVariable(vtx) { const linked = this.matchWholeLinkedObject(vtx.id); if (linked) { return this.classifyCdsAndReturn(vtx, { id: vtx.id, types: [linked.type], trace: input_types_1.InputTraceType.Unknown }); } const origins = df_helper_1.Dataflow.origin(this.dfg, vtx.id); if (origins === undefined || origins.length === 0) { if (this.isDefinedByOnCall(vtx.id)) { return this.classifyCdsAndReturn(vtx, { id: vtx.id, types: [input_types_1.InputType.Scope], trace: input_types_1.InputTraceType.Unknown }); } // the definition is not part of the criterion's function, so it has to come from an enclosing scope const outer = this.classifyInFullGraph(vtx.id); return this.classifyCdsAndReturn(vtx, outer ? { ...outer, id: vtx.id } : { id: vtx.id, types: [input_types_1.InputType.Unknown], trace: input_types_1.InputTraceType.Unknown }); } const acc = new ClassificationAccumulator(); for (const o of origins) { if (o.type === 4 /* OriginType.ConstantOrigin */) { acc.types.push(input_types_1.InputType.DerivedConstant); acc.values.push(this.extractConstantValue(o.id)); } else if (o.type === 0 /* OriginType.ReadVariableOrigin */ || o.type === 1 /* OriginType.WriteVariableOrigin */) { this.classifyVariableOrigin(o.id, acc); } else if (o.type === 2 /* OriginType.FunctionCallOrigin */ || o.type === 3 /* OriginType.BuiltInFunctionOrigin */) { this.classifyByVertex(o.id, acc); } else { acc.pushUnknown(); } } return this.classifyCdsAndReturn(vtx, acc.build(vtx.id)); } /** * Resolves a variable definition or use origin, handling the special cases of * scope-escaped variables (DefinedByOnCall) and parameter definitions. */ classifyVariableOrigin(definitionId, acc) { const v = this.dfg.getVertex(definitionId); if (!v) { acc.pushUnknown(); return; } // if the referenced definition is linked via defined-by-on-call to another id (e.g., a parameter linked to a // caller argument), follow it into the caller; only if that leads nowhere is it an opaque Scope origin const onCall = this.definedByOnCallTargets(v.id); if (onCall.length > 0) { const callers = onCall.map(t => this.classifyInFullGraph(t)) .filter(assert_1.isNotUndefined) .filter(c => !c.types.includes(input_types_1.InputType.Unknown)); if (callers.length > 0) { callers.forEach(c => acc.merge(c)); return; } acc.types.push(input_types_1.InputType.Scope); acc.values.push(undefined); acc.allPure = false; } // if this is a variable definition that is a parameter, classify as Parameter if (vertex_1.VariableDefinitionVertex.is(v) && this.dfg.idMap?.get(v.id)?.info.role === "param-n" /* RoleInParent.ParameterName */) { acc.types.push(this.matchWholeLinkedObject(v.id)?.type ?? input_types_1.InputType.Parameter); acc.values.push(undefined); return; } acc.merge(this.classifyEntry(v)); } classifyByVertex(id, acc) { const v = this.dfg.getVertex(id); if (v) { acc.merge(this.classifyEntry(v)); } else { acc.pushUnknown(); } } classifyVariableDefinition(vtx) { // parameter definitions are classified as Parameter if (this.dfg.idMap?.get(vtx.id)?.info.role === "param-n" /* RoleInParent.ParameterName */) { const types = [this.matchWholeLinkedObject(vtx.id)?.type ?? input_types_1.InputType.Parameter]; return this.classifyCdsAndReturn(vtx, { id: vtx.id, types, trace: input_types_1.InputTraceType.Unknown }); } const sources = vtx.source; if (sources === undefined || sources.length === 0) { // fallback to unknown if we cannot find the value return this.classifyCdsAndReturn(vtx, { id: vtx.id, types: [input_types_1.InputType.Unknown], trace: input_types_1.InputTraceType.Unknown }); } const acc = new ClassificationAccumulator(); for (const tid of sources) { const tv = this.dfg.getVertex(tid); if (tv) { acc.merge(this.classifyEntry(tv)); } else { acc.pushUnknown(); } } return this.classifyCdsAndReturn(vtx, acc.build(vtx.id)); } classifyCdsAndReturn(vtx, src) { if (vtx.cds) { const cds = (0, arrays_1.uniqueArray)(vtx.cds.flatMap(c => { const cv = this.dfg.getVertex(c.id); if (!cv) { return undefined; } const e = this.classifyEntry(cv); return e.cds ? [...e.types, ...e.cds] : [...e.types]; }).filter(assert_1.isNotUndefined).concat(src.cds ?? [])); if (cds.length > 0) { src.cds = cds; } } if (src.cds?.length === 0) { delete src.cds; } this.cache.set(vtx.id, src); return src; } } /** the function definitions the given node is nested in, innermost first */ function* enclosingFunctions(node, idMap) { for (const parent of model_1.RNode.iterateParents(node, idMap)) { if (r_function_definition_1.RFunctionDefinition.is(parent)) { yield parent; } } } /** whether the given occurrence of `obj` is bound by a function matching {@link LinkedInputObject.withParams} */ function isBoundAsLinkedObject(node, obj, idMap) { if (!obj.withParams?.length) { return true; } for (const fn of enclosingFunctions(node, idMap)) { const params = new Set(fn.parameters.map(p => p.name.content)); if (params.has(obj.name)) { return obj.withParams.every(p => params.has(p)); } } return false; } function declarationKey(object, field) { return `${object}\u0000${field}`; } /** whether reading `field` of the given object is an input; an object restricted to {@link LinkedInputObject.fields} is none as a whole */ function fieldIsInput(obj, field) { return obj.fields === undefined || (field !== undefined && obj.fields.includes(field)); } /** * Whether a call by the name `called` means `id`. A `pkg::fn` call has to match exactly, while a bare call only * means a namespaced entry if that package is attached - just like in R, where the search path decides. * With `packages` left out (no package information at all) any bare call may mean it. */ function callMeans(called, id, packages) { if (identifier_1.Identifier.matches(id, called)) { return true; } const namespace = identifier_1.Identifier.getNamespace(id); return identifier_1.Identifier.getNamespace(called) === undefined && identifier_1.Identifier.matches(called, id) && (namespace === undefined || packages === undefined || packages.has(namespace)); } function matchesList(fn, list, packages) { return list?.some(id => fn.id === id || (identifier_1.Identifier.is(id) && callMeans(fn.name, id, packages))) ?? false; } /** * Takes the given id which is expected to either be: * - a function call - in this case all arguments are considered to be inputs (additionally to all read edges from the function call in the dataflow graph) * - anything else - in that case the node itself is considered as an "input" - please note that in these scenarios the *return* value will only contain one mapping - that for the id you passed in. * * This method traces the dependencies in the dataflow graph using the specification of functions passed in. * For the scope escape analysis, pass on the full, non-reduced DFG as `fullDfg`, and the packages attached in the * program as `packages` so that bare calls only match the entries of packages that are actually in scope. */ function classifyInput(id, dfg, config, fullDfg, packages) { const vtx = dfg.getVertex(id); if (!vtx) { return []; } const c = new InputClassifier(dfg, config, fullDfg, packages); if (vertex_1.FunctionCallVertex.is(vtx)) { const ret = []; const args = vtx.args; for (const arg of args) { if (graph_1.FunctionArgument.isEmpty(arg)) { continue; } const ref = graph_1.FunctionArgument.getReference(arg); if (ref === undefined) { continue; } const argVtx = dfg.getVertex(ref); if (argVtx === undefined) { continue; } const entry = c.classifyEntry(argVtx); const argName = graph_1.FunctionArgument.getName(arg); ret.push(argName !== undefined ? { ...entry, name: argName } : entry); } return ret; } else { return [ c.classifyEntry(vtx) ]; } } //# sourceMappingURL=simple-input-classifier.js.map