UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

314 lines 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveByName = resolveByName; exports.resolvesToBuiltInConstant = resolvesToBuiltInConstant; exports.resolveToConstants = resolveToConstants; exports.getAliases = getAliases; exports.trackAliasInEnvironments = trackAliasInEnvironments; exports.trackAliasesInGraph = trackAliasesInGraph; exports.resolveValueOfVariable = resolveValueOfVariable; exports.resolveIdToValue = resolveIdToValue; const environment_1 = require("./environment"); const logic_1 = require("../../util/logic"); const identifier_1 = require("./identifier"); const info_1 = require("../info"); const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"); const vertex_1 = require("../graph/vertex"); const config_1 = require("../../config"); const assert_1 = require("../../util/assert"); const type_1 = require("../../r-bridge/lang-4.x/ast/model/type"); const visiting_queue_1 = require("../../slicing/static/visiting-queue"); const fingerprint_1 = require("../../slicing/static/fingerprint"); const edge_1 = require("../graph/edge"); const FunctionTargetTypes = identifier_1.ReferenceType.Function | identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.Unknown | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Parameter; const VariableTargetTypes = identifier_1.ReferenceType.Variable | identifier_1.ReferenceType.Parameter | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Unknown; const ConstantTargetTypes = identifier_1.ReferenceType.Constant | identifier_1.ReferenceType.BuiltInConstant | identifier_1.ReferenceType.Unknown; const BuiltInConstantTargetTypes = identifier_1.ReferenceType.BuiltInConstant | identifier_1.ReferenceType.Unknown; const BuiltInFunctionTargetTypes = identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.Unknown; const TargetTypePredicate = { [identifier_1.ReferenceType.Unknown]: () => true, [identifier_1.ReferenceType.Function]: ({ type }) => (0, identifier_1.isReferenceType)(type, FunctionTargetTypes), [identifier_1.ReferenceType.Variable]: ({ type }) => (0, identifier_1.isReferenceType)(type, VariableTargetTypes), [identifier_1.ReferenceType.Constant]: ({ type }) => (0, identifier_1.isReferenceType)(type, ConstantTargetTypes), [identifier_1.ReferenceType.Parameter]: () => true, [identifier_1.ReferenceType.Argument]: () => true, [identifier_1.ReferenceType.BuiltInConstant]: ({ type }) => (0, identifier_1.isReferenceType)(type, BuiltInConstantTargetTypes), [identifier_1.ReferenceType.BuiltInFunction]: ({ type }) => (0, identifier_1.isReferenceType)(type, BuiltInFunctionTargetTypes) }; /** * Resolves a given identifier name to a list of its possible definition location using R scoping and resolving rules. * * @param name - The name of the identifier to resolve * @param environment - The current environment used for name resolution * @param target - The target (meta) type of the identifier to resolve * * @returns A list of possible identifier definitions (one if the definition location is exactly and always known), or `undefined` * if the identifier is undefined in the current scope/with the current environment information. */ function resolveByName(name, environment, target = identifier_1.ReferenceType.Unknown) { let current = environment.current; let definitions = undefined; const wantedType = TargetTypePredicate[target]; do { const definition = current.memory.get(name); if (definition !== undefined) { const filtered = definition.filter(wantedType); if (filtered.length === definition.length && definition.every(d => (0, info_1.happensInEveryBranch)(d.controlDependencies))) { return definition; } else if (filtered.length > 0) { definitions ??= []; definitions = definitions.concat(filtered); } } current = current.parent; } while (current.id !== environment_1.BuiltInEnvironment.id); const builtIns = current.memory.get(name); if (definitions) { return builtIns === undefined ? definitions : [...definitions, ...builtIns]; } else { return builtIns; } } function resolvesToBuiltInConstant(name, environment, wantedValue) { if (name === undefined) { return logic_1.Ternary.Never; } const definition = resolveByName(name, environment, identifier_1.ReferenceType.Constant); if (definition === undefined) { return logic_1.Ternary.Never; } let all = true; let some = false; for (const def of definition) { if (def.type === identifier_1.ReferenceType.BuiltInConstant && def.value === wantedValue) { some = true; } else { all = false; } } if (all) { return logic_1.Ternary.Always; } else { return some ? logic_1.Ternary.Maybe : logic_1.Ternary.Never; } } /** Please use {@link resolveValueOfVariable} */ function resolveToConstants(name, environment) { if (name === undefined) { return undefined; } const definitions = resolveByName(name, environment, identifier_1.ReferenceType.Constant); return definitions?.map(def => def.value); } const AliasHandler = { [vertex_1.VertexType.Value]: (sourceId) => [sourceId], [vertex_1.VertexType.Use]: getUseAlias, [vertex_1.VertexType.FunctionCall]: () => undefined, [vertex_1.VertexType.FunctionDefinition]: () => undefined, [vertex_1.VertexType.VariableDefinition]: () => undefined }; function getUseAlias(sourceId, dataflow, environment) { const definitions = []; // Source is Symbol -> resolve definitions of symbol const identifier = (0, node_id_1.recoverName)(sourceId, dataflow.idMap); if (identifier === undefined) { return undefined; } const defs = resolveByName(identifier, environment); if (defs === undefined) { return undefined; } for (const def of defs) { // If one definition is not constant (or a variable aliasing a constant) // we can't say for sure what value the source has if (def.type === identifier_1.ReferenceType.Variable) { if (def.value === undefined) { return undefined; } definitions.push(...def.value); } else if (def.type === identifier_1.ReferenceType.Constant || def.type === identifier_1.ReferenceType.BuiltInConstant) { definitions.push(def.nodeId); } else { return undefined; } } return definitions; } function getAliases(sourceIds, dataflow, environment) { const definitions = new Set(); for (const sourceId of sourceIds) { const info = dataflow.getVertex(sourceId); if (info === undefined) { return undefined; } const defs = AliasHandler[info.tag](sourceId, dataflow, environment); for (const def of defs ?? []) { definitions.add(def); } } return [...definitions]; } /** Please use {@link resolveValueOfVariable} */ function trackAliasInEnvironments(identifier, use, idMap) { if (identifier === undefined) { return undefined; } const defs = resolveByName(identifier, use); if (defs === undefined) { return undefined; } const values = []; for (const def of defs) { if (def.type === identifier_1.ReferenceType.BuiltInConstant) { values.push(def.value); } else if (def.type === identifier_1.ReferenceType.BuiltInFunction) { // Tracked in #1207 } else if (def.value !== undefined) { /* if there is at least one location for which we have no idea, we have to give up for now! */ if (def.value.length === 0) { return undefined; } for (const id of def.value) { const value = idMap?.get(id)?.content; if (value !== undefined) { values.push(value); } } } } if (values.length == 0) { return undefined; } return values; } function isNestedInLoop(node, ast) { const parent = node?.info.parent; if (node === undefined || !parent) { return false; } const parentNode = ast.get(parent); if (parentNode === undefined) { return false; } if (parentNode.type === type_1.RType.WhileLoop || parentNode.type === type_1.RType.RepeatLoop) { return true; } return isNestedInLoop(parentNode, ast); } function trackAliasesInGraph(id, graph, idMap) { idMap ??= graph.idMap; (0, assert_1.guard)(idMap !== undefined, 'The ID map is required to get the lineage of a node'); const start = graph.getVertex(id); (0, assert_1.guard)(start !== undefined, 'Unable to find start for alias tracking'); const queue = new visiting_queue_1.VisitingQueue(25); const clean = (0, environment_1.initializeCleanEnvironments)(); const cleanFingerprint = (0, fingerprint_1.envFingerprint)(clean); queue.add(id, clean, cleanFingerprint, false); let forceBot = false; const resultIds = []; while (queue.nonEmpty()) { const { id, baseEnvironment } = queue.next(); const res = graph.get(id); if (!res) { continue; } const [vertex, outgoingEdges] = res; const cds = vertex.cds; for (const cd of cds ?? []) { const target = graph.idMap?.get(cd.id); if (target === undefined) { continue; } if (target.type === type_1.RType.WhileLoop || target.type === type_1.RType.RepeatLoop) { forceBot = true; break; } } if (!forceBot && (cds?.length === 0 && isNestedInLoop(idMap.get(id), idMap))) { forceBot = true; } if (forceBot) { break; } if (vertex.tag === vertex_1.VertexType.Value) { resultIds.push(id); continue; } // travel all read and defined-by edges for (const [targetId, edge] of outgoingEdges) { // currently, they have to be exact! if (edge.types === edge_1.EdgeType.Reads || edge.types === edge_1.EdgeType.DefinedBy || edge.types === edge_1.EdgeType.DefinedByOnCall) { queue.add(targetId, baseEnvironment, cleanFingerprint, false); } } } if (forceBot || resultIds.length === 0) { return undefined; } const values = []; for (const id of resultIds) { const node = idMap.get(id); if (node !== undefined) { values.push(node.content); } } return values; } /** * Convenience function using the variable resolver as specified within the configuration file * In the future we may want to have this set once at the start of the analysis * * @see {@link resolveIdToValue} - for a more general approach which "evaluates" a node based on value resolve */ function resolveValueOfVariable(identifier, environment, idMap) { const resolve = (0, config_1.getConfig)().solver.variables; switch (resolve) { case config_1.VariableResolve.Alias: return trackAliasInEnvironments(identifier, environment, idMap); case config_1.VariableResolve.Builtin: return resolveToConstants(identifier, environment); case config_1.VariableResolve.Disabled: return []; default: (0, assert_1.assertUnreachable)(resolve); } } /** * Generalized {@link resolveValueOfVariable} function which evaluates a node based on the value resolve * * @param id - The node id or node to resolve * @param environment - The current environment used for name resolution * @param graph - The graph to resolve in * @param idMap - The id map to resolve the node if given as an id * @param full - Whether to track variables */ function resolveIdToValue(id, { environment, graph, idMap, full }) { idMap ??= graph?.idMap; const node = typeof id === 'object' ? id : idMap?.get(id); if (node === undefined) { return undefined; } switch (node.type) { case type_1.RType.Symbol: if (environment) { return full ? resolveValueOfVariable(node.lexeme, environment, idMap) : undefined; } else if (graph && (0, config_1.getConfig)().solver.variables === config_1.VariableResolve.Alias) { return full ? trackAliasesInGraph(node.info.id, graph, idMap) : undefined; } else { return undefined; } case type_1.RType.String: case type_1.RType.Number: case type_1.RType.Logical: return [node.content]; default: return undefined; } } //# sourceMappingURL=resolve-by-name.js.map