@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
155 lines • 6.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isInstalledResourceFile = isInstalledResourceFile;
exports.useResolvesToDefinitionOrBuiltin = useResolvesToDefinitionOrBuiltin;
exports.isNonStandardEvaluated = isNonStandardEvaluated;
exports.isInSubscript = isInSubscript;
exports.collectScopeDefinedNames = collectScopeDefinedNames;
exports.isDefinedInEnclosingScope = isDefinedInEnclosingScope;
const edge_1 = require("../../dataflow/graph/edge");
const df_helper_1 = require("../../dataflow/graph/df-helper");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const vertex_1 = require("../../dataflow/graph/vertex");
const type_1 = require("../../r-bridge/lang-4.x/ast/model/type");
const graph_1 = require("../../dataflow/graph/graph");
/**
* Path heuristic for an R package `inst/` resource (installed verbatim, not namespace source). Fallback for
* the authoritative `FileRole.Install`, covering requests that bypass the file-role plugins.
*/
function isInstalledResourceFile(file) {
return file !== undefined && /(^|[\\/])inst[\\/]/.test(file);
}
const ResolveEdges = edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedByOnCall;
/**
* Whether the variable use `id` resolves to a local definition, a parameter, or a built-in (function or
* constant such as `T`, `pi`). Broader than `Dataflow.origin`, which misses built-in constants.
*/
function useResolvesToDefinitionOrBuiltin(graph, id) {
for (const [target, edge] of graph.outgoingEdges(id) ?? graph_1.NoEdges) {
if (edge_1.DfEdge.doesNotIncludeType(edge, ResolveEdges) || edge_1.DfEdge.includesType(edge, edge_1.EdgeType.NonStandardEvaluation)) {
continue;
}
if (node_id_1.NodeId.isBuiltIn(target)) {
return true;
}
const targetVtx = graph.getVertex(target);
if (vertex_1.VariableDefinitionVertex.is(targetVtx) || vertex_1.FunctionDefinitionVertex.is(targetVtx)) {
return true;
}
}
return false;
}
/**
* Whether any edge incident to `id` marks it as non-standard-evaluated (quoted), e.g. `quote`/`substitute`.
* A loop body is marked as non-standard-evaluated too, but it is evaluated, so it does not count here.
*/
function isNonStandardEvaluated(graph, id) {
return df_helper_1.Dataflow.isQuoted(id, graph, true);
}
/**
* Whether the use `id` sits inside a `[`/`[[` subscript (not the accessed object). `data.table`'s
* `DT[i, j, by]` masks the subscript symbols as columns, but flowR cannot tell this apart from ordinary
* indexing (`x[i]`), so the rule suppresses subscript symbols by default. Stops at the enclosing function.
*/
function isInSubscript(graph, id) {
const idMap = graph.idMap;
if (idMap === undefined) {
return false;
}
let childId = id;
let parentId = idMap.get(id)?.info.parent;
for (let guard = 0; parentId !== undefined && guard < 64; guard++) {
const parent = idMap.get(parentId);
if (parent === undefined || parent.type === type_1.RType.FunctionDefinition) {
return false;
}
if (parent.type === type_1.RType.Access && (parent.operator === '[' || parent.operator === '[[') && parent.accessed.info.id !== childId) {
return true; // reached from a subscript, not the accessed object
}
childId = parentId;
parentId = parent.info.parent;
}
return false;
}
/** AST nodes that make their children conditionally executed within their scope. */
const ConditionalNodeTypes = new Set([type_1.RType.IfThenElse, type_1.RType.ForLoop, type_1.RType.WhileLoop, type_1.RType.RepeatLoop]);
/**
* Walk from `startParent` up to the nearest enclosing {@link RType.FunctionDefinition} (or the top level),
* reporting that scope and whether the walk crossed no `if`/loop block - so a binding at the origin is
* guaranteed to execute whenever its scope runs. (`cds` is unsuitable: guard clauses like `if(...) stop()`
* add a control dependency to otherwise-unconditional sibling statements.)
*/
function enclosingScope(idMap, startParent) {
let cur = startParent;
let unconditional = true;
for (let guard = 0; cur !== undefined && guard < 256; guard++) {
const node = idMap.get(cur);
if (node === undefined) {
break;
}
if (node.type === type_1.RType.FunctionDefinition) {
return { scope: cur, unconditional };
}
if (ConditionalNodeTypes.has(node.type)) {
unconditional = false;
}
cur = node.info.parent;
}
return { scope: 'top', unconditional };
}
/**
* Per scope, the names it binds unconditionally. flowR's static resolution does not always link a use in a
* nested function to a binding introduced later in an enclosing scope; the rule consults this as a fallback.
* Only unconditional bindings are recorded: one assigned solely inside an `if`/loop is not guaranteed to
* exist, so suppressing an unresolved use of it would hide a real possibly-undefined access.
*/
function collectScopeDefinedNames(graph) {
const idMap = graph.idMap;
const byScope = new Map();
if (idMap === undefined) {
return byScope;
}
for (const [id, vtx] of graph.vertices(true)) {
if (!vertex_1.VariableDefinitionVertex.is(vtx)) {
continue; // function bindings/params surface as variable definitions of their name symbol
}
const name = idMap.get(id)?.lexeme;
if (name === undefined) {
continue;
}
const { scope, unconditional } = enclosingScope(idMap, idMap.get(id)?.info.parent);
if (!unconditional) {
continue;
}
const names = byScope.get(scope) ?? new Set();
names.add(name);
byScope.set(scope, names);
}
return byScope;
}
/**
* Whether `name` is bound in the scope of `useId` or an enclosing scope (up to the top level), per
* {@link collectScopeDefinedNames}. Suppresses forward-referenced closure variables flowR did not link.
*/
function isDefinedInEnclosingScope(graph, defined, useId, name) {
const idMap = graph.idMap;
if (idMap === undefined) {
return false;
}
if (defined.get('top')?.has(name)) {
return true;
}
let cur = idMap.get(useId)?.info.parent;
for (let guard = 0; cur !== undefined && guard < 256; guard++) {
const node = idMap.get(cur);
if (node === undefined) {
break;
}
if (node.type === type_1.RType.FunctionDefinition && defined.get(cur)?.has(name)) {
return true;
}
cur = node.info.parent;
}
return false;
}
//# sourceMappingURL=undefined-symbol-util.js.map