@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
345 lines • 19.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.diffDataflowGraph = diffDataflowGraph;
exports.diffFunctionArguments = diffFunctionArguments;
exports.diffVertices = diffVertices;
exports.diffEdges = diffEdges;
const graph_1 = require("./graph");
const diff_1 = require("../../util/diff");
const json_1 = require("../../util/json");
const arrays_1 = require("../../util/collections/arrays");
const vertex_1 = require("./vertex");
const edge_1 = require("./edge");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const identifier_1 = require("../environments/identifier");
const diff_2 = require("../environments/diff");
const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const info_1 = require("../info");
/**
* This is the underlying function to calculate the difference based on a given context.
* Use {@link Dataflow.diff} to calculate the diff of two graphs.
*/
function diffDataflowGraph(ctx) {
diffRootVertices(ctx);
diffVertices(ctx);
diffOutgoingEdges(ctx);
}
function diffOutgoingEdges(ctx) {
const lEdges = new Map(ctx.left.edges());
const rEdges = new Map(ctx.right.edges());
if (lEdges.size < rEdges.size && !ctx.config.leftIsSubgraph || lEdges.size > rEdges.size && !ctx.config.rightIsSubgraph) {
ctx.report.addComment(`Detected different number of edges! ${ctx.leftname} has ${lEdges.size} (${JSON.stringify(lEdges, json_1.jsonReplacer)}). ${ctx.rightname} has ${rEdges.size} ${JSON.stringify(rEdges, json_1.jsonReplacer)}`);
}
for (const [id, edge] of lEdges) {
/* This has nothing to do with the subset relation as we verify this in the same graph.
* Yet we still do the check as a subgraph may not have to have all source vertices for edges.
*/
if (!ctx.left.hasVertex(id)) {
if (!ctx.config.leftIsSubgraph) {
ctx.report.addComment(`The source ${id} of edges ${JSON.stringify(edge, json_1.jsonReplacer)} is not present in ${ctx.leftname}. This means that the graph contains an edge but not the corresponding vertex.`);
continue;
}
}
diffEdges(ctx, id, edge, rEdges.get(id));
}
// just to make it both ways in case the length differs
for (const [id, edge] of rEdges) {
if (!ctx.right.hasVertex(id)) {
if (!ctx.config.rightIsSubgraph) {
ctx.report.addComment(`The source ${id} of edges ${JSON.stringify(edge, json_1.jsonReplacer)} is not present in ${ctx.rightname}. This means that the graph contains an edge but not the corresponding vertex.`);
continue;
}
}
if (!ctx.config.leftIsSubgraph && !lEdges.has(id)) {
diffEdges(ctx, id, undefined, edge);
}
/* otherwise, we already cover the edge above */
}
}
function diffRootVertices(ctx) {
(0, diff_1.setDifference)(ctx.left.rootIds(), ctx.right.rootIds(), { ...ctx, position: `${ctx.position}Root vertices differ in graphs. ` });
(0, diff_1.setDifference)(new Set([...ctx.left.unknownSideEffects].map(n => typeof n === 'object' ? n.id : n)), new Set([...ctx.right.unknownSideEffects].map(n => typeof n === 'object' ? n.id : n)), { ...ctx, position: `${ctx.position}Unknown side effects differ in graphs. ` });
}
function diffFunctionArgumentsReferences(fn, a, b, ctx) {
if (a === '<value>' || b === '<value>') {
if (a !== b) {
ctx.report.addComment(`${ctx.position}${ctx.leftname}: ${JSON.stringify(a, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(b, json_1.jsonReplacer)}`, { tag: 'vertex', id: fn });
}
return;
}
(0, diff_2.diffIdentifierReferences)(a, b, ctx);
}
/**
* Compares two function argument lists and reports differences.
*/
function diffFunctionArguments(fn, a, b, ctx) {
if (a === false || b === false) {
if (a !== b) {
ctx.report.addComment(`${ctx.position}${ctx.leftname}: ${JSON.stringify(a, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(b, json_1.jsonReplacer)}`, { tag: 'vertex', id: fn });
}
return;
}
else if (a.length !== b.length) {
ctx.report.addComment(`${ctx.position}Differs in number of arguments. ${ctx.leftname}: ${JSON.stringify(a, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(b, json_1.jsonReplacer)}`, { tag: 'vertex', id: fn });
return;
}
for (let i = 0; i < a.length; ++i) {
const aArg = a[i];
const bArg = b[i];
if (aArg === r_function_call_1.EmptyArgument || bArg === r_function_call_1.EmptyArgument) {
if (aArg !== bArg) {
ctx.report.addComment(`${ctx.position}In argument #${i} (of ${ctx.leftname}, empty) the argument differs: ${JSON.stringify(aArg)} vs ${JSON.stringify(bArg)}.`);
}
}
else if (graph_1.FunctionArgument.isNamed(aArg) && graph_1.FunctionArgument.isNamed(bArg)) {
// must have the same name
if (aArg.name !== bArg.name) {
ctx.report.addComment(`${ctx.position}In argument #${i} (of ${ctx.leftname}, named) the name differs: ${aArg.name} vs ${bArg.name}.`);
continue;
}
diffFunctionArgumentsReferences(fn, aArg, bArg, {
...ctx,
position: `${ctx.position} In argument #${i} (of ${ctx.leftname}, named). `
});
}
else {
if (aArg.name !== bArg.name) {
ctx.report.addComment(`${ctx.position}In argument #${i} (of ${ctx.leftname}, unnamed) the name differs: ${aArg.name} vs ${bArg.name}.`);
}
(0, info_1.diffControlDependencies)(aArg.cds, bArg.cds, { ...ctx, position: `${ctx.position}In argument #${i} (of ${ctx.leftname}, unnamed) the control dependency differs: ${JSON.stringify(aArg.cds)} vs ${JSON.stringify(bArg.cds)}.` });
}
}
}
/**
* Compares the vertices of two dataflow graphs and reports differences.
*/
function diffVertices(ctx) {
// collect vertices from both sides
const lVert = ctx.left.vertices(true).map(([id, info]) => [id, info]).toArray();
const rVert = ctx.right.vertices(true).map(([id, info]) => [id, info]).toArray();
if (lVert.length < rVert.length && !ctx.config.leftIsSubgraph
|| lVert.length > rVert.length && !ctx.config.rightIsSubgraph) {
ctx.report.addComment(`Detected different number of vertices! ${ctx.leftname} has ${lVert.length}, ${ctx.rightname} has ${rVert.length}`);
}
for (const [id, lInfo] of lVert) {
const rInfoMay = ctx.right.get(id);
if (rInfoMay === undefined) {
if (!ctx.config.rightIsSubgraph) {
ctx.report.addComment(`Vertex ${id} is not present in ${ctx.rightname}`, { tag: 'vertex', id });
}
continue;
}
const [rInfo] = rInfoMay;
if (lInfo.tag !== rInfo.tag) {
ctx.report.addComment(`Vertex ${id} differs in tags. ${ctx.leftname}: ${lInfo.tag} vs. ${ctx.rightname}: ${rInfo.tag}`, { tag: 'vertex', id });
}
/* as names are optional, we have to recover the other name if at least one of them is no longer available */
if (lInfo.name !== undefined || rInfo.name !== undefined) {
const lname = lInfo.name ?? (0, node_id_1.recoverName)(id, ctx.left.idMap) ?? '??';
const rname = rInfo.name ?? (0, node_id_1.recoverName)(id, ctx.right.idMap) ?? '??';
if (lname !== rname) {
ctx.report.addComment(`Vertex ${id} differs in names. ${ctx.leftname}: ${String(lname)} vs ${ctx.rightname}: ${String(rname)}`, {
tag: 'vertex',
id
});
}
}
(0, info_1.diffControlDependencies)(lInfo.cds, rInfo.cds, { ...ctx, position: `Vertex ${id} differs in cds. ` });
if (lInfo.origin !== undefined || rInfo.origin !== undefined) {
// compare arrays
const equalArrays = lInfo.origin && rInfo.origin && (0, arrays_1.arrayEqual)(lInfo.origin, rInfo.origin);
if (!equalArrays) {
ctx.report.addComment(`Vertex ${id} differs in origin. ${ctx.leftname}: ${JSON.stringify(lInfo.origin, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(rInfo.origin, json_1.jsonReplacer)}`, { tag: 'vertex', id });
}
}
if (lInfo.link !== undefined || rInfo.link !== undefined) {
const equal = lInfo.link && rInfo.link && (0, arrays_1.arrayEqual)(lInfo.link.origin, rInfo.link.origin);
if (!equal) {
ctx.report.addComment(`Vertex ${id} differs in link. ${ctx.leftname}: ${JSON.stringify(lInfo.link, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(rInfo.link, json_1.jsonReplacer)}`, { tag: 'vertex', id });
}
}
if ((lInfo.environment === undefined && rInfo.environment !== undefined && !ctx.config.leftIsSubgraph)
|| (lInfo.environment !== undefined && rInfo.environment === undefined && !ctx.config.rightIsSubgraph)) {
/* only diff them if specified at all */
(0, diff_2.diffEnvironmentInformation)(lInfo.environment, rInfo.environment, {
...ctx,
position: `${ctx.position}Vertex ${id} differs in environment. `
});
}
if (lInfo.tag === vertex_1.VertexType.FunctionCall) {
if (rInfo.tag !== vertex_1.VertexType.FunctionCall) {
ctx.report.addComment(`Vertex ${id} differs in tags. ${ctx.leftname}: ${lInfo.tag} vs. ${ctx.rightname}: ${rInfo.tag}`);
}
else {
if (lInfo.onlyBuiltin !== rInfo.onlyBuiltin) {
ctx.report.addComment(`Vertex ${id} differs in onlyBuiltin. ${ctx.leftname}: ${lInfo.onlyBuiltin} vs ${ctx.rightname}: ${rInfo.onlyBuiltin}`, { tag: 'vertex', id });
}
if ((lInfo.args.length === 0 && rInfo.args.length !== 0 && !ctx.config.leftIsSubgraph)
|| (lInfo.args.length !== 0 && rInfo.args.length === 0 && !ctx.config.rightIsSubgraph)) {
diffFunctionArguments(lInfo.id, lInfo.args, rInfo.args, {
...ctx,
position: `${ctx.position}Vertex ${id} (function call) differs in arguments. `
});
}
}
}
if (lInfo.tag === vertex_1.VertexType.FunctionDefinition) {
if (rInfo.tag !== vertex_1.VertexType.FunctionDefinition) {
ctx.report.addComment(`Vertex ${id} differs in tags. ${ctx.leftname}: ${lInfo.tag} vs. ${ctx.rightname}: ${rInfo.tag}`, { tag: 'vertex', id });
}
else {
if (!(0, arrays_1.arrayEqual)(lInfo.exitPoints, rInfo.exitPoints, (a, b) => {
if (a.type !== b.type || a.nodeId !== b.nodeId) {
return false;
}
(0, info_1.diffControlDependencies)(a.cds, b.cds, { ...ctx, position: '' });
return true;
})) {
ctx.report.addComment(`Vertex ${id} differs in exit points. ${ctx.leftname}: ${JSON.stringify(lInfo.exitPoints, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(rInfo.exitPoints, json_1.jsonReplacer)}`, { tag: 'vertex', id });
}
if ((lInfo.subflow.environment === undefined && rInfo.subflow.environment !== undefined && !ctx.config.leftIsSubgraph)
|| (lInfo.subflow.environment !== undefined && rInfo.subflow.environment === undefined && !ctx.config.rightIsSubgraph)) {
(0, diff_2.diffEnvironmentInformation)(lInfo.subflow.environment, rInfo.subflow.environment, {
...ctx,
position: `${ctx.position}Vertex ${id} (function definition) differs in subflow environments. `
});
}
diffInReadParameters(lInfo.params, rInfo.params, {
...ctx,
position: `${ctx.position}Vertex ${id} differs in subflow in-read-parameters. `
});
(0, diff_1.setDifference)(new Set(rInfo.mode ?? []), new Set(lInfo.mode ?? []), {
...ctx,
position: `${ctx.position}Vertex ${id} differs in function definition mode. `
});
(0, diff_1.setDifference)(lInfo.subflow.graph, rInfo.subflow.graph, {
...ctx,
position: `${ctx.position}Vertex ${id} differs in subflow graph. `
});
diffReferenceLists(id, lInfo.subflow.in, rInfo.subflow.in, {
...ctx,
position: `${ctx.position}Vertex ${id} differs in subflow *in* refs. `
});
diffReferenceLists(id, lInfo.subflow.out, rInfo.subflow.out, {
...ctx,
position: `${ctx.position}Vertex ${id} differs in subflow *out* refs. `
});
diffReferenceLists(id, lInfo.subflow.unknownReferences, rInfo.subflow.unknownReferences, {
...ctx,
position: `${ctx.position}Vertex ${id} differs in subflow *unknown* refs. `
});
diffHooks(lInfo.subflow.hooks, rInfo.subflow.hooks, ctx, id);
}
}
}
}
function diffInReadParameters(l, r, ctx) {
const lKeys = new Set(Object.keys(l));
const rKeys = new Set(Object.keys(r));
(0, diff_1.setDifference)(lKeys, rKeys, { ...ctx, position: `${ctx.position}In-read-parameters differ in graphs. ` });
for (const k of lKeys) {
const lVal = l[k];
const rVal = r[k];
if (rVal === undefined) {
if (!ctx.config.rightIsSubgraph) {
ctx.report.addComment(`In-read-parameter ${k} is not present in ${ctx.rightname}`, { tag: 'vertex', id: k });
}
continue;
}
if (lVal !== rVal) {
ctx.report.addComment(`In-read-parameter ${k} differs. ${ctx.leftname}: ${lVal} vs ${ctx.rightname}: ${rVal}`, { tag: 'vertex', id: k });
}
}
for (const k of rKeys) {
if (!lKeys.has(k)) {
if (!ctx.config.leftIsSubgraph) {
ctx.report.addComment(`In-read-parameter ${k} is not present in ${ctx.leftname}`, { tag: 'vertex', id: k });
}
}
}
}
function diffReferenceLists(fn, a, b, ctx) {
// sort by id
if (a === undefined || b === undefined) {
if (a !== b) {
ctx.report.addComment(`${ctx.position}${ctx.leftname}: ${JSON.stringify(a, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(b, json_1.jsonReplacer)}`, { tag: 'vertex', id: fn });
}
return;
}
if (a.length !== b.length) {
ctx.report.addComment(`${ctx.position}Differs in number of references.\n - ${ctx.leftname}: ${JSON.stringify(a, json_1.jsonReplacer)} vs\n - ${ctx.rightname}: ${JSON.stringify(b, json_1.jsonReplacer)}`, { tag: 'vertex', id: fn });
return;
}
const aSorted = [...a].sort((x, y) => x.nodeId.toString().localeCompare(y.nodeId.toString()));
const bSorted = [...b].sort((x, y) => x.nodeId.toString().localeCompare(y.nodeId.toString()));
for (let i = 0; i < aSorted.length; ++i) {
const inam = aSorted[i].name;
(0, diff_2.diffIdentifierReferences)(aSorted[i], bSorted[i], {
...ctx,
position: `${ctx.position}In reference #${i} ("${inam ? identifier_1.Identifier.toString(inam) : '?'}", id: ${aSorted[i].nodeId ?? '?'}) `,
});
}
}
function diffHooks(left, right, ctx, id) {
// compare length
if (left.length !== right.length) {
ctx.report.addComment(`Differs in number of hooks. ${ctx.leftname}: ${JSON.stringify(left, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(right, json_1.jsonReplacer)}`, { tag: 'vertex', id });
return;
}
// compare each hook
for (let i = 0; i < left.length; ++i) {
const lHook = left[i];
const rHook = right[i];
if (lHook.type !== rHook.type) {
ctx.report.addComment(`Hook #${i} differs in type. ${ctx.leftname}: ${JSON.stringify(lHook.type)} vs ${ctx.rightname}: ${JSON.stringify(rHook.type)}`, { tag: 'vertex', id });
}
if (lHook.id !== rHook.id) {
ctx.report.addComment(`Hook #${i} differs in id. ${ctx.leftname}: ${lHook.id} vs ${ctx.rightname}: ${rHook.id}`, { tag: 'vertex', id });
}
if (lHook.add !== rHook.add) {
ctx.report.addComment(`Hook #${i} differs in add. ${ctx.leftname}: ${lHook.add} vs ${ctx.rightname}: ${rHook.add}`, { tag: 'vertex', id });
}
if (lHook.after !== rHook.after) {
ctx.report.addComment(`Hook #${i} differs in after. ${ctx.leftname}: ${lHook.after} vs ${ctx.rightname}: ${rHook.after}`, { tag: 'vertex', id });
}
(0, info_1.diffControlDependencies)(lHook.cds, rHook.cds, { ...ctx, position: `Hook #${i} differs in control dependencies. ` });
}
}
function diffEdge(edge, otherEdge, ctx, id, target) {
const edgeTypes = edge_1.DfEdge.splitTypes(edge);
const otherEdgeTypes = edge_1.DfEdge.splitTypes(otherEdge);
if ((edgeTypes.length < otherEdgeTypes.length && !ctx.config.leftIsSubgraph) || (edgeTypes.length > otherEdgeTypes.length && !ctx.config.rightIsSubgraph)) {
ctx.report.addComment(`Target of ${id}->${target} in ${ctx.leftname} differs in number of edge types: ${JSON.stringify([...edgeTypes])} vs ${JSON.stringify([...otherEdgeTypes])}`, { tag: 'edge', from: id, to: target });
}
if (edge.types !== otherEdge.types) {
ctx.report.addComment(`Target of ${id}->${target} in ${ctx.leftname} differs in edge types: ${JSON.stringify([...edge_1.DfEdge.typesToNames(edge)])} vs ${JSON.stringify([...edge_1.DfEdge.typesToNames(otherEdge)])}`, { tag: 'edge', from: id, to: target });
}
}
/**
* Compares two sets of outgoing edges and reports differences.
*/
function diffEdges(ctx, id, lEdges, rEdges) {
if (lEdges === undefined || rEdges === undefined) {
if ((lEdges === undefined && !ctx.config.leftIsSubgraph)
|| (rEdges === undefined && !ctx.config.rightIsSubgraph)) {
ctx.report.addComment(`Vertex ${id} has undefined outgoing edges. ${ctx.leftname}: ${JSON.stringify(lEdges, json_1.jsonReplacer)} vs ${ctx.rightname}: ${JSON.stringify(rEdges, json_1.jsonReplacer)}`, { tag: 'vertex', id });
}
return;
}
if (lEdges.size < rEdges.size && !ctx.config.leftIsSubgraph
|| lEdges.size > rEdges.size && !ctx.config.rightIsSubgraph) {
ctx.report.addComment(`Vertex ${id} differs in number of outgoing edges. ${ctx.leftname}: [${[...lEdges.keys()].join(',')}] vs ${ctx.rightname}: [${[...rEdges.keys()].join(',')}] `, { tag: 'vertex', id });
}
// order independent compare
for (const [target, edge] of lEdges) {
const otherEdge = rEdges.get(target);
if (otherEdge === undefined) {
if (!ctx.config.rightIsSubgraph) {
ctx.report.addComment(`Target of ${id}->${target} in ${ctx.leftname} is not present in ${ctx.rightname}`, { tag: 'edge', from: id, to: target });
}
continue;
}
diffEdge(edge, otherEdge, ctx, id, target);
}
}
//# sourceMappingURL=diff-dataflow-graph.js.map