@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
291 lines • 13.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeDependenciesQuery = executeDependenciesQuery;
const query_1 = require("../../query");
const dependencies_query_format_1 = require("./dependencies-query-format");
const vertex_1 = require("../../../dataflow/graph/vertex");
const graph_1 = require("../../../dataflow/graph/graph");
const log_1 = require("../../../util/log");
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
const r_function_call_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const visitor_1 = require("../../../r-bridge/lang-4.x/ast/model/processing/visitor");
const assert_1 = require("../../../util/assert");
const objects_1 = require("../../../util/objects");
const library_functions_1 = require("./function-info/library-functions");
const source_functions_1 = require("./function-info/source-functions");
const read_functions_1 = require("./function-info/read-functions");
const write_functions_1 = require("./function-info/write-functions");
const function_info_1 = require("./function-info/function-info");
const identify_link_to_last_call_relation_1 = require("../call-context-query/identify-link-to-last-call-relation");
const r_value_1 = require("../../../dataflow/eval/values/r-value");
const general_1 = require("../../../dataflow/eval/values/general");
const alias_tracking_1 = require("../../../dataflow/eval/resolve/alias-tracking");
const string_constants_1 = require("../../../dataflow/eval/values/string/string-constants");
function collectNamespaceAccesses(data, libraries) {
/* for libraries, we have to additionally track all uses of `::` and `:::`, for this we currently simply traverse all uses */
(0, visitor_1.visitAst)(data.ast.ast, n => {
if (n.type === type_1.RType.Symbol && n.namespace) {
/* we should improve the identification of ':::' */
libraries.push({
nodeId: n.info.id,
functionName: (n.info.fullLexeme ?? n.lexeme).includes(':::') ? ':::' : '::',
libraryName: n.namespace
});
}
});
}
function executeDependenciesQuery(data, queries) {
if (queries.length !== 1) {
log_1.log.warn('Dependencies query expects only up to one query, but got ', queries.length, 'only using the first query');
}
const now = Date.now();
const [query] = queries;
const ignoreDefault = query.ignoreDefaultFunctions ?? false;
const libraryFunctions = getFunctionsToCheck(query.libraryFunctions, ignoreDefault, library_functions_1.LibraryFunctions);
const sourceFunctions = getFunctionsToCheck(query.sourceFunctions, ignoreDefault, source_functions_1.SourceFunctions);
const readFunctions = getFunctionsToCheck(query.readFunctions, ignoreDefault, read_functions_1.ReadFunctions);
const writeFunctions = getFunctionsToCheck(query.writeFunctions, ignoreDefault, write_functions_1.WriteFunctions);
const numberOfFunctions = libraryFunctions.length + sourceFunctions.length + readFunctions.length + writeFunctions.length;
const results = numberOfFunctions === 0 ? { kinds: {}, '.meta': { timing: 0 } } : (0, query_1.executeQueriesOfSameType)(data, ...makeCallContextQuery(libraryFunctions, 'library'), ...makeCallContextQuery(sourceFunctions, 'source'), ...makeCallContextQuery(readFunctions, 'read'), ...makeCallContextQuery(writeFunctions, 'write'));
function getLexeme(argument, id) {
if ((argument && argument !== dependencies_query_format_1.Unknown) || !id) {
return undefined;
}
let get = data.ast.idMap.get(id);
if (get?.type === type_1.RType.Argument) {
get = get.value;
}
return get?.info.fullLexeme ?? get?.lexeme;
}
const libraries = getResults(data, results, 'library', libraryFunctions, (id, vertex, argId, value, linkedIds) => ({
nodeId: id,
functionName: vertex.name,
lexemeOfArgument: getLexeme(value, argId),
libraryName: value ?? dependencies_query_format_1.Unknown,
linkedIds: linkedIds?.length ? linkedIds : undefined
}));
if (!ignoreDefault) {
collectNamespaceAccesses(data, libraries);
}
const sourcedFiles = getResults(data, results, 'source', sourceFunctions, (id, vertex, argId, value, linkedIds) => ({
nodeId: id,
functionName: vertex.name,
file: value ?? dependencies_query_format_1.Unknown,
lexemeOfArgument: getLexeme(value, argId),
linkedIds: linkedIds?.length ? linkedIds : undefined
}));
const readData = getResults(data, results, 'read', readFunctions, (id, vertex, argId, value, linkedIds) => ({
nodeId: id,
functionName: vertex.name,
source: value ?? dependencies_query_format_1.Unknown,
lexemeOfArgument: getLexeme(value, argId),
linkedIds: linkedIds?.length ? linkedIds : undefined
}));
const writtenData = getResults(data, results, 'write', writeFunctions, (id, vertex, argId, value, linkedIds) => ({
nodeId: id,
functionName: vertex.name,
// write functions that don't have argIndex are assumed to write to stdout
destination: value ?? 'stdout',
lexemeOfArgument: getLexeme(value, argId),
linkedIds: linkedIds?.length ? linkedIds : undefined
}));
return {
'.meta': {
timing: Date.now() - now
},
libraries, sourcedFiles, readData, writtenData
};
}
function makeCallContextQuery(functions, kind) {
return functions.map(f => ({
type: 'call-context',
callName: f.name,
callTargets: identify_link_to_last_call_relation_1.CallTargets.MustIncludeGlobal,
includeAliases: false,
callNameExact: true,
subkind: f.name,
linkTo: f.linkTo,
kind
}));
}
function dropInfoOnLinkedIds(linkedIds) {
if (!linkedIds) {
return undefined;
}
return linkedIds.map(id => typeof id === 'object' ? id.id : id);
}
function getResults(data, results, kind, functions, makeInfo) {
const kindEntries = Object.entries(results?.kinds[kind]?.subkinds ?? {});
return kindEntries.flatMap(([name, results]) => results.flatMap(({ id, linkedIds }) => {
const vertex = data.dataflow.graph.getVertex(id);
const info = functions.find(f => f.name === name);
const args = getArgumentValue(data, vertex, info.argIdx, info.argName, info.resolveValue);
const linkedArgs = collectValuesFromLinks(args, data, linkedIds);
const foundValues = linkedArgs ?? args;
if (!foundValues) {
if (info.ignoreIf === 'arg-missing') {
return [];
}
const record = (0, objects_1.compactRecord)(makeInfo(id, vertex, undefined, undefined, dropInfoOnLinkedIds(linkedIds)));
return record ? [record] : [];
}
const results = [];
for (const [arg, values] of foundValues.entries()) {
for (const value of values) {
const result = (0, objects_1.compactRecord)(makeInfo(id, vertex, arg, value, dropInfoOnLinkedIds(linkedIds)));
if (result) {
results.push(result);
}
}
}
return results;
})) ?? [];
}
function collectValuesFromLinks(args, data, linkedIds) {
if (!linkedIds || linkedIds.length === 0) {
return undefined;
}
const hasAtLeastAValue = args !== undefined && [...args.values()].some(set => [...set].some(v => v !== dependencies_query_format_1.Unknown && v !== undefined));
const map = new Map();
for (const linkedId of linkedIds) {
if (typeof linkedId !== 'object' || !linkedId.info) {
continue;
}
const info = linkedId.info;
// do not collect this one
if (hasAtLeastAValue && info.when !== function_info_1.DependencyInfoLinkConstraint.Always) {
continue;
}
// collect this one!
const vertex = data.dataflow.graph.getVertex(linkedId.id);
if (vertex === undefined || vertex.tag !== vertex_1.VertexType.FunctionCall) {
continue;
}
const args = getArgumentValue(data, vertex, info.argIdx, info.argName, info.resolveValue);
if (args === undefined) {
continue;
}
for (const [arg, values] of args.entries()) {
const set = map.get(arg) ?? new Set();
map.set(arg, set);
for (const value of values) {
set.add(value);
}
}
}
return map.size ? map : undefined;
}
function hasCharacterOnly(data, vertex, idMap) {
if (!vertex.args || vertex.args.length === 0 || !idMap) {
return false;
}
const treatAsChar = getArgumentValue(data, vertex, 5, 'character.only', true);
if (!treatAsChar) {
return false;
}
const hasTrue = [...treatAsChar.values()].some(set => set?.has('TRUE'));
const hasFalse = hasTrue ? [...treatAsChar.values()].some(set => set === undefined || set.has('FALSE')) : false;
if (hasTrue && hasFalse) {
return 'maybe';
}
else {
return hasTrue;
}
}
function resolveBasedOnConfig(data, vertex, argument, environment, idMap, resolveValue) {
let full = true;
if (!resolveValue) {
full = false;
}
if (resolveValue === 'library') {
const hasChar = hasCharacterOnly(data, vertex, idMap);
if (hasChar === false) {
if (argument.type === type_1.RType.Symbol) {
return [argument.lexeme];
}
full = false;
}
}
full = true;
const resolved = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(argument, { environment, graph: data.dataflow.graph, full: full }));
if (resolved) {
const values = [];
for (const value of resolved.elements) {
if (!(0, r_value_1.isValue)(value)) {
return undefined;
}
if (value.type === 'string' && (0, r_value_1.isValue)(value.value)) {
values.push(value.value.str);
}
else if (value.type === 'logical' && (0, r_value_1.isValue)(value.value)) {
values.push(value.value.valueOf() ? 'TRUE' : 'FALSE');
}
else if (value.type === 'vector' && (0, r_value_1.isValue)(value.elements)) {
const elements = (0, string_constants_1.collectStrings)(value.elements);
if (elements === undefined) {
return undefined;
}
values.push(...elements);
}
else {
return undefined;
}
}
return values;
}
}
/**
* Get the values of all arguments matching the criteria.
*/
function getArgumentValue(data, vertex, argumentIndex, argumentName, resolveValue) {
const graph = data.dataflow.graph;
if (argumentName) {
const arg = vertex?.args.findIndex(arg => arg !== r_function_call_1.EmptyArgument && arg.name === argumentName);
if (arg >= 0) {
argumentIndex = arg;
}
}
if (!vertex || argumentIndex === undefined) {
return undefined;
}
if (argumentIndex === 'unnamed') {
// return all unnamed arguments
const references = vertex.args.filter(arg => arg !== r_function_call_1.EmptyArgument && !arg.name).map(graph_1.getReferenceOfArgument).filter(assert_1.isNotUndefined);
const map = new Map();
for (const ref of references) {
let valueNode = graph.idMap?.get(ref);
if (valueNode?.type === type_1.RType.Argument) {
valueNode = valueNode.value;
}
if (valueNode) {
// this should be evaluated in the callee-context
const values = resolveBasedOnConfig(data, vertex, valueNode, vertex.environment, graph.idMap, resolveValue) ?? [dependencies_query_format_1.Unknown];
map.set(ref, new Set(values));
}
}
return map;
}
if (argumentIndex < vertex.args.length) {
const arg = (0, graph_1.getReferenceOfArgument)(vertex.args[argumentIndex]);
if (!arg) {
return undefined;
}
let valueNode = graph.idMap?.get(arg);
if (valueNode?.type === type_1.RType.Argument) {
valueNode = valueNode.value;
}
if (valueNode) {
const values = resolveBasedOnConfig(data, vertex, valueNode, vertex.environment, graph.idMap, resolveValue) ?? [dependencies_query_format_1.Unknown];
return new Map([[arg, new Set(values)]]);
}
}
return undefined;
}
function getFunctionsToCheck(customFunctions, ignoreDefaultFunctions, defaultFunctions) {
let functions = ignoreDefaultFunctions ? [] : [...defaultFunctions];
if (customFunctions) {
functions = functions.concat(customFunctions);
}
return functions;
}
//# sourceMappingURL=dependencies-query-executor.js.map