@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
237 lines • 11.5 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 type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
const objects_1 = require("../../../util/objects");
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 resolve_argument_1 = require("../../../dataflow/eval/resolve/resolve-argument");
const assert_1 = require("../../../util/assert");
const log_1 = require("../../../util/log");
const model_1 = require("../../../r-bridge/lang-4.x/ast/model/model");
/**
* Executes a dependencies query.
*/
async function executeDependenciesQuery({ analyzer, }, queries) {
let query = queries[0];
if (queries.length !== 1) {
// merge
for (let i = 1; i < queries.length; i++) {
const q = queries[i];
query = {
...query,
enabledCategories: query.enabledCategories === undefined && q.enabledCategories === undefined ? undefined : [...(query.enabledCategories ?? []), ...(q.enabledCategories ?? [])],
ignoreDefaultFunctions: query.ignoreDefaultFunctions || q.ignoreDefaultFunctions,
additionalCategories: {
...query.additionalCategories,
...q.additionalCategories
}
};
}
log_1.log.info('Merged multiple dependencies queries into one:', query);
}
const data = { analyzer };
const normalize = await analyzer.normalize();
const dataflow = await analyzer.dataflow();
const config = analyzer.flowrConfig;
const now = Date.now();
const ignoreDefault = query.ignoreDefaultFunctions ?? false;
const functions = new Map(Object.entries(dependencies_query_format_1.DefaultDependencyCategories).map(([c, v]) => {
return [c, getFunctionsToCheck(query[`${c}Functions`], c, query.enabledCategories, ignoreDefault, v.functions)];
}));
if (query.additionalCategories !== undefined) {
for (const [category, value] of Object.entries(query.additionalCategories)) {
// custom categories only use the "functions" collection and do not allow specifying additional functions in the object itself, so we "undefined" a lot here
functions.set(category, getFunctionsToCheck(undefined, category, undefined, false, value.functions));
}
}
const queryResults = functions.values().toArray().flat().length === 0 ? { kinds: {}, '.meta': { timing: 0 } } :
await (0, query_1.executeQueriesOfSameType)(data, functions.entries().flatMap(makeCallContextQuery).toArray());
const g = (0, dependencies_query_format_1.getAllCategories)(queries);
const enabled = query.enabledCategories;
const results = Object.fromEntries(await Promise.all(functions.entries().map(async ([c, f]) => {
const results = getResults(queries, { dataflow, config, normalize }, queryResults, c, f, data);
// only default categories allow additional analyses, so we null-coalesce here!
if (enabled === undefined || (enabled?.length > 0 && enabled.includes(c))) {
await g[c]?.additionalAnalysis?.(data, ignoreDefault, f, queryResults, results);
}
return [c, results];
})));
return {
'.meta': {
timing: Date.now() - now
},
...results,
};
}
function makeCallContextQuery([kind, functions]) {
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);
}
const readOnlyModes = new Set(['r', 'rt', 'rb']);
const writeOnlyModes = new Set(['w', 'wt', 'wb', 'a', 'at', 'ab']);
function getResults(queries, { dataflow, config, normalize }, results, kind, functions, data) {
const defaultValue = (0, dependencies_query_format_1.getAllCategories)(queries)[kind].defaultValue;
const vars = config.solver.variables;
const functionMap = new Map(functions.map(f => [f.name, f]));
const kindEntries = Object.entries(results?.kinds[kind]?.subkinds ?? {});
const finalResults = [];
const ictx = data.analyzer.inspectContext();
const d = ictx.deps;
const dfg = dataflow.graph;
for (const [name, results] of kindEntries) {
for (const { id, linkedIds } of results) {
const vertex = dfg.getVertex(id);
const info = functionMap.get(name);
const args = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, info.argIdx, info.argName, info.resolveValue, ictx);
const linkedArgs = collectValuesFromLinks(args, { dataflow, config, ctx: ictx }, linkedIds);
const linked = dropInfoOnLinkedIds(linkedIds);
function ignoreOnArgVal() {
if (info.ignoreIf === 'arg-true' || info.ignoreIf === 'arg-false') {
const margs = info.additionalArgs?.val;
(0, assert_1.guard)(margs, 'Need additional argument val when checking for arg-true');
const valArgs = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
const valValues = valArgs?.values().flatMap(v => Array.from(v)).toArray() ?? [];
if (valValues.length === 0) {
return false;
}
if (info.ignoreIf === 'arg-true' && valValues.every(v => v === 'TRUE')) {
// all values are TRUE, so we can ignore this
return true;
}
else if (info.ignoreIf === 'arg-false' && valValues.every(v => v === 'FALSE')) {
// all values are FALSE, so we can ignore this
return true;
}
}
return false;
}
const foundValues = linkedArgs ?? args;
if (!foundValues) {
if (info.ignoreIf === 'arg-missing') {
continue;
}
else if (ignoreOnArgVal()) {
continue;
}
const record = (0, objects_1.compactRecord)({
nodeId: id,
functionName: vertex.name,
lexemeOfArgument: undefined,
linkedIds: linked?.length ? linked : undefined,
value: info.defaultValue ?? defaultValue
});
if (record) {
finalResults.push(record);
}
continue;
}
else if (info.ignoreIf === 'mode-only-read' || info.ignoreIf === 'mode-only-write') {
const margs = info.additionalArgs?.mode;
(0, assert_1.guard)(margs, 'Need additional argument mode when checking for mode');
const modeArgs = (0, resolve_argument_1.getArgumentStringValue)(vars, dfg, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext());
const modeValues = modeArgs?.values().flatMap(v => Array.from(v)) ?? [];
if (info.ignoreIf === 'mode-only-read' && modeValues.every(m => m && readOnlyModes.has(m))) {
// all modes are read-only, so we can ignore this
continue;
}
else if (info.ignoreIf === 'mode-only-write' && modeValues.every(m => m && writeOnlyModes.has(m))) {
// all modes are write-only, so we can ignore this
continue;
}
}
else if (ignoreOnArgVal()) {
continue;
}
for (const [arg, values] of foundValues.entries()) {
for (const value of values) {
const dep = value ? d.getDependency(value) ?? undefined : undefined;
finalResults.push((0, objects_1.compactRecord)({
nodeId: id,
functionName: vertex.name,
lexemeOfArgument: getLexeme(value, arg),
linkedIds: linked?.length ? linked : undefined,
value: value ?? info.defaultValue ?? defaultValue,
versionConstraints: dep?.versionConstraints,
derivedVersion: dep?.derivedVersion,
namespaceInfo: dep?.namespaceInfo
}));
}
}
}
}
return finalResults;
function getLexeme(argument, id) {
if ((argument && argument !== dependencies_query_format_1.Unknown) || !id) {
return undefined;
}
let get = normalize.idMap.get(id);
if (get?.type === type_1.RType.Argument) {
get = get.value;
}
return model_1.RNode.lexeme(get);
}
}
function collectValuesFromLinks(args, data, linkedIds) {
if (!linkedIds || linkedIds.length === 0) {
return undefined;
}
const hasAtLeastAValue = args !== undefined && args.values().flatMap(x => Array.from(x)).toArray().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?.tag !== vertex_1.VertexType.FunctionCall) {
continue;
}
const args = (0, resolve_argument_1.getArgumentStringValue)(data.config.solver.variables, data.dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue, data.ctx);
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 getFunctionsToCheck(customFunctions, functionFlag, enabled, ignoreDefaultFunctions, defaultFunctions) {
// "If unset or empty, all function types are searched for."
if (enabled !== undefined && (enabled?.length === 0 || enabled.indexOf(functionFlag) < 0)) {
return [];
}
let functions = ignoreDefaultFunctions ? [] : defaultFunctions.slice();
if (customFunctions) {
functions = functions.concat(customFunctions);
}
return functions;
}
//# sourceMappingURL=dependencies-query-executor.js.map