Coverage Report

Created: 2022-08-24 06:38

/src/solidity/libyul/AsmPrinter.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
 * @author Christian <c@ethdev.com>
20
 * @date 2017
21
 * Converts a parsed assembly into its textual form.
22
 */
23
24
#include <libyul/AsmPrinter.h>
25
#include <libyul/AST.h>
26
#include <libyul/Exceptions.h>
27
#include <libyul/Dialect.h>
28
29
#include <libsolutil/CommonData.h>
30
#include <libsolutil/StringUtils.h>
31
32
#include <boost/algorithm/string.hpp>
33
#include <boost/algorithm/string/replace.hpp>
34
35
#include <range/v3/view/transform.hpp>
36
37
#include <memory>
38
#include <functional>
39
40
using namespace std;
41
using namespace solidity;
42
using namespace solidity::langutil;
43
using namespace solidity::util;
44
using namespace solidity::yul;
45
46
string AsmPrinter::operator()(Literal const& _literal)
47
0
{
48
0
  string const locationComment = formatDebugData(_literal);
49
50
0
  switch (_literal.kind)
51
0
  {
52
0
  case LiteralKind::Number:
53
0
    yulAssert(isValidDecimal(_literal.value.str()) || isValidHex(_literal.value.str()), "Invalid number literal");
54
0
    return locationComment + _literal.value.str() + appendTypeName(_literal.type);
55
0
  case LiteralKind::Boolean:
56
0
    yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "Invalid bool literal.");
57
0
    return locationComment + ((_literal.value == "true"_yulstring) ? "true" : "false") + appendTypeName(_literal.type, true);
58
0
  case LiteralKind::String:
59
0
    break;
60
0
  }
61
62
0
  return locationComment + escapeAndQuoteString(_literal.value.str()) + appendTypeName(_literal.type);
63
0
}
64
65
string AsmPrinter::operator()(Identifier const& _identifier)
66
0
{
67
0
  yulAssert(!_identifier.name.empty(), "Invalid identifier.");
68
0
  return formatDebugData(_identifier) + _identifier.name.str();
69
0
}
70
71
string AsmPrinter::operator()(ExpressionStatement const& _statement)
72
0
{
73
0
  string const locationComment = formatDebugData(_statement);
74
75
0
  return locationComment + std::visit(*this, _statement.expression);
76
0
}
77
78
string AsmPrinter::operator()(Assignment const& _assignment)
79
0
{
80
0
  string const locationComment = formatDebugData(_assignment);
81
82
0
  yulAssert(_assignment.variableNames.size() >= 1, "");
83
0
  string variables = (*this)(_assignment.variableNames.front());
84
0
  for (size_t i = 1; i < _assignment.variableNames.size(); ++i)
85
0
    variables += ", " + (*this)(_assignment.variableNames[i]);
86
87
0
  return locationComment + variables + " := " + std::visit(*this, *_assignment.value);
88
0
}
89
90
string AsmPrinter::operator()(VariableDeclaration const& _variableDeclaration)
91
0
{
92
0
  string out = formatDebugData(_variableDeclaration);
93
94
0
  out += "let ";
95
0
  out += boost::algorithm::join(
96
0
    _variableDeclaration.variables | ranges::views::transform(
97
0
      [this](TypedName argument) { return formatTypedName(argument); }
98
0
    ),
99
0
    ", "
100
0
  );
101
0
  if (_variableDeclaration.value)
102
0
  {
103
0
    out += " := ";
104
0
    out += std::visit(*this, *_variableDeclaration.value);
105
0
  }
106
0
  return out;
107
0
}
108
109
string AsmPrinter::operator()(FunctionDefinition const& _functionDefinition)
110
0
{
111
0
  yulAssert(!_functionDefinition.name.empty(), "Invalid function name.");
112
113
0
  string out = formatDebugData(_functionDefinition);
114
0
  out += "function " + _functionDefinition.name.str() + "(";
115
0
  out += boost::algorithm::join(
116
0
    _functionDefinition.parameters | ranges::views::transform(
117
0
      [this](TypedName argument) { return formatTypedName(argument); }
118
0
    ),
119
0
    ", "
120
0
  );
121
0
  out += ")";
122
0
  if (!_functionDefinition.returnVariables.empty())
123
0
  {
124
0
    out += " -> ";
125
0
    out += boost::algorithm::join(
126
0
      _functionDefinition.returnVariables | ranges::views::transform(
127
0
        [this](TypedName argument) { return formatTypedName(argument); }
128
0
      ),
129
0
      ", "
130
0
    );
131
0
  }
132
133
0
  return out + "\n" + (*this)(_functionDefinition.body);
134
0
}
135
136
string AsmPrinter::operator()(FunctionCall const& _functionCall)
137
0
{
138
0
  string const locationComment = formatDebugData(_functionCall);
139
0
  string const functionName = (*this)(_functionCall.functionName);
140
0
  return
141
0
    locationComment +
142
0
    functionName + "(" +
143
0
    boost::algorithm::join(
144
0
      _functionCall.arguments | ranges::views::transform([&](auto&& _node) { return std::visit(*this, _node); }),
145
0
      ", " ) +
146
0
    ")";
147
0
}
148
149
string AsmPrinter::operator()(If const& _if)
150
0
{
151
0
  yulAssert(_if.condition, "Invalid if condition.");
152
153
0
  string out = formatDebugData(_if);
154
0
  out += "if " + std::visit(*this, *_if.condition);
155
156
0
  string body = (*this)(_if.body);
157
0
  char delim = '\n';
158
0
  if (body.find('\n') == string::npos)
159
0
    delim = ' ';
160
161
0
  return out + delim + body;
162
0
}
163
164
string AsmPrinter::operator()(Switch const& _switch)
165
0
{
166
0
  yulAssert(_switch.expression, "Invalid expression pointer.");
167
168
0
  string out = formatDebugData(_switch);
169
0
  out += "switch " + std::visit(*this, *_switch.expression);
170
171
0
  for (auto const& _case: _switch.cases)
172
0
  {
173
0
    if (!_case.value)
174
0
      out += "\ndefault ";
175
0
    else
176
0
      out += "\ncase " + (*this)(*_case.value) + " ";
177
0
    out += (*this)(_case.body);
178
0
  }
179
0
  return out;
180
0
}
181
182
string AsmPrinter::operator()(ForLoop const& _forLoop)
183
0
{
184
0
  yulAssert(_forLoop.condition, "Invalid for loop condition.");
185
0
  string const locationComment = formatDebugData(_forLoop);
186
187
0
  string pre = (*this)(_forLoop.pre);
188
0
  string condition = std::visit(*this, *_forLoop.condition);
189
0
  string post = (*this)(_forLoop.post);
190
191
0
  char delim = '\n';
192
0
  if (
193
0
    pre.size() + condition.size() + post.size() < 60 &&
194
0
    pre.find('\n') == string::npos &&
195
0
    post.find('\n') == string::npos
196
0
  )
197
0
    delim = ' ';
198
0
  return
199
0
    locationComment +
200
0
    ("for " + move(pre) + delim + move(condition) + delim + move(post) + "\n") +
201
0
    (*this)(_forLoop.body);
202
0
}
203
204
string AsmPrinter::operator()(Break const& _break)
205
0
{
206
0
  return formatDebugData(_break) + "break";
207
0
}
208
209
string AsmPrinter::operator()(Continue const& _continue)
210
0
{
211
0
  return formatDebugData(_continue) + "continue";
212
0
}
213
214
// '_leave' and '__leave' is reserved in VisualStudio
215
string AsmPrinter::operator()(Leave const& leave_)
216
0
{
217
0
  return formatDebugData(leave_) + "leave";
218
0
}
219
220
string AsmPrinter::operator()(Block const& _block)
221
0
{
222
0
  string const locationComment = formatDebugData(_block);
223
224
0
  if (_block.statements.empty())
225
0
    return locationComment + "{ }";
226
0
  string body = boost::algorithm::join(
227
0
    _block.statements | ranges::views::transform([&](auto&& _node) { return std::visit(*this, _node); }),
228
0
    "\n"
229
0
  );
230
0
  if (body.size() < 30 && body.find('\n') == string::npos)
231
0
    return locationComment + "{ " + body + " }";
232
0
  else
233
0
  {
234
0
    boost::replace_all(body, "\n", "\n    ");
235
0
    return locationComment + "{\n    " + body + "\n}";
236
0
  }
237
0
}
238
239
string AsmPrinter::formatTypedName(TypedName _variable)
240
0
{
241
0
  yulAssert(!_variable.name.empty(), "Invalid variable name.");
242
0
  return formatDebugData(_variable) + _variable.name.str() + appendTypeName(_variable.type);
243
0
}
244
245
string AsmPrinter::appendTypeName(YulString _type, bool _isBoolLiteral) const
246
0
{
247
0
  if (m_dialect && !_type.empty())
248
0
  {
249
0
    if (!_isBoolLiteral && _type == m_dialect->defaultType)
250
0
      _type = {};
251
0
    else if (_isBoolLiteral && _type == m_dialect->boolType && !m_dialect->defaultType.empty())
252
      // Special case: If we have a bool type but empty default type, do not remove the type.
253
0
      _type = {};
254
0
  }
255
0
  if (_type.empty())
256
0
    return {};
257
0
  else
258
0
    return ":" + _type.str();
259
0
}
260
261
string AsmPrinter::formatSourceLocation(
262
  SourceLocation const& _location,
263
  map<string, unsigned> const& _nameToSourceIndex,
264
  DebugInfoSelection const& _debugInfoSelection,
265
  CharStreamProvider const* _soliditySourceProvider
266
)
267
0
{
268
0
  yulAssert(!_nameToSourceIndex.empty(), "");
269
0
  if (_debugInfoSelection.snippet)
270
0
    yulAssert(_debugInfoSelection.location, "@src tag must always contain the source location");
271
272
0
  if (_debugInfoSelection.none())
273
0
    return "";
274
275
0
  string sourceIndex = "-1";
276
0
  string solidityCodeSnippet = "";
277
0
  if (_location.sourceName)
278
0
  {
279
0
    sourceIndex = to_string(_nameToSourceIndex.at(*_location.sourceName));
280
281
0
    if (
282
0
      _debugInfoSelection.snippet &&
283
0
      _soliditySourceProvider &&
284
0
      !_soliditySourceProvider->charStream(*_location.sourceName).isImportedFromAST()
285
0
    )
286
0
    {
287
0
      solidityCodeSnippet = escapeAndQuoteString(
288
0
        _soliditySourceProvider->charStream(*_location.sourceName).singleLineSnippet(_location)
289
0
      );
290
291
      // On top of escaping quotes we also escape the slash inside any `*/` to guard against
292
      // it prematurely terminating multi-line comment blocks. We do not escape all slashes
293
      // because the ones without `*` are not dangerous and ignoring them reduces visual noise.
294
0
      boost::replace_all(solidityCodeSnippet, "*/", "*\\/");
295
0
    }
296
0
  }
297
298
0
  string sourceLocation =
299
0
    "@src " +
300
0
    sourceIndex +
301
0
    ":" +
302
0
    to_string(_location.start) +
303
0
    ":" +
304
0
    to_string(_location.end);
305
306
0
  return sourceLocation + (solidityCodeSnippet.empty() ? "" : "  ") + solidityCodeSnippet;
307
0
}
308
309
string AsmPrinter::formatDebugData(shared_ptr<DebugData const> const& _debugData, bool _statement)
310
0
{
311
0
  if (!_debugData || m_debugInfoSelection.none())
312
0
    return "";
313
314
0
  vector<string> items;
315
0
  if (auto id = _debugData->astID)
316
0
    if (m_debugInfoSelection.astID)
317
0
      items.emplace_back("@ast-id " + to_string(*id));
318
319
0
  if (
320
0
    m_lastLocation != _debugData->originLocation &&
321
0
    !m_nameToSourceIndex.empty()
322
0
  )
323
0
  {
324
0
    m_lastLocation = _debugData->originLocation;
325
326
0
    items.emplace_back(formatSourceLocation(
327
0
      _debugData->originLocation,
328
0
      m_nameToSourceIndex,
329
0
      m_debugInfoSelection,
330
0
      m_soliditySourceProvider
331
0
    ));
332
0
  }
333
334
0
  string commentBody = joinHumanReadable(items, " ");
335
0
  if (commentBody.empty())
336
0
    return "";
337
0
  else
338
0
    return
339
0
      _statement ?
340
0
      "/// " + commentBody + "\n" :
341
0
      "/** " + commentBody + " */ ";
342
0
}