|
| 1 | +/* |
| 2 | + This file is part of solidity. |
| 3 | +
|
| 4 | + solidity is free software: you can redistribute it and/or modify |
| 5 | + it under the terms of the GNU General Public License as published by |
| 6 | + the Free Software Foundation, either version 3 of the License, or |
| 7 | + (at your option) any later version. |
| 8 | +
|
| 9 | + solidity is distributed in the hope that it will be useful, |
| 10 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | + GNU General Public License for more details. |
| 13 | +
|
| 14 | + You should have received a copy of the GNU General Public License |
| 15 | + along with solidity. If not, see <http://www.gnu.org/licenses/>. |
| 16 | +*/ |
| 17 | +// SPDX-License-Identifier: GPL-3.0 |
| 18 | + |
| 19 | + |
| 20 | +#pragma once |
| 21 | + |
| 22 | +#include <libyul/backends/evm/ssa/SSACFG.h> |
| 23 | + |
| 24 | +#include <range/v3/view/concat.hpp> |
| 25 | + |
| 26 | +#include <cstdint> |
| 27 | +#include <optional> |
| 28 | +#include <vector> |
| 29 | + |
| 30 | +namespace solidity::yul::ssa |
| 31 | +{ |
| 32 | +/// Detect bridges according to Algorithm 1 of https://arxiv.org/pdf/2108.07346.pdf. |
| 33 | +/// |
| 34 | +/// A bridge in an undirected graph is an edge whose removal increases the number of connected components. In control |
| 35 | +/// flow analysis, bridge vertices are critical articulation points where removing the vertex would disconnect |
| 36 | +/// reachable code from unreachable code. This implementation adapts the bridge-finding algorithm to directed control |
| 37 | +/// flow graphs by treating them as undirected, then validating edge directionality to identify vertices that gate |
| 38 | +/// access to subsequent blocks. |
| 39 | +/// |
| 40 | +/// We use bridges to determine blocks in which it is fine to introduce junk slots on the stack at any point in time. |
| 41 | +class BridgeFinder |
| 42 | +{ |
| 43 | +public: |
| 44 | + explicit BridgeFinder(SSACFG const& _cfg): |
| 45 | + m_cfg(_cfg), |
| 46 | + m_bridgeVertex(_cfg.numBlocks()), |
| 47 | + m_visited(_cfg.numBlocks()), |
| 48 | + m_disc(_cfg.numBlocks()), |
| 49 | + m_low(_cfg.numBlocks()) |
| 50 | + { |
| 51 | + size_t time = 0; |
| 52 | + dfs(time, _cfg.entry, std::nullopt); |
| 53 | + } |
| 54 | + |
| 55 | + bool bridgeVertex(SSACFG::BlockId const& _blockId) const |
| 56 | + { |
| 57 | + return m_bridgeVertex[_blockId.value]; |
| 58 | + } |
| 59 | + |
| 60 | +private: |
| 61 | + void dfs(size_t& _time, SSACFG::BlockId const& _vertex, std::optional<SSACFG::BlockId> const& _parent) |
| 62 | + { |
| 63 | + m_visited[_vertex.value] = true; |
| 64 | + m_disc[_vertex.value] = _time; |
| 65 | + m_low[_vertex.value] = _time; |
| 66 | + ++_time; |
| 67 | + |
| 68 | + auto const& currentBlock = m_cfg.block(_vertex); |
| 69 | + currentBlock.forEachExit([&](SSACFG::BlockId const& _exit) |
| 70 | + { |
| 71 | + processNeighbor(_exit, _time, _vertex, currentBlock.entries, _parent); |
| 72 | + }); |
| 73 | + |
| 74 | + for (SSACFG::BlockId const neighbor: currentBlock.entries) |
| 75 | + processNeighbor(neighbor, _time, _vertex, currentBlock.entries, _parent); |
| 76 | + } |
| 77 | + |
| 78 | + void processNeighbor( |
| 79 | + SSACFG::BlockId const& _neighbor, |
| 80 | + size_t& _time, |
| 81 | + SSACFG::BlockId const& _vertex, |
| 82 | + std::set<SSACFG::BlockId> const& _vertexEntries, |
| 83 | + std::optional<SSACFG::BlockId> const& _parent |
| 84 | + ) |
| 85 | + { |
| 86 | + if (_neighbor == _parent) |
| 87 | + return; |
| 88 | + |
| 89 | + if (!m_visited[_neighbor.value]) |
| 90 | + { |
| 91 | + dfs(_time, _neighbor, _vertex); |
| 92 | + m_low[_vertex.value] = std::min(m_low[_vertex.value], m_low[_neighbor.value]); |
| 93 | + if (m_low[_neighbor.value] > m_disc[_vertex.value]) |
| 94 | + { |
| 95 | + // vertex <-> neighbor is a bridge in the undirected graph |
| 96 | + bool const edgeNeighborToVertex = _vertexEntries.contains(_neighbor); |
| 97 | + bool const edgeVertexToNeighbor = m_cfg.block(_neighbor).entries.contains(_vertex); |
| 98 | + |
| 99 | + // special case: if it's the entry itself, we mark it as bridge vertex (provided correct orientation), |
| 100 | + // so that functions which do nothing but revert have their whole tree marked as such (sans loops) |
| 101 | + if (!_parent) |
| 102 | + m_bridgeVertex[_vertex.value] = edgeVertexToNeighbor; |
| 103 | + // Since we are not really undirected, check if we don't have a cycle (u -> v and v -> u) and see, |
| 104 | + // which edge really exists here. |
| 105 | + // Then record the targeted vertex as bridge vertex. |
| 106 | + if (edgeVertexToNeighbor && !edgeNeighborToVertex) |
| 107 | + // bridge vertex -> neighbor |
| 108 | + m_bridgeVertex[_neighbor.value] = true; |
| 109 | + else if (edgeNeighborToVertex && !edgeVertexToNeighbor) |
| 110 | + // bridge neighbor -> vertex |
| 111 | + m_bridgeVertex[_vertex.value] = true; |
| 112 | + } |
| 113 | + } |
| 114 | + else |
| 115 | + m_low[_vertex.value] = std::min(m_low[_vertex.value], m_disc[_neighbor.value]); |
| 116 | + } |
| 117 | + |
| 118 | + SSACFG const& m_cfg; |
| 119 | + // determines whether a vertex is a bridge vertex. optimizing for performance over space with u8. |
| 120 | + std::vector<std::uint8_t> m_bridgeVertex; |
| 121 | + // determines whether a vertex is visited. optimizing for performance over space with u8. |
| 122 | + std::vector<std::uint8_t> m_visited; |
| 123 | + // vertex discovery time |
| 124 | + std::vector<std::size_t> m_disc; |
| 125 | + // minimum discovery time of subtree - if m_low[child] > m_low[parent], we have a bridge |
| 126 | + std::vector<std::size_t> m_low; |
| 127 | +}; |
| 128 | + |
| 129 | +} |
0 commit comments