@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
168 lines • 7.79 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkingDirectory = void 0;
const path_1 = __importDefault(require("path"));
const vertex_1 = require("../../graph/vertex");
const happens_before_1 = require("../../../control-flow/happens-before");
const logic_1 = require("../../../util/logic");
const identifier_1 = require("../../environments/identifier");
const resolve_argument_1 = require("./resolve-argument");
const dependencies_query_format_1 = require("../../../queries/catalog/dependencies-query/dependencies-query-format");
const strings_1 = require("../../../util/text/strings");
const info_1 = require("../../info");
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
const edge_1 = require("../../graph/edge");
const defaultmap_1 = require("../../../util/collections/defaultmap");
const files_1 = require("../../../util/files");
const built_in_source_1 = require("../../internal/process/functions/call/built-in/built-in-source");
const graph_1 = require("../../graph/graph");
/** functions recognized as changing the working directory; a registry so more (e.g. `withr::with_dir`) can be added */
const ChangeFunctions = [
{ name: 'setwd', namespace: 'base', argIdx: 0, argName: 'dir' }
];
const CandidateCap = 64;
function matchSpec(name) {
if (name === undefined) {
return undefined;
}
const bare = identifier_1.Identifier.getName(name);
const ns = identifier_1.Identifier.getNamespace(name);
return ChangeFunctions.find(s => s.name === bare && (ns === undefined || ns === s.namespace));
}
function isAbsoluteDir(dir) {
return dir.startsWith('~') || (0, strings_1.isAbsolutePath)(dir, undefined);
}
/** apply one `setwd(dir)`: an absolute (or `~`-rooted) `dir` replaces `current`, a relative `dir` joins onto it */
function applyChange(current, dir) {
const d = (0, files_1.toPosixPath)(dir);
return isAbsoluteDir(dir) ? path_1.default.posix.normalize(d) : path_1.default.posix.join((0, files_1.toPosixPath)(current), d);
}
function guardIn(g, set) {
return set.some(b => b.id === g.id && b.when === g.when);
}
function subsumed(inner, outer) {
return inner.every(g => guardIn(g, outer));
}
function consistent(inner, outer) {
return inner.every(g => !outer.some(b => b.id === g.id && b.when !== g.when));
}
function mergeGuards(a, b) {
const out = [...a];
for (const g of b) {
if (!guardIn(g, out)) {
out.push(g);
}
}
return out;
}
/** the changes ordered so predecessors come first; parallel branches keep an arbitrary relative order */
function topoOrder(changes, cfg) {
const remaining = [...changes];
const result = [];
while (remaining.length > 0) {
const idx = Math.max(0, remaining.findIndex(a => remaining.every(b => b === a || (0, happens_before_1.happensBefore)(cfg, b.id, a.id) === logic_1.Ternary.Never)));
result.push(remaining.splice(idx, 1)[0]);
}
return result;
}
function enclosingFunction(idMap, id) {
let cursor = idMap?.get(id)?.info.parent;
while (cursor !== undefined) {
const node = idMap?.get(cursor);
if (node?.type === type_1.RType.FunctionDefinition) {
return node.info.id;
}
cursor = node?.info.parent;
}
return undefined;
}
function isIterated(cds, idMap) {
return cds.some(cd => info_1.ControlDependency.isIterated(cd, idMap));
}
function resolveAt(target, targetCds, baseWd, changes, cfg) {
const relevant = changes.filter(c => (0, happens_before_1.happensBefore)(cfg, c.id, target) !== logic_1.Ternary.Never);
// a setwd hidden in a function may still have run before target via a call happensBefore cannot see
if (changes.some(c => c.enclosedInFunction && !relevant.includes(c))) {
return { candidates: [], certain: false };
}
if (relevant.length === 0) {
return { candidates: [baseWd], certain: true };
}
if (relevant.some(c => c.dir === undefined || (c.iterated && !isAbsoluteDir(c.dir)))) {
return { candidates: [], certain: false };
}
let states = [{ wd: baseWd, guards: [] }];
for (const c of topoOrder(relevant, cfg)) {
const next = [];
for (const s of states) {
if (!consistent(c.cds, s.guards)) {
next.push(s);
continue;
}
const ran = { wd: applyChange(s.wd, c.dir), guards: mergeGuards(s.guards, c.cds) };
if (!c.iterated && subsumed(c.cds, s.guards)) {
next.push(ran);
}
else {
const neg = c.cds.length === 1 ? [(0, info_1.negateControlDependency)(c.cds[0])] : [];
next.push({ wd: s.wd, guards: mergeGuards(s.guards, neg) });
next.push(ran);
}
}
states = next;
if (states.length > CandidateCap) {
return { candidates: [], certain: false };
}
}
const candidates = [...new Set(states.filter(s => consistent(targetCds, s.guards)).map(s => s.wd))];
return { candidates, certain: candidates.length === 1 };
}
function collect(graph, ctx) {
const out = [];
const byFn = new defaultmap_1.DefaultMap(() => []);
for (const [id, vertex] of graph.verticesOfType(vertex_1.VertexType.FunctionCall)) {
const spec = matchSpec(vertex.name);
if (spec === undefined) {
continue;
}
const resolved = (0, resolve_argument_1.getArgumentStringValue)(ctx.config.solver.variables, graph, vertex, spec.argIdx, spec.argName, true, ctx);
const values = resolved ? Array.from(resolved.values()).flatMap(s => Array.from(s)) : [];
const known = values.filter(v => v !== undefined && v !== dependencies_query_format_1.Unknown);
const dir = values.length > 0 && known.length === values.length && new Set(known).size === 1 ? known[0] : undefined;
const cds = vertex.cds ?? [];
const iterated = isIterated(cds, graph.idMap);
const fn = enclosingFunction(graph.idMap, id);
if (fn === undefined) {
out.push({ id, dir, cds, iterated, enclosedInFunction: false });
}
else {
byFn.get(fn).push({ dir, cds, iterated });
}
}
// a function's setwd is propagated to its call sites; only a lone unconditional setwd has a site-independent effect
for (const [fn, setwds] of byFn.entries()) {
const net = setwds.length === 1 && setwds[0].cds.length === 0 && !setwds[0].iterated ? setwds[0].dir : undefined;
for (const [site, edge] of graph.ingoingEdges(fn) ?? graph_1.NoEdges) {
if (!edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Calls)) {
continue;
}
const cds = graph.getVertex(site)?.cds ?? [];
out.push({ id: site, dir: net, cds, iterated: isIterated(cds, graph.idMap), enclosedInFunction: enclosingFunction(graph.idMap, site) !== undefined });
}
}
return out;
}
/** Resolve R's process-global working directory (sensitive to `setwd`) at a program point; path-sensitive, unbounded rather than wrong under loops/functions. */
exports.WorkingDirectory = {
collect,
resolveAt,
/** reusable `(callId, scriptFile?) => candidate roots`, collecting the changes once for the whole graph */
rootsResolver(graph, cfg, ctx) {
const changes = collect(graph, ctx);
return (id, file) => resolveAt(id, graph.getVertex(id)?.cds ?? [], file ? (0, built_in_source_1.platformDirname)(file) : '.', changes, cfg).candidates;
}
};
//# sourceMappingURL=resolve-working-directory.js.map