@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
187 lines • 7.85 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowrAnalyzerCache = void 0;
const flowr_cache_1 = require("./flowr-cache");
const default_pipelines_1 = require("../../core/steps/pipeline/default-pipelines");
const assert_1 = require("../../util/assert");
const flowr_analyzer_controlflow_cache_1 = require("./flowr-analyzer-controlflow-cache");
const call_graph_1 = require("../../dataflow/graph/call-graph");
function isTreeSitterParse(parse) {
return typeof parse === 'object' && parse !== null && 'files' in parse
&& parse.files.every(f => typeof f.parsed === 'object' && f.parsed !== null && 'rootNode' in f.parsed);
}
/**
* This provides the full analyzer caching layer, please avoid using this directly
* and prefer the {@link FlowrAnalyzer}.
*/
class FlowrAnalyzerCache extends flowr_cache_1.FlowrCache {
args;
pipeline = undefined;
controlFlowCache = undefined;
callGraphCache = undefined;
constructor(args) {
super();
this.args = args;
this.initCacheProviders();
}
initCacheProviders() {
this.pipeline = (0, default_pipelines_1.createDataflowPipeline)(this.args.parser, {
context: this.args.context,
getId: this.args.getId
});
this.controlFlowCache = new flowr_analyzer_controlflow_cache_1.FlowrAnalyzerControlFlowCache();
this.callGraphCache = undefined;
this.computeIfAbsent(true, () => this.pipeline?.getResults(true));
}
static create(data) {
return new FlowrAnalyzerCache(data);
}
/** Free the current parse trees: tree-sitter trees live on the WASM heap, which the GC does not reclaim. */
disposeParse() {
const files = this.peekParse()?.files;
for (const file of files ?? []) {
file.parsed?.delete?.();
}
}
receive(event) {
super.receive(event);
const type = event.type;
if (type === "full" /* InvalidationEventType.Full */) {
this.disposeParse();
}
switch (type) {
case "full" /* InvalidationEventType.Full */:
case "single-file-invalidate" /* InvalidationEventType.SingleFileInvalidate */:
this.initCacheProviders();
break;
default:
(0, assert_1.assertUnreachable)(type);
}
}
get() {
/* this will do a ref assignment, so indirect force */
return this.computeIfAbsent(false, () => this.pipeline?.getResults(true));
}
reset() {
this.receive({ type: "full" /* InvalidationEventType.Full */ });
}
async runTapeUntil(force, until) {
(0, assert_1.guard)(this.args.context.files.loadingOrder.getUnorderedRequests().length > 0, 'At least one request must be set to run the analysis pipeline');
if (force) {
this.reset();
}
/* the contingent belongs to the analysis, so stepping the tape starts one of its own */
return this.args.context.gas.withGas(undefined, () => this.stepTapeUntil(until));
}
async stepTapeUntil(until) {
let g;
while ((g = until()) === undefined && this.pipeline.hasNextStep()) {
await this.pipeline.nextStep();
}
this.storeIncrementalSnapshotIfAvailable();
(0, assert_1.guard)(g !== undefined, 'Could not reach the desired pipeline step, invalid cache state(?)');
return g;
}
storeIncrementalSnapshotIfAvailable() {
if (this.args.parser.name !== 'tree-sitter') {
return;
}
const parse = this.peekParse();
if (isTreeSitterParse(parse)) {
this.args.context.inc.storeOldParseResults(parse);
}
}
/**
* Get the parse output for the request, parsing if necessary.
* @param force - Do not use the cache, instead force a new parse.
* @see {@link FlowrAnalyzerCache#peekParse} - to get the parse output if already available without triggering a new parse.
*/
async parse(force) {
const d = this.get();
return this.runTapeUntil(force, () => d.parse);
}
/**
* Get the parse output for the request if already available, otherwise return `undefined`.
* This will not trigger a new parse.
* @see {@link FlowrAnalyzerCache#parse} - to get the parse output, parsing if necessary.
*/
peekParse() {
return this.get()?.parse;
}
/**
* Get the normalized abstract syntax tree for the request, normalizing if necessary.
* @param force - Do not use the cache, instead force new analyses.
* @see {@link FlowrAnalyzerCache#peekNormalize} - to get the normalized AST if already available without triggering a new normalization.
*/
async normalize(force) {
const d = this.get();
return this.runTapeUntil(force, () => d.normalize);
}
/**
* Get the normalized abstract syntax tree for the request if already available, otherwise return `undefined`.
* This will not trigger a new normalization.
* @see {@link FlowrAnalyzerCache#normalize} - to get the normalized AST, normalizing if necessary.
*/
peekNormalize() {
return this.get()?.normalize;
}
/**
* Get the dataflow graph for the request, computing if necessary.
* @param force - Do not use the cache, instead force new analyses.
* @see {@link FlowrAnalyzerCache#peekDataflow} - to get the dataflow graph if already available without triggering a new computation.
*/
async dataflow(force) {
const d = this.get();
return this.runTapeUntil(force, () => d.dataflow);
}
/**
* Get the dataflow graph for the request if already available, otherwise return `undefined`.
* This will not trigger a new computation.
* @see {@link FlowrAnalyzerCache#dataflow} - to get the dataflow graph, computing if necessary.
*/
peekDataflow() {
return this.get()?.dataflow;
}
/**
* Get the control flow graph (CFG) for the request, computing if necessary.
* @param force - Do not use the cache, instead force new analyses.
* @param kind - The kind of CFG that is requested.
* @param simplifications - Simplification passes to be applied to the CFG.
*/
async controlflow(force, kind, simplifications) {
const cfgInfo = {
ctx: this.args.context,
cfgQuick: this.peekDataflow()?.cfgQuick,
ast: async () => await this.normalize(),
dfg: async () => await this.dataflow()
};
return this.controlFlowCache.get(force, kind, cfgInfo, simplifications);
}
/**
* Get the control flow graph (CFG) for the request if already available, otherwise return `undefined`.
* @param kind - The kind of CFG that is requested.
* @param simplifications - Simplification passes to be applied to the CFG.
* @see {@link FlowrAnalyzerCache#controlflow} - to get the control flow graph, computing if necessary.
*/
peekControlflow(kind, simplifications) {
return this.controlFlowCache.peek(kind, simplifications);
}
/**
* Get the call graph for the request, computing if necessary.
* @param force - Do not use the cache, instead force new analyses.
*/
async callGraph(force) {
if (!this.callGraphCache || force) {
this.callGraphCache = call_graph_1.CallGraph.compute((await this.dataflow(force)).graph);
}
return this.callGraphCache;
}
/**
* Get the call graph for the request if already available, otherwise return `undefined`.
*/
peekCallGraph() {
return this.callGraphCache;
}
}
exports.FlowrAnalyzerCache = FlowrAnalyzerCache;
//# sourceMappingURL=flowr-analyzer-cache.js.map