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