UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

168 lines 7.79 kB
"use strict"; 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