Coverage Report

Created: 2025-09-04 07:34

/src/solidity/libyul/YulControlFlowGraphExporter.cpp
Line
Count
Source (jump to first uncovered line)
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/Utilities.h>
20
#include <libyul/YulControlFlowGraphExporter.h>
21
22
#include <libsolutil/Algorithms.h>
23
#include <libsolutil/Numeric.h>
24
25
#include <range/v3/view/enumerate.hpp>
26
#include <range/v3/view/map.hpp>
27
#include <range/v3/view/transform.hpp>
28
29
using namespace solidity;
30
using namespace solidity::langutil;
31
using namespace solidity::util;
32
using namespace solidity::yul;
33
34
YulControlFlowGraphExporter::YulControlFlowGraphExporter(ControlFlow const& _controlFlow, ControlFlowLiveness const* _liveness): m_controlFlow(_controlFlow), m_liveness(_liveness)
35
0
{
36
0
}
37
38
std::string YulControlFlowGraphExporter::varToString(SSACFG const& _cfg, SSACFG::ValueId _var)
39
0
{
40
0
  if (_var.value == std::numeric_limits<size_t>::max())
41
0
    return std::string("INVALID");
42
0
  auto const& info = _cfg.valueInfo(_var);
43
0
  return std::visit(
44
0
    util::GenericVisitor{
45
0
      [&](SSACFG::UnreachableValue const&) -> std::string {
46
0
        return "[unreachable]";
47
0
      },
48
0
      [&](SSACFG::LiteralValue const& _literal) {
49
0
        return toCompactHexWithPrefix(_literal.value);
50
0
      },
51
0
      [&](auto const&) {
52
0
        return "v" + std::to_string(_var.value);
53
0
      }
Unexecuted instantiation: YulControlFlowGraphExporter.cpp:auto YulControlFlowGraphExporter::varToString(solidity::yul::SSACFG const&, solidity::yul::SSACFG::ValueId)::$_3::operator()<solidity::yul::SSACFG::VariableValue>(solidity::yul::SSACFG::VariableValue const&) const
Unexecuted instantiation: YulControlFlowGraphExporter.cpp:auto YulControlFlowGraphExporter::varToString(solidity::yul::SSACFG const&, solidity::yul::SSACFG::ValueId)::$_3::operator()<solidity::yul::SSACFG::PhiValue>(solidity::yul::SSACFG::PhiValue const&) const
54
0
    },
55
0
    info
56
0
  );
57
0
}
58
59
Json YulControlFlowGraphExporter::run()
60
0
{
61
0
  if (m_liveness)
62
0
    yulAssert(&m_liveness->controlFlow.get() == &m_controlFlow);
63
64
0
  Json yulObjectJson = Json::object();
65
0
  yulObjectJson["blocks"] = exportBlock(*m_controlFlow.mainGraph, SSACFG::BlockId{0}, m_liveness ? m_liveness->mainLiveness.get() : nullptr);
66
67
0
  Json functionsJson = Json::object();
68
0
  size_t index = 0;
69
0
  for (auto const& [function, functionGraph]: m_controlFlow.functionGraphMapping)
70
0
    functionsJson[function->name.str()] = exportFunction(*functionGraph, m_liveness ? m_liveness->functionLiveness[index++].get() : nullptr);
71
0
  yulObjectJson["functions"] = functionsJson;
72
73
0
  return yulObjectJson;
74
0
}
75
76
Json YulControlFlowGraphExporter::exportFunction(SSACFG const& _cfg, SSACFGLiveness const* _liveness)
77
0
{
78
0
  Json functionJson = Json::object();
79
0
  functionJson["type"] = "Function";
80
0
  functionJson["entry"] = "Block" + std::to_string(_cfg.entry.value);
81
0
  static auto constexpr argsTransform = [](auto const& _arg) { return fmt::format("v{}", std::get<1>(_arg).value); };
82
0
  functionJson["arguments"] = _cfg.arguments | ranges::views::transform(argsTransform) | ranges::to<std::vector>;
83
0
  functionJson["numReturns"] = _cfg.returns.size();
84
0
  functionJson["blocks"] = exportBlock(_cfg, _cfg.entry, _liveness);
85
0
  return functionJson;
86
0
}
87
88
Json YulControlFlowGraphExporter::exportBlock(SSACFG const& _cfg, SSACFG::BlockId _entryId, SSACFGLiveness const* _liveness)
89
0
{
90
0
  Json blocksJson = Json::array();
91
0
  util::BreadthFirstSearch<SSACFG::BlockId> bfs{{{_entryId}}};
92
0
  bfs.run([&](SSACFG::BlockId _blockId, auto _addChild) {
93
0
    auto const& block = _cfg.block(_blockId);
94
    // Convert current block to JSON
95
0
    Json blockJson = toJson(_cfg, _blockId, _liveness);
96
97
0
    Json exitBlockJson = Json::object();
98
0
    std::visit(util::GenericVisitor{
99
0
      [&](SSACFG::BasicBlock::MainExit const&) {
100
0
        exitBlockJson["type"] = "MainExit";
101
0
      },
102
0
      [&](SSACFG::BasicBlock::Jump const& _jump)
103
0
      {
104
0
        exitBlockJson["targets"] = { "Block" + std::to_string(_jump.target.value) };
105
0
        exitBlockJson["type"] = "Jump";
106
0
        _addChild(_jump.target);
107
0
      },
108
0
      [&](SSACFG::BasicBlock::ConditionalJump const& _conditionalJump)
109
0
      {
110
0
        exitBlockJson["targets"] = { "Block" + std::to_string(_conditionalJump.zero.value), "Block" + std::to_string(_conditionalJump.nonZero.value) };
111
0
        exitBlockJson["cond"] = varToString(_cfg, _conditionalJump.condition);
112
0
        exitBlockJson["type"] = "ConditionalJump";
113
114
0
        _addChild(_conditionalJump.zero);
115
0
        _addChild(_conditionalJump.nonZero);
116
0
      },
117
0
      [&](SSACFG::BasicBlock::FunctionReturn const& _return) {
118
0
        exitBlockJson["returnValues"] = toJson(_cfg, _return.returnValues);
119
0
        exitBlockJson["type"] = "FunctionReturn";
120
0
      },
121
0
      [&](SSACFG::BasicBlock::Terminated const&) {
122
0
        exitBlockJson["type"] = "Terminated";
123
0
      },
124
0
      [&](SSACFG::BasicBlock::JumpTable const&) {
125
0
        yulAssert(false);
126
0
      }
127
0
    }, block.exit);
128
0
    blockJson["exit"] = exitBlockJson;
129
0
    blocksJson.emplace_back(blockJson);
130
0
  });
131
132
0
  return blocksJson;
133
0
}
134
135
Json YulControlFlowGraphExporter::toJson(SSACFG const& _cfg, SSACFG::BlockId _blockId, SSACFGLiveness const* _liveness)
136
0
{
137
0
  auto const valueToString = [&](SSACFG::ValueId const& valueId) { return varToString(_cfg, valueId); };
138
139
0
  Json blockJson = Json::object();
140
0
  auto const& block = _cfg.block(_blockId);
141
142
0
  blockJson["id"] = "Block" + std::to_string(_blockId.value);
143
0
  if (_liveness)
144
0
  {
145
0
    Json livenessJson = Json::object();
146
0
    livenessJson["in"] = _liveness->liveIn(_blockId)
147
0
      | ranges::views::transform(valueToString)
148
0
      | ranges::to<Json::array_t>();
149
0
    livenessJson["out"] = _liveness->liveOut(_blockId)
150
0
      | ranges::views::transform(valueToString)
151
0
      | ranges::to<Json::array_t>();
152
0
    blockJson["liveness"] = livenessJson;
153
0
  }
154
0
  blockJson["instructions"] = Json::array();
155
0
  if (!block.phis.empty())
156
0
  {
157
0
    blockJson["entries"] = block.entries
158
0
      | ranges::views::transform([](auto const& entry) { return "Block" + std::to_string(entry.value); })
159
0
      | ranges::to<Json::array_t>();
160
0
    for (auto const& phi: block.phis)
161
0
    {
162
0
      auto* phiInfo = std::get_if<SSACFG::PhiValue>(&_cfg.valueInfo(phi));
163
0
      yulAssert(phiInfo);
164
0
      Json phiJson = Json::object();
165
0
      phiJson["op"] = "PhiFunction";
166
0
      phiJson["in"] = toJson(_cfg, phiInfo->arguments);
167
0
      phiJson["out"] = toJson(_cfg, std::vector<SSACFG::ValueId>{phi});
168
0
      blockJson["instructions"].push_back(phiJson);
169
0
    }
170
0
  }
171
0
  for (auto const& operation: block.operations)
172
0
    blockJson["instructions"].push_back(toJson(blockJson, _cfg, operation));
173
174
0
  return blockJson;
175
0
}
176
177
Json YulControlFlowGraphExporter::toJson(Json& _ret, SSACFG const& _cfg, SSACFG::Operation const& _operation)
178
0
{
179
0
  Json opJson = Json::object();
180
0
  std::visit(GenericVisitor{
181
0
    [&](SSACFG::Call const& _call)
182
0
    {
183
0
      _ret["type"] = "FunctionCall";
184
0
      opJson["op"] = _call.function.get().name.str();
185
0
    },
186
0
    [&](SSACFG::LiteralAssignment const&)
187
0
    {
188
0
      yulAssert(_operation.inputs.size() == 1);
189
0
      yulAssert(_cfg.isLiteralValue(_operation.inputs.back()));
190
0
      opJson["op"] = "LiteralAssignment";
191
0
    },
192
0
    [&](SSACFG::BuiltinCall const& _call)
193
0
    {
194
0
      _ret["type"] = "BuiltinCall";
195
0
      Json builtinArgsJson = Json::array();
196
0
      auto const& builtin = _call.builtin.get();
197
0
      if (!builtin.literalArguments.empty())
198
0
      {
199
0
        auto const& functionCallArgs = _call.call.get().arguments;
200
0
        for (size_t i = 0; i < builtin.literalArguments.size(); ++i)
201
0
        {
202
0
          std::optional<LiteralKind> const& argument = builtin.literalArguments[i];
203
0
          if (argument.has_value() && i < functionCallArgs.size())
204
0
          {
205
            // The function call argument at index i must be a literal if builtin.literalArguments[i] is not nullopt
206
0
            yulAssert(std::holds_alternative<Literal>(functionCallArgs[i]));
207
0
            builtinArgsJson.push_back(formatLiteral(std::get<Literal>(functionCallArgs[i])));
208
0
          }
209
0
        }
210
0
      }
211
212
0
      if (!builtinArgsJson.empty())
213
0
        opJson["literalArgs"] = builtinArgsJson;
214
215
0
      opJson["op"] = _call.builtin.get().name;
216
0
    },
217
0
  }, _operation.kind);
218
219
0
  opJson["in"] = toJson(_cfg, _operation.inputs);
220
0
  opJson["out"] = toJson(_cfg, _operation.outputs);
221
222
0
  return opJson;
223
0
}
224
225
Json YulControlFlowGraphExporter::toJson(SSACFG const& _cfg, std::vector<SSACFG::ValueId> const& _values)
226
0
{
227
0
  Json ret = Json::array();
228
0
  for (auto const& value: _values)
229
0
    ret.push_back(varToString(_cfg, value));
230
0
  return ret;
231
0
}