UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

259 lines 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.diffOfDataflowGraphs = diffOfDataflowGraphs; exports.equalFunctionArguments = equalFunctionArguments; 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 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"); const diff_graph_1 = require("../../util/diff-graph"); /** * Compare two dataflow graphs and return a report on the differences. * If you simply want to check whether they equal, use {@link GraphDifferenceReport#isEqual|`<result>.isEqual()`}. * * @see {@link diffOfControlFlowGraphs} - for control flow graphs */ function diffOfDataflowGraphs(left, right, config) { if (left.graph === right.graph) { return new diff_graph_1.GraphDifferenceReport(); } const ctx = (0, diff_graph_1.initDiffContext)(left, right, config); diffDataflowGraph(ctx); return ctx.report; } 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); } function equalFunctionArguments(fn, a, b) { const ctx = { report: new diff_graph_1.GraphDifferenceReport(), leftname: 'left', rightname: 'right', position: '', config: {} }; diffFunctionArguments(fn, a, b, ctx); return ctx.report.isEqual(); } 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 ((0, graph_1.isNamedArgument)(aArg) && (0, graph_1.isNamedArgument)(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.controlDependencies, bArg.controlDependencies, { ...ctx, position: `${ctx.position}In argument #${i} (of ${ctx.leftname}, unnamed) the control dependency differs: ${JSON.stringify(aArg.controlDependencies)} vs ${JSON.stringify(bArg.controlDependencies)}.` }); } } } function diffVertices(ctx) { // collect vertices from both sides const lVert = [...ctx.left.vertices(true)].map(([id, info]) => [id, info]); const rVert = [...ctx.right.vertices(true)].map(([id, info]) => [id, info]); 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 controlDependencies. ` }); 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 === 'function-definition') { if (rInfo.tag !== 'function-definition') { 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)) { 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. ` }); } (0, diff_1.setDifference)(lInfo.subflow.graph, rInfo.subflow.graph, { ...ctx, position: `${ctx.position}Vertex ${id} differs in subflow graph. ` }); } } } } function diffEdge(edge, otherEdge, ctx, id, target) { const edgeTypes = (0, edge_1.splitEdgeTypes)(edge.types); const otherEdgeTypes = (0, edge_1.splitEdgeTypes)(otherEdge.types); 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([...(0, edge_1.edgeTypesToNames)(edge.types)])} vs ${JSON.stringify([...(0, edge_1.edgeTypesToNames)(otherEdge.types)])}`, { tag: 'edge', from: id, to: target }); } } 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