UNPKG

@syntest/cfg

Version:
166 lines 8.17 kB
"use strict"; 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