UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

180 lines 9.94 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ABSOLUTE_PATH = void 0; const linter_format_1 = require("../linter-format"); const objects_1 = require("../../util/objects"); const flowr_search_builder_1 = require("../../search/flowr-search-builder"); const range_1 = require("../../util/range"); const linter_tags_1 = require("../linter-tags"); const type_1 = require("../../r-bridge/lang-4.x/ast/model/type"); const strings_1 = require("../../util/text/strings"); const assert_1 = require("../../util/assert"); const read_functions_1 = require("../../queries/catalog/dependencies-query/function-info/read-functions"); const write_functions_1 = require("../../queries/catalog/dependencies-query/function-info/write-functions"); const search_enrichers_1 = require("../../search/search-executor/search-enrichers"); const source_functions_1 = require("../../queries/catalog/dependencies-query/function-info/source-functions"); const vertex_1 = require("../../dataflow/graph/vertex"); const dependencies_query_format_1 = require("../../queries/catalog/dependencies-query/dependencies-query-format"); const resolve_argument_1 = require("../../dataflow/eval/resolve/resolve-argument"); const path_1 = __importDefault(require("path")); const r_string_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-string"); function inferWd(file, wd) { if (wd === '@script') { // we can use the script path as the working directory return file; } else if (wd === '@home') { // we can use the home directory as the working directory return process.env.HOME || process.env.USERPROFILE || ''; } else { return wd; } } // this can be improved by respecting raw strings and supporting more scenarios function buildQuickFix(str, filePath, wd) { if (!wd || !str || !r_string_1.RString.is(str)) { return undefined; } return [{ type: 'replace', loc: range_1.SourceLocation.fromNode(str) ?? range_1.SourceLocation.invalid(), description: `Replace with a relative path to \`${filePath}\``, replacement: str.content.quotes + '.' + path_1.default.sep + path_1.default.relative(wd, filePath) + str.content.quotes }]; } /** return all strings constructable by these functions */ const PathFunctions = new Map([ ['file.path', (df, vtx, ctx) => { const fsep = (0, resolve_argument_1.getArgumentStringValue)(ctx.config.solver.variables, df, vtx, undefined, 'fsep', true, ctx); // in the future we can access `.Platform$file.sep` here const sepValues = fsep?.values()?.flatMap(s => s.values().filter(assert_1.isNotUndefined)).toArray() ?? [path_1.default.sep]; if (sepValues.some(s => s === dependencies_query_format_1.Unknown || (0, assert_1.isUndefined)(s))) { // if we have no fsep, we cannot construct a path return undefined; } const args = (0, resolve_argument_1.getArgumentStringValue)(ctx.config.solver.variables, df, vtx, 'unnamed', undefined, true, ctx); const argValues = args ? Array.from(args.values()).flatMap(v => [...v]) : []; if (!argValues || argValues.length === 0 || argValues.some(v => v === dependencies_query_format_1.Unknown || (0, assert_1.isUndefined)(v))) { // if we have no arguments, we cannot construct a path return undefined; } const results = []; for (const val of sepValues) { results.push(argValues.join(val)); } return results; }] ]); exports.ABSOLUTE_PATH = { /* this can be done better once we have types */ createSearch: (config) => { let q; if (config.include.allStrings) { q = flowr_search_builder_1.Q.all().filter(type_1.RType.String); } else { q = flowr_search_builder_1.Q.fromQuery({ type: 'dependencies', // we use the dependencies query to give us all functions that take a file path as input ignoreDefaultFunctions: true, readFunctions: read_functions_1.ReadFunctions.concat(write_functions_1.WriteFunctions, source_functions_1.SourceFunctions, config.additionalPathFunctions), }); } if (config.include.constructed) { q = q.merge(flowr_search_builder_1.Q.all().filter(vertex_1.VertexType.FunctionCall).with(search_enrichers_1.Enrichment.CallTargets)); /* in the future, we want to directly check whether this is one of the supported functions */ } return q.unique(); }, processSearchResult: (elements, config, data) => { const metadata = { totalConsidered: 0, totalUnknown: 0 }; const queryResults = elements.enrichmentContent(search_enrichers_1.Enrichment.QueryData)?.queries; const regex = config.absolutePathRegex ? new RegExp(config.absolutePathRegex) : undefined; return { results: elements.getElements().flatMap(element => { metadata.totalConsidered++; const node = element.node; const wd = inferWd(node.info.file, config.useAsWd); if (r_string_1.RString.is(node)) { if (node.content.str.length >= 3 && (0, strings_1.isAbsolutePath)(node.content.str, regex)) { return [{ certainty: linter_format_1.LintingResultCertainty.Uncertain, filePath: node.content.str, loc: range_1.SourceLocation.fromNode(node) ?? range_1.SourceLocation.invalid(), quickFix: buildQuickFix(node, node.content.str, wd) }]; } else { return []; } } else if ((0, search_enrichers_1.enrichmentContent)(element, search_enrichers_1.Enrichment.QueryData)) { const result = queryResults[(0, search_enrichers_1.enrichmentContent)(element, search_enrichers_1.Enrichment.QueryData).query]; const mappedStrings = result.read.filter(r => r.value !== undefined && r.value !== dependencies_query_format_1.Unknown && (0, strings_1.isAbsolutePath)(r.value, regex)).map(r => { const elem = data.normalize.idMap.get(r.nodeId); return { certainty: linter_format_1.LintingResultCertainty.Certain, filePath: r.value, loc: elem ? range_1.SourceLocation.fromNode(elem) ?? range_1.SourceLocation.invalid() : range_1.SourceLocation.invalid(), quickFix: buildQuickFix(elem, r.value, wd) }; }); if (mappedStrings.length > 0) { return mappedStrings; } else if (result.read.every(r => r.value !== dependencies_query_format_1.Unknown)) { // if we have no absolute paths, but all paths are known, we can return an empty array return []; } } else { const dfNode = data.dataflow.graph.getVertex(node.info.id); if ((0, vertex_1.isFunctionCallVertex)(dfNode)) { const handler = dfNode.name ? PathFunctions.get(dfNode.name) : undefined; const strings = handler ? handler(data.dataflow.graph, dfNode, data.analyzer.inspectContext()) : []; if (strings) { return strings.filter(s => (0, strings_1.isAbsolutePath)(s, regex)).map(str => ({ certainty: linter_format_1.LintingResultCertainty.Uncertain, filePath: str, loc: range_1.SourceLocation.fromNode(element.node) ?? range_1.SourceLocation.invalid(), quickFix: undefined })); } } // check whether the df node is a function call that returns a file path } metadata.totalUnknown++; return undefined; }).filter(assert_1.isNotUndefined).map(r => (0, objects_1.compactRecord)(r)), '.meta': metadata }; }, prettyPrint: { [linter_format_1.LintingPrettyPrintContext.Query]: result => `Path \`${result.filePath}\` at ${range_1.SourceLocation.format(result.loc)}`, [linter_format_1.LintingPrettyPrintContext.Full]: result => `Path \`${result.filePath}\` at ${range_1.SourceLocation.format(result.loc)} is absolute` }, info: { name: 'Absolute Paths', description: 'Checks whether file paths are absolute.', tags: [linter_tags_1.LintingRuleTag.Robustness, linter_tags_1.LintingRuleTag.Reproducibility, linter_tags_1.LintingRuleTag.Smell, linter_tags_1.LintingRuleTag.QuickFix], // checks all found paths for whether they're absolute to ensure correctness, but doesn't handle non-constant paths so not all will be returned certainty: linter_format_1.LintingRuleCertainty.BestEffort, defaultConfig: { include: { constructed: true, allStrings: false }, additionalPathFunctions: [], absolutePathRegex: undefined, useAsWd: '@script' } } }; //# sourceMappingURL=absolute-path.js.map