UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

273 lines 13.8 kB
"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 identifier_1 = require("../../../dataflow/environments/identifier"); const df_helper_1 = require("../../../dataflow/graph/df-helper"); 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"); const graph_1 = require("../../../dataflow/graph/graph"); const link_devices_1 = require("./link-devices"); /** * 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 const custom = getFunctionsToCheck(undefined, category, undefined, false, value.functions); functions.set(category, [...(functions.get(category) ?? []), ...custom]); } } const queryResults = !functions.values().toArray().some(f => f.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]; }))); /* also without a device: a plot on screen has no file, but still has the addons drawn onto it */ if (results.visualize?.length) { (0, link_devices_1.linkPlotsToDevices)(results.write ?? [], results.visualize, dataflow, normalize); } 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 functionName = df_helper_1.Dataflow.qualify(id, dfg, false) ?? vertex.name; if (info.package !== undefined) { const callNamespace = identifier_1.Identifier.getNamespace(functionName); if (callNamespace !== undefined && callNamespace !== info.package) { continue; } } 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; } function ignoreOnArgSet() { if (info.ignoreIf !== 'arg-set') { return; } const hasArg = (name, index) => vertex.args.some((arg, idx) => graph_1.FunctionArgument.isNamed(arg) && arg.name === name || graph_1.FunctionArgument.isPositional(arg) && idx === index); if (hasArg(info.argName, info.argIdx)) { return false; } const margs = info.additionalArgs?.argSet; (0, assert_1.guard)(margs, 'Need additional argument argSet when checking for arg-set'); return hasArg(margs.argName, margs.argIdx); } 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, 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() || ignoreOnArgSet()) { continue; } for (const [arg, values] of foundValues.entries()) { for (const value of values) { let resolvedValue = value; if (info.stringReplacements && resolvedValue !== undefined && Object.hasOwn(info.stringReplacements, resolvedValue)) { resolvedValue = info.stringReplacements[resolvedValue]; } const dep = resolvedValue ? d.getDependency(resolvedValue) ?? undefined : undefined; const lexeme = getLexeme(value, arg); finalResults.push((0, objects_1.compactRecord)({ nodeId: id, functionName, /* only a value we could not make sense of is worth asking about further */ argumentId: lexeme === undefined ? undefined : arg, lexemeOfArgument: lexeme, linkedIds: linked?.length ? linked : undefined, value: resolvedValue ?? info.defaultValue ?? defaultValue, versionConstraints: dep?.versionConstraints, derivedRange: dep?.effectiveRange, namespaceInfo: dep?.namespaceInfo })); } } } } return finalResults; function getLexeme(argument, id) { if ((argument && argument !== dependencies_query_format_1.Unknown && argument !== dependencies_query_format_1.Constant) || !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 !== dependencies_query_format_1.Constant && 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_1.FunctionCallVertex.is(vertex)) { 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.includes(functionFlag))) { return []; } let functions = ignoreDefaultFunctions ? [] : defaultFunctions.slice(); if (customFunctions) { functions = functions.concat(customFunctions); } return functions; } //# sourceMappingURL=dependencies-query-executor.js.map