UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

288 lines 12.4 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 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 resolve_by_name_1 = require("../../../dataflow/environments/resolve-by-name"); 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"); 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, 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; } } return (0, resolve_by_name_1.resolveIdToValue)(argument, { environment, graph: data.dataflow.graph, full }); } function unwrapRValue(value) { if (value === undefined) { return undefined; } switch (typeof value) { case 'string': return value; case 'number': return value.toString(); case 'boolean': return value ? 'TRUE' : 'FALSE'; } if (typeof value !== 'object' || value === null) { return JSON.stringify(value); } if ('str' in value) { return value.str; } else if ('num' in value) { return value.num.toString(); } else { return JSON.stringify(value); } } /** * 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) { // TDODO: extend vector support etc. // this should be evaluated in the callee-context const values = resolveBasedOnConfig(data, vertex, valueNode, vertex.environment, graph.idMap, resolveValue) ?.map(unwrapRValue) ?? [dependencies_query_format_1.Unknown]; map.set(ref, new Set(values)); } } return map; } if (vertex.args.length > argumentIndex) { 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) ?.map(unwrapRValue) ?? [dependencies_query_format_1.Unknown]; return new Map([[arg, new Set(values)]]); } } return undefined; } function getFunctionsToCheck(customFunctions, ignoreDefaultFunctions, defaultFunctions) { const functions = ignoreDefaultFunctions ? [] : [...defaultFunctions]; if (customFunctions) { functions.push(...customFunctions); } return functions; } //# sourceMappingURL=dependencies-query-executor.js.map