UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

155 lines 6.75 kB
"use strict"; 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