@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
181 lines • 10.1 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 environment_1 = require("../../../../../environments/environment");
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 built_in_1 = require("../../../../../environments/built-in");
const overwrite_1 = require("../../../../../environments/overwrite");
const logger_1 = require("../../../../../logger");
const log_1 = require("../../../../../../util/log");
const dotDotDotAccess = /^\.\.\d+$/;
function linkReadNameToWriteIfPossible(read, environments, listEnvironments, remainingRead, nextGraph) {
const readName = read.name && dotDotDotAccess.test(read.name) ? '...' : 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.controlDependencies))) {
const has = remainingRead.get(readName);
if (has) {
if (!has?.some(h => h.nodeId === read.nodeId && h.name === read.name && h.controlDependencies === read.controlDependencies)) {
has.push(read);
}
}
else {
remainingRead.set(readName, [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;
}
for (const target of probableTarget) {
// we can stick with maybe even if readId.attribute is always
nextGraph.addEdge(read, target, edge_1.EdgeType.Reads);
if ((read.type === identifier_1.ReferenceType.Function || read.type === identifier_1.ReferenceType.BuiltInFunction) && ((0, built_in_1.isBuiltIn)(target.definedAt))) {
nextGraph.addEdge(read, target, edge_1.EdgeType.Calls);
}
}
}
function processNextExpression(currentElement, environment, listEnvironments, remainingRead, nextGraph) {
// all inputs that have not been written until now are read!
for (const read of [...currentElement.in, ...currentElement.unknownReferences]) {
linkReadNameToWriteIfPossible(read, environment, listEnvironments, remainingRead, nextGraph);
}
}
function updateSideEffectsForCalledFunctions(calledEnvs, inputEnvironment, nextGraph) {
for (const { functionCall, called } of calledEnvs) {
const callDependencies = nextGraph.getVertex(functionCall, true)?.cds;
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.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 !== undefined && current.id !== environment_1.BuiltInEnvironment.id) {
for (const definitions of current.memory.values()) {
for (const def of definitions) {
if (!(0, built_in_1.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
inputEnvironment = (0, overwrite_1.overwriteEnvironment)(inputEnvironment, environment, callDependencies);
}
}
}
return inputEnvironment;
}
function processExpressionList(name, args, rootId, data) {
const expressions = args.map(e => (0, unpack_argument_1.unpackArgument)(e));
(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 = [];
let expressionCounter = 0;
const processedExpressions = [];
let defaultReturnExpr = undefined;
for (const expression of expressions) {
(0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `processing expression ${++expressionCounter} of ${expressions.length}`);
if (expression === undefined) {
processedExpressions.push(undefined);
continue;
}
// use the current environments for processing
data = { ...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, environment_1.makeAllMaybe)(processed.out, nextGraph, processed.environment, true);
processed.in = (0, environment_1.makeAllMaybe)(processed.in, nextGraph, processed.environment, false);
processed.unknownReferences = (0, environment_1.makeAllMaybe)(processed.unknownReferences, nextGraph, processed.environment, false);
}
(0, info_1.addNonDefaultExitPoints)(exitPoints, processed.exitPoints);
out = out.concat(processed.out);
(0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `expression ${expressionCounter} of ${expressions.length} has ${processed.unknownReferences.length} unknown nodes`);
processNextExpression(processed, environment, listEnvironments, remainingRead, nextGraph);
environment = exitPoints.length > 0 ? (0, overwrite_1.overwriteEnvironment)(environment, processed.environment) : processed.environment;
const calledEnvs = (0, linker_1.linkFunctionCalls)(nextGraph, data.completeAst.idMap, processed.graph);
// if the called function has global redefinitions, we have to keep them within our environment
environment = updateSideEffectsForCalledFunctions(calledEnvs, environment, nextGraph);
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;
}
}
logger_1.dataflowLogger.trace(`expression list exits with ${remainingRead.size} remaining read names`);
if (defaultReturnExpr) {
exitPoints.push({
type: 0 /* ExitPointType.Default */,
nodeId: defaultReturnExpr.entryPoint,
controlDependencies: data.controlDependencies
});
}
const ingoing = [...remainingRead.values()].flat();
const rootNode = data.completeAst.idMap.get(rootId);
const withGroup = rootNode?.grouping;
if (withGroup) {
ingoing.push({ nodeId: rootId, name: name.content, controlDependencies: data.controlDependencies, type: identifier_1.ReferenceType.Function });
(0, common_1.patchFunctionCall)({
nextGraph,
rootId,
name,
data,
argumentProcessResult: processedExpressions,
origin: 'builtin:expression-list'
});
nextGraph.addEdge(rootId, (0, built_in_1.builtInId)('{'), 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: withGroup ? [{ nodeId: rootId, type: 0 /* ExitPointType.Default */, controlDependencies: data.controlDependencies }]
: exitPoints
};
}
//# sourceMappingURL=built-in-expression-list.js.map