@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
109 lines • 4.75 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VisitingQueue = void 0;
const fingerprint_1 = require("./fingerprint");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const gas_1 = require("../../gas");
const static_slicer_1 = require("./static-slicer");
/** How many nodes the traversal visits between two {@link GasFeatureKey.Slicer|gas} checks. */
const GasCheckEvery = 512;
class VisitingQueue {
threshold;
timesHitThreshold = 0;
seen = new Map();
seenByCache = new Set();
idThreshold = new Map();
queue = [];
cache = new Map();
// the set of potential additions holds nodes which may be added if a second edge deems them relevant (e.g., found with the `defined-by-on-call` edge)
// additionally it holds which node id added the addition so we can separate their inclusion on the structure
potentialAdditions = new Map();
cachedCallTargets = new Map();
/** whether the dataflow graph has a vertex for an id, i.e. whether the traversal could continue from it */
isGraphVertex;
gas;
stoppedEarly = false;
untilGasCheck = 0;
/** entries dequeued, see {@link SliceProgress} */
visited = 0;
constructor(threshold, cache, isGraphVertex, gas) {
this.threshold = threshold;
this.cache = cache;
this.isGraphVertex = isGraphVertex;
this.gas = gas;
}
/**
* Adds a node to the queue if it has not been seen before.
* @param target - the node to add
* @param env - the environment the node is traversed in
* @param envFingerprint - the fingerprint of the environment
* @param onlyForSideEffects - whether the node is only used for its side effects
*/
add(target, env, envFingerprint, onlyForSideEffects) {
/* a built-in without a vertex is R's own definition (`x <- 1` reads `built-in:<-`), a dead end the traversal
* would drop right back out of, so it only ever widened the result */
if (node_id_1.NodeId.isBuiltIn(target) && this.isGraphVertex?.(target) === false) {
return;
}
const idCounter = this.idThreshold.get(target) ?? 0;
if (idCounter > this.threshold) {
this.timesHitThreshold++;
return;
}
/* we do not include the in call part in the fingerprint as it is 'deterministic' from the source position */
const print = (0, fingerprint_1.fingerprint)(target, envFingerprint, onlyForSideEffects);
if (!this.seen.has(print)) {
const cached = this.cache?.get(print);
if (cached) {
this.seenByCache.add(target);
for (const id of cached) {
this.queue.push({ id, baseEnvironment: env, envFingerprint, onlyForSideEffects });
}
}
this.idThreshold.set(target, idCounter + 1);
this.seen.set(print, target);
this.queue.push({ id: target, baseEnvironment: env, envFingerprint, onlyForSideEffects });
}
}
next() {
this.visited++;
return this.queue.pop();
}
/** Whether there is anything left to visit, which the traversal is out of gas for as soon as it is exhausted. */
nonEmpty() {
return this.queue.length > 0 && !this.outOfGas();
}
/**
* The traversal is synchronous, so a caller can only bound it from within. Gas is polled every
* {@link GasCheckEvery} nodes, which keeps even an enabled check off the per-node path.
*/
outOfGas() {
if (this.gas === undefined || this.untilGasCheck-- > 0) {
return this.stoppedEarly;
}
this.untilGasCheck = GasCheckEvery - 1;
if (!this.stoppedEarly && this.gas.checkGas(gas_1.GasFeatureKey.Slicer) >= 2 /* GasLevel.Critical */) {
this.stoppedEarly = true;
static_slicer_1.slicerLogger.warn(`slicing ran out of gas, the slice is incomplete (${gas_1.GasWikiRef})`);
}
return this.stoppedEarly;
}
hasId(id) {
return this.idThreshold.has(id);
}
memoizeCallTargets(id, targets) {
if (!this.cachedCallTargets.has(id)) {
this.cachedCallTargets.set(id, targets());
}
return this.cachedCallTargets.get(id);
}
status() {
return {
timesHitThreshold: this.timesHitThreshold,
result: new Set([...this.seen.values(), ...this.seenByCache]),
...(this.stoppedEarly ? { stoppedEarly: true, progress: { visited: this.visited, frontier: this.queue.length } } : {})
};
}
}
exports.VisitingQueue = VisitingQueue;
//# sourceMappingURL=visiting-queue.js.map