@syntest/cfg
Version:
A Control Flow Graph package
166 lines • 8.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.contractControlFlowProgram = exports.edgeContraction = void 0;
/*
* Copyright 2020-2023 Delft University of Technology and SynTest contributors
*
* This file is part of SynTest Framework - SynTest Core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const logging_1 = require("@syntest/logging");
const diagnostics_1 = require("../diagnostics");
const ContractedControlFlowGraph_1 = require("../graph/ContractedControlFlowGraph");
const ControlFlowGraph_1 = require("../graph/ControlFlowGraph");
const Edge_1 = require("../graph/Edge");
const Node_1 = require("../graph/Node");
const NodeType_1 = require("../graph/NodeType");
/**
* Edge contraction algorithm.
*
* This algorithm contracts every edge whose source has a single exit and whose destination has a single entry.
*
* https://en.wikipedia.org/wiki/Control-flow_graph
* @param controlFlowGraph the control flow graph to contract
* @returns the contracted control flow graph
*/
function edgeContraction(controlFlowGraph) {
const original = controlFlowGraph;
const nodeMapping = new Map();
let changed = false;
// Perform edge contraction until no more changes are made
do {
changed = false;
if (controlFlowGraph.nodes.size === 2) {
// Only entry and exit nodes left, so we can stop
break;
}
bfs(controlFlowGraph, (edge) => {
return (controlFlowGraph.getOutgoingEdges(edge.source).length === 1 &&
controlFlowGraph.getIncomingEdges(edge.target).length === 1 &&
edge.source !== controlFlowGraph.entry.id &&
edge.source !== controlFlowGraph.successExit.id &&
edge.source !== controlFlowGraph.errorExit.id &&
edge.target !== controlFlowGraph.entry.id &&
edge.target !== controlFlowGraph.successExit.id &&
edge.target !== controlFlowGraph.errorExit.id);
}, (edge) => {
controlFlowGraph = mergeNodes(controlFlowGraph, edge.source, edge.target);
if (nodeMapping.has(edge.source)) {
nodeMapping.get(edge.source).push(edge.target);
}
else {
nodeMapping.set(edge.source, [edge.source, edge.target]);
}
changed = true;
});
} while (changed);
// safety check
for (const edge of controlFlowGraph.edges) {
const outgoingFromSource = controlFlowGraph.getOutgoingEdges(edge.source);
const incomingToTarget = controlFlowGraph.getIncomingEdges(edge.target);
if (outgoingFromSource.length > 1 && incomingToTarget.length > 1) {
(0, logging_1.getLogger)("edgeContration").warn((0, diagnostics_1.shouldNeverHappen)(`Missing placeholder node: \n${edge.source}\n${edge.target}`));
// throw new Error(shouldNeverHappen(`Missing placeholder node: \n${edge.source}\n${edge.target}`))
}
}
return new ContractedControlFlowGraph_1.ContractedControlFlowGraph(controlFlowGraph.entry, controlFlowGraph.successExit, controlFlowGraph.errorExit, controlFlowGraph.nodes, controlFlowGraph.edges, original, nodeMapping);
}
exports.edgeContraction = edgeContraction;
// side effects
function contractControlFlowProgram(program) {
program.graph = edgeContraction(program.graph);
for (const f of program.functions) {
f.graph = edgeContraction(f.graph);
}
return program;
}
exports.contractControlFlowProgram = contractControlFlowProgram;
function bfs(controlFlowGraph, condition, callback) {
/**
* Perform BFS to find a node with only one incoming and one outgoing edge.
*/
const queue = [];
const visited = new Set();
queue.push(...controlFlowGraph.getOutgoingEdges(controlFlowGraph.entry.id));
while (queue.length > 0) {
const edge = queue.shift();
if (visited.has(edge.id)) {
continue;
}
visited.add(edge.id);
if (condition(edge)) {
callback(edge);
break;
}
queue.push(...controlFlowGraph.getOutgoingEdges(edge.target));
}
}
function beforeGuards(controlFlowGraph, source, target) {
if (controlFlowGraph.getOutgoingEdges(source).length !== 1) {
throw new Error((0, diagnostics_1.tooManyOutgoing)(source));
}
if (controlFlowGraph.getIncomingEdges(target).length !== 1) {
throw new Error((0, diagnostics_1.tooManyIncoming)(target));
}
if (controlFlowGraph.getOutgoingEdges(source)[0].target !== target) {
throw new Error((0, diagnostics_1.notDirectlyConnected)(source, target));
}
const isEntry = source === controlFlowGraph.entry.id;
const isSuccessExit = target === controlFlowGraph.successExit.id;
const isErrorExit = target === controlFlowGraph.errorExit.id;
if (isEntry && (isSuccessExit || isErrorExit)) {
throw new Error((0, diagnostics_1.cannotMergeEntryAndExit)());
}
}
function afterGuards(newNodes, newEdges, controlFlowGraph, source, target) {
if (newNodes.size !== controlFlowGraph.nodes.size - 1) {
throw new Error((0, diagnostics_1.exactlyOneNodeShouldBeRemoved)(source, target, newNodes.size - controlFlowGraph.nodes.size));
}
if (newEdges.length !== controlFlowGraph.edges.length - 1) {
throw new Error((0, diagnostics_1.exactlyOneEdgeShouldBeRemoved)(source, target, newNodes.size - controlFlowGraph.nodes.size));
}
}
function mergeNodes(controlFlowGraph, source, target) {
beforeGuards(controlFlowGraph, source, target);
const sourceNode = controlFlowGraph.getNodeById(source);
const targetNode = controlFlowGraph.getNodeById(target);
const mergedNode = new Node_1.Node(source, // We use the id of node1 because the first node always contains the result of a control node (e.g. if, while, etc.) this is also where the instrumentation places the branch coverage
NodeType_1.NodeType.NORMAL, sourceNode.label + "-" + targetNode.label, [...sourceNode.statements, ...targetNode.statements], {
...sourceNode.metadata,
...targetNode.metadata,
});
const filteredNodes = [...controlFlowGraph.nodes.values()].filter((node) => node.id !== source && node.id !== target);
const newNodesArray = [mergedNode, ...filteredNodes];
const newNodes = new Map(newNodesArray.map((node) => [node.id, node]));
const removedEdges = controlFlowGraph.edges.filter((edge) => edge.source === source && edge.target === target);
if (removedEdges.length !== 1) {
throw new Error((0, diagnostics_1.exactlyOneEdgeShouldBeRemoved)(source, target, removedEdges.length));
}
const newEdges = controlFlowGraph.edges
.filter((edge) => edge.id !== removedEdges[0].id)
.map((edge) => {
if (edge.source === target || edge.source === source) {
// second case should not exist as there is only one outgoing edge
return new Edge_1.Edge(edge.id, edge.type, edge.label, mergedNode.id, edge.target, edge.description);
}
if (edge.target === source || edge.target === target) {
// second case should not exist as there is only one incoming edge
return new Edge_1.Edge(edge.id, edge.type, edge.label, edge.source, mergedNode.id, edge.description);
}
return edge;
});
afterGuards(newNodes, newEdges, controlFlowGraph, source, target);
return new ControlFlowGraph_1.ControlFlowGraph(controlFlowGraph.entry, controlFlowGraph.successExit, controlFlowGraph.errorExit, newNodes, newEdges);
}
//# sourceMappingURL=edgeContraction.js.map