Coverage Report

Created: 2026-06-30 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/backends/evm/ssa/SSACFG.cpp
Line
Count
Source
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
#include <libyul/backends/evm/ssa/SSACFG.h>
20
21
#include <libyul/backends/evm/ssa/ControlFlowGraphs.h>
22
#include <libyul/backends/evm/ssa/JunkAdmittingBlocksFinder.h>
23
#include <libyul/backends/evm/ssa/LivenessAnalysis.h>
24
#include <libyul/backends/evm/ssa/io/DotExporterBase.h>
25
26
#include <libsolutil/StringUtils.h>
27
28
#include <fmt/ranges.h>
29
30
#include <range/v3/view/iota.hpp>
31
#include <range/v3/view/transform.hpp>
32
#include <range/v3/view/zip.hpp>
33
34
using namespace solidity;
35
using namespace solidity::util;
36
using namespace solidity::yul;
37
using namespace solidity::yul::ssa;
38
39
namespace
40
{
41
42
/// Build a human-readable Phi/Upsilon annotation for a phi value.
43
/// Shows which upsilons feed it, listed per predecessor block.
44
std::string formatPhi(SSACFG const& _cfg, InstId _phiId)
45
0
{
46
  // Collect all upsilons targeting _phiId from the whole CFG.
47
0
  std::vector<std::string> formattedUpsilons;
48
0
  for (BlockId const blockId: _cfg.liveBlocks())
49
0
    _cfg.forEachUpsilon(_cfg.block(blockId), [&](InstId const instId, SSACFG::Inst const& inst) {
50
0
      if (_cfg.upsilonPhi(instId) == _phiId)
51
0
        formattedUpsilons.push_back(
52
0
          fmt::format("Block {} => {}", blockId.value, inst.inputs.at(0).str(_cfg))
53
0
        );
54
0
    });
55
0
  if (!formattedUpsilons.empty())
56
0
    return fmt::format("φ(\\l\\\n\t{}\\l\\\n)", fmt::join(formattedUpsilons, ",\\l\\\n\t"));
57
0
  return "φ()";
58
0
}
59
60
class SSACFGDotExporter: public io::DotExporterBase
61
{
62
public:
63
  SSACFGDotExporter(SSACFG const& _cfg, size_t _functionIndex, LivenessAnalysis const* _liveness, ControlFlowGraphs const* _controlFlow):
64
0
    DotExporterBase(_cfg, _functionIndex),
65
0
    m_liveness(_liveness),
66
0
    m_controlFlow(_controlFlow)
67
0
  {
68
0
    if (_liveness)
69
0
      m_junkAdmittingBlocks = std::make_unique<JunkAdmittingBlocksFinder>(_cfg, _liveness->topologicalSort());
70
0
  }
71
72
protected:
73
  void writeBlockLabel(std::ostream& _out, BlockId _blockId) override
74
0
  {
75
0
    auto const& block = m_cfg.block(_blockId);
76
0
    auto const valueToString = [&](InstId const& valueId) { return valueId.str(m_cfg); };
77
78
0
    if (m_liveness)
79
0
    {
80
0
      _out << fmt::format(
81
0
        "\\\nBlock {}; ({}, max {})\\n",
82
0
        _blockId.value,
83
0
        m_liveness->topologicalSort().preOrderIndexOf(_blockId.value),
84
0
        m_liveness->topologicalSort().maxSubtreePreOrderIndexOf(_blockId.value)
85
0
      );
86
0
      _out << fmt::format(
87
0
        "LiveIn: {}\\l\\\n",
88
0
        fmt::join(m_liveness->liveIn(_blockId) | ranges::views::transform([&](auto const& liveIn) { return valueToString(liveIn.first) + fmt::format("[{}]", liveIn.second); }), ", ")
89
0
      );
90
0
      _out << fmt::format(
91
0
        "LiveOut: {}\\l\\n",
92
0
        fmt::join(m_liveness->liveOut(_blockId) | ranges::views::transform([&](auto const& liveOut) { return valueToString(liveOut.first) + fmt::format("[{}]", liveOut.second); }), ", ")
93
0
      );
94
0
      auto const usedVariables = m_liveness->used(_blockId);
95
0
      _out << fmt::format(
96
0
        "Used: {}\\l\\n",
97
0
        fmt::join(usedVariables | ranges::views::transform([&](auto const& used) { return valueToString(used.first) + fmt::format("[{}]", used.second); }), ", ")
98
0
      );
99
0
    }
100
0
    else
101
0
      _out << fmt::format("\\\nBlock {}\\n", _blockId.value);
102
103
    // Phis first, then BuiltinCall / Call in program order. Upsilons are rendered
104
    // under successor phis (via formatPhi) and skipped here. Trailing Projections of
105
    // multi-return calls are rendered as standalone lines following their producer.
106
0
    m_cfg.forEachPhi(block, [&](InstId const instId, SSACFG::Inst const&) {
107
0
      _out << fmt::format("phi{} := {}\\l\\\n", instId.value, formatPhi(m_cfg, instId));
108
0
    });
109
0
    m_cfg.forEachOperation(block, [&](InstId const instId, SSACFG::Inst const& inst) {
110
0
      std::string label;
111
0
      switch (inst.opcode)
112
0
      {
113
0
      case InstOpcode::Call:
114
0
      {
115
0
        auto const graphID = m_cfg.callPayload(instId).graphID;
116
0
        label = m_controlFlow ? m_controlFlow->functionGraph(graphID)->name : fmt::format("func{}", graphID);
117
0
        break;
118
0
      }
119
0
      case InstOpcode::BuiltinCall:
120
0
        label = m_cfg.evmDialect.builtin(m_cfg.builtinPayload(instId).builtin).name;
121
0
        break;
122
0
      case InstOpcode::MemoryGuard:
123
0
        if (m_controlFlow && m_controlFlow->memoryGuard)
124
0
          label = fmt::format("memoryguard<{}>", toCompactHexWithPrefix(*m_controlFlow->memoryGuard));
125
0
        else
126
0
          label = "memoryguard";
127
0
        break;
128
0
      default:
129
0
        yulAssert(false);
130
0
      }
131
0
      if (m_cfg.numReturnsOf(instId) >= 1)
132
0
        _out << fmt::format("{} := ", valueToString(instId));
133
0
      _out << fmt::format(
134
0
        "{}({})\\l\\\n",
135
0
        escapeLabel(label),
136
0
        fmt::join(inst.inputs | ranges::views::transform(valueToString), ", ")
137
0
      );
138
0
      for (InstId const projId: m_cfg.projectionsOf(instId))
139
0
        _out << fmt::format(
140
0
          "{} := {}.proj({})\\l\\\n",
141
0
          valueToString(projId),
142
0
          valueToString(instId),
143
0
          static_cast<unsigned>(m_cfg.projectionIndex(projId))
144
0
        );
145
0
    });
146
0
  }
147
148
  std::vector<std::pair<std::string, std::string>> blockNodeAttributes(BlockId _blockId) override
149
0
  {
150
0
    if (m_junkAdmittingBlocks && m_junkAdmittingBlocks->allowsAdditionOfJunk(_blockId))
151
0
      return {{"fillcolor", "\"#FF746C\""}, {"style", "filled"}};
152
0
    return {};
153
0
  }
154
155
  EdgeStyle edgeStyle(BlockId _source, BlockId _target) override
156
0
  {
157
0
    if (m_liveness && m_liveness->topologicalSort().backEdge(_source, _target))
158
0
      return EdgeStyle::Dashed;
159
0
    return EdgeStyle::Solid;
160
0
  }
161
162
private:
163
  LivenessAnalysis const* m_liveness;
164
  ControlFlowGraphs const* m_controlFlow;
165
  std::unique_ptr<JunkAdmittingBlocksFinder> m_junkAdmittingBlocks;
166
};
167
168
}
169
170
std::string SSACFG::toDot(
171
  bool _includeDiGraphDefinition,
172
  std::optional<size_t> _functionIndex,
173
  LivenessAnalysis const* _liveness,
174
  ControlFlowGraphs const* _controlFlow
175
) const
176
0
{
177
0
  SSACFGDotExporter exporter(*this, _functionIndex.value_or(isMainGraph() ? 0 : 1), _liveness, _controlFlow);
178
0
  if (!isMainGraph())
179
0
    return exporter.exportFunction(*this, _includeDiGraphDefinition);
180
0
  else
181
0
    return exporter.exportBlocks(entry, _includeDiGraphDefinition);
182
0
}