@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
180 lines • 9.94 kB
JavaScript
;
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