@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
687 lines • 32.5 kB
JavaScript
"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