@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
205 lines • 10.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processExpressionList = processExpressionList;
const info_1 = require("../../../../../info");
const processor_1 = require("../../../../../processor");
const linker_1 = require("../../../../linker");
const assert_1 = require("../../../../../../util/assert");
const unpack_argument_1 = require("../argument/unpack-argument");
const common_1 = require("../common");
const node_id_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id");
const graph_1 = require("../../../../../graph/graph");
const identifier_1 = require("../../../../../environments/identifier");
const resolve_by_name_1 = require("../../../../../environments/resolve-by-name");
const edge_1 = require("../../../../../graph/edge");
const vertex_1 = require("../../../../../graph/vertex");
const scoping_1 = require("../../../../../environments/scoping");
const overwrite_1 = require("../../../../../environments/overwrite");
const logger_1 = require("../../../../../logger");
const log_1 = require("../../../../../../util/log");
const reference_to_maybe_1 = require("../../../../../environments/reference-to-maybe");
const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name");
function linkReadNameToWriteIfPossible(read, environments, listEnvironments, remainingRead, nextGraph) {
const readName = read.name && identifier_1.Identifier.isDotDotDotAccess(read.name) ? identifier_1.Identifier.dotdotdot() : read.name;
const probableTarget = readName ? (0, resolve_by_name_1.resolveByName)(readName, environments, read.type) : undefined;
// record if at least one has not been defined
if (probableTarget === undefined || probableTarget.some(t => !listEnvironments.has(t.nodeId) || !(0, info_1.happensInEveryBranch)(t.cds))) {
const readId = readName ? identifier_1.Identifier.getName(readName) : undefined;
const has = remainingRead.get(readId);
if (has) {
if (!has.some(h => h.nodeId === read.nodeId && h.name === read.name && h.cds === read.cds)) {
has.push(read);
}
}
else {
remainingRead.set(readId, [read]);
}
}
// keep it, for we have no target, as read-ids are unique within the same fold, this should work for same links
// we keep them if they are defined outside the current parent and maybe throw them away later
if (probableTarget === undefined) {
return;
}
const rid = read.nodeId;
for (const target of probableTarget) {
const tid = target.nodeId;
if (node_id_1.NodeId.isBuiltIn(target.definedAt) && (read.type === identifier_1.ReferenceType.Function || read.type === identifier_1.ReferenceType.BuiltInFunction)) {
nextGraph.addEdge(rid, tid, edge_1.EdgeType.Reads | edge_1.EdgeType.Calls);
}
else {
nextGraph.addEdge(rid, tid, edge_1.EdgeType.Reads);
}
}
}
function updateSideEffectsForCalledFunctions(calledEnvs, inputEnvironment, nextGraph, localDefs) {
for (const { functionCall, called } of calledEnvs) {
let callDependencies = null;
for (const calledFn of called) {
(0, assert_1.guard)(calledFn.tag === vertex_1.VertexType.FunctionDefinition, 'called function must be a function definition');
// only merge the environments they have in common
let environment = calledFn.subflow.environment;
while (environment.level > inputEnvironment.level) {
environment = (0, scoping_1.popLocalEnvironment)(environment);
}
// update alle definitions to be defined at this function call
let current = environment.current;
let hasUpdate = false;
while (!current?.builtInEnv) {
for (const definitions of current.memory.values()) {
for (const def of definitions) {
if (!node_id_1.NodeId.isBuiltIn(def.definedAt)) {
hasUpdate = true;
nextGraph.addEdge(def.nodeId, functionCall, edge_1.EdgeType.SideEffectOnCall);
}
}
}
current = current.parent;
}
if (hasUpdate) {
// we update all definitions to be linked with the corresponding function call
// we, however, have to ignore expression-local writes!
if (localDefs.length > 0) {
environment = {
current: environment.current.removeAll(localDefs.filter(d => (0, assert_1.isNotUndefined)(d.name))),
level: environment.level
};
}
if (callDependencies === null) {
callDependencies = nextGraph.getVertex(functionCall)?.cds;
}
inputEnvironment = (0, overwrite_1.overwriteEnvironment)(inputEnvironment, environment, callDependencies);
}
}
}
return inputEnvironment;
}
/**
* Processes a list of expressions joining their dataflow graphs accordingly.
*/
function processExpressionList(name, args, rootId, data) {
const expressions = args.map(unpack_argument_1.unpackNonameArg);
(0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `[expr list] with ${expressions.length} expressions`);
let { environment } = data;
// used to detect if a "write" happens within the same expression list
const listEnvironments = new Set();
const remainingRead = new Map();
const nextGraph = new graph_1.DataflowGraph(data.completeAst.idMap);
let out = [];
const exitPoints = [];
const activeCdsAtStart = data.cds;
const invertExitCds = [];
const processedExpressions = [];
let defaultReturnExpr = undefined;
for (const expression of expressions) {
if (expression === undefined) {
processedExpressions.push(undefined);
continue;
}
// use the current environments for processing
data.environment = environment;
const processed = (0, processor_1.processDataflowFor)(expression, data);
processedExpressions.push(processed);
nextGraph.mergeWith(processed.graph);
defaultReturnExpr = processed;
// if the expression contained next or break anywhere before the next loop, the "overwrite" should be an "append", because we do not know if the rest is executed
// update the environments for the next iteration with the previous writes
if (exitPoints.length > 0) {
processed.out = (0, reference_to_maybe_1.makeAllMaybe)(processed.out, nextGraph, processed.environment, true, invertExitCds);
processed.in = (0, reference_to_maybe_1.makeAllMaybe)(processed.in, nextGraph, processed.environment, false, invertExitCds);
processed.unknownReferences = (0, reference_to_maybe_1.makeAllMaybe)(processed.unknownReferences, nextGraph, processed.environment, false);
}
out = out.concat(processed.out);
// all inputs that have not been written until now are read!
for (const ls of [processed.in, processed.unknownReferences]) {
for (const read of ls) {
linkReadNameToWriteIfPossible(read, environment, listEnvironments, remainingRead, nextGraph);
}
}
const calledEnvs = (0, linker_1.linkFunctionCalls)(nextGraph, data.completeAst.idMap, processed.graph);
for (const c of calledEnvs) {
if (c.propagateExitPoints.length > 0) {
for (const exit of c.propagateExitPoints) {
processed.exitPoints.push(exit);
}
}
}
(0, info_1.addNonDefaultExitPoints)(exitPoints, invertExitCds, activeCdsAtStart, processed.exitPoints);
environment = exitPoints.length > 0 ? (0, overwrite_1.overwriteEnvironment)(environment, processed.environment) : processed.environment;
// if the called function has global redefinitions, we have to keep them within our environment
environment = updateSideEffectsForCalledFunctions(calledEnvs, environment, nextGraph, processed.out);
for (const { nodeId } of processed.out) {
listEnvironments.add(nodeId);
}
/** if at least built-one of the exit points encountered happens unconditionally, we exit here (dead code)! */
if ((0, info_1.alwaysExits)(processed)) {
/* if there is an always-exit expression, there is no default return active anymore */
defaultReturnExpr = undefined;
break;
}
}
if (defaultReturnExpr) {
exitPoints.push(data.cds ? {
type: 0 /* ExitPointType.Default */,
nodeId: defaultReturnExpr.entryPoint,
cds: data.cds
} : {
type: 0 /* ExitPointType.Default */,
nodeId: defaultReturnExpr.entryPoint
});
}
const ingoing = remainingRead.values().toArray().flat();
const rootNode = data.completeAst.idMap.get(rootId);
const withGroup = rootNode?.grouping;
if (withGroup) {
ingoing.push({ nodeId: rootId, name: name.content, cds: data.cds, type: identifier_1.ReferenceType.Function });
(0, common_1.patchFunctionCall)({
nextGraph,
rootId,
name,
data,
argumentProcessResult: processedExpressions,
origin: built_in_proc_name_1.BuiltInProcName.ExpressionList
});
nextGraph.addEdge(rootId, node_id_1.NodeId.toBuiltIn('{'), edge_1.EdgeType.Reads | edge_1.EdgeType.Calls);
// process all exit points as potential returns:
for (const exit of exitPoints) {
if (exit.type === 1 /* ExitPointType.Return */ || exit.type === 0 /* ExitPointType.Default */) {
nextGraph.addEdge(rootId, exit.nodeId, edge_1.EdgeType.Returns);
}
}
}
const meId = withGroup ? rootId : (processedExpressions.find(assert_1.isNotUndefined)?.entryPoint ?? rootId);
return {
/* no active nodes remain, they are consumed within the remaining read collection */
unknownReferences: [],
in: ingoing,
out,
environment: environment,
graph: nextGraph,
/* if we have no group, we take the last evaluated expr */
entryPoint: meId,
exitPoints: exitPoints,
hooks: processedExpressions.flatMap(p => p?.hooks ?? []),
};
}
//# sourceMappingURL=built-in-expression-list.js.map