Coverage Report

Created: 2025-06-24 07:59

/src/solidity/liblangutil/SemVerHandler.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 <chris@ethereum.org>
20
 * @date 2016
21
 * Utilities to handle semantic versioning.
22
 */
23
24
#include <liblangutil/SemVerHandler.h>
25
26
#include <liblangutil/Exceptions.h>
27
28
#include <functional>
29
#include <limits>
30
#include <fmt/format.h>
31
32
using namespace std::string_literals;
33
using namespace solidity;
34
using namespace solidity::langutil;
35
using namespace solidity::util;
36
37
SemVerMatchExpressionParser::SemVerMatchExpressionParser(std::vector<Token> _tokens, std::vector<std::string> _literals):
38
  m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
39
3.65k
{
40
3.65k
  solAssert(m_tokens.size() == m_literals.size(), "");
41
3.65k
}
42
43
SemVerVersion::SemVerVersion(std::string const& _versionString)
44
75.0k
{
45
75.0k
  auto i = _versionString.begin();
46
75.0k
  auto end = _versionString.end();
47
48
300k
  for (unsigned level = 0; level < 3; ++level)
49
225k
  {
50
225k
    unsigned v = 0;
51
507k
    for (; i != end && '0' <= *i && *i <= '9'; ++i)
52
282k
      v = v * 10 + unsigned(*i - '0');
53
225k
    numbers[level] = v;
54
225k
    if (level < 2)
55
150k
    {
56
150k
      if (i == end || *i != '.')
57
0
        solThrow(SemVerError, "Invalid versionString: "s + _versionString);
58
150k
      else
59
150k
        ++i;
60
150k
    }
61
225k
  }
62
75.0k
  if (i != end && *i == '-')
63
57.0k
  {
64
57.0k
    auto prereleaseStart = ++i;
65
1.02M
    while (i != end && *i != '+') ++i;
66
57.0k
    prerelease = std::string(prereleaseStart, i);
67
57.0k
  }
68
75.0k
  if (i != end && *i == '+')
69
57.0k
  {
70
57.0k
    auto buildStart = ++i;
71
1.59M
    while (i != end) ++i;
72
57.0k
    build = std::string(buildStart, i);
73
57.0k
  }
74
75.0k
  if (i != end)
75
0
    solThrow(SemVerError, "Invalid versionString "s + _versionString);
76
75.0k
}
77
78
bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _version) const
79
10.2k
{
80
10.2k
  if (prefix == Token::BitNot)
81
17
  {
82
17
    MatchComponent comp = *this;
83
84
17
    comp.prefix = Token::GreaterThanOrEqual;
85
17
    if (!comp.matches(_version))
86
4
      return false;
87
88
13
    if (levelsPresent >= 2)
89
1
      comp.levelsPresent = 2;
90
12
    else
91
12
      comp.levelsPresent = 1;
92
13
    comp.prefix = Token::LessThanOrEqual;
93
13
    return comp.matches(_version);
94
17
  }
95
10.2k
  else if (prefix == Token::BitXor)
96
146
  {
97
146
    MatchComponent comp = *this;
98
99
146
    comp.prefix = Token::GreaterThanOrEqual;
100
146
    if (!comp.matches(_version))
101
13
      return false;
102
103
133
    if (comp.version.numbers[0] == 0 && comp.levelsPresent != 1)
104
42
      comp.levelsPresent = 2;
105
91
    else
106
91
      comp.levelsPresent = 1;
107
133
    comp.prefix = Token::LessThanOrEqual;
108
133
    return comp.matches(_version);
109
146
  }
110
10.0k
  else
111
10.0k
  {
112
10.0k
    int cmp = 0;
113
10.0k
    bool didCompare = false;
114
23.3k
    for (unsigned i = 0; i < levelsPresent && cmp == 0; i++)
115
13.2k
      if (version.numbers[i] != std::numeric_limits<unsigned>::max())
116
6.53k
      {
117
6.53k
        didCompare = true;
118
6.53k
        cmp = static_cast<int>(_version.numbers[i]) - static_cast<int>(version.numbers[i]);
119
6.53k
      }
120
121
10.0k
    if (cmp == 0 && !_version.prerelease.empty() && didCompare)
122
74
      cmp = -1;
123
124
10.0k
    switch (prefix)
125
10.0k
    {
126
6.50k
    case Token::Assign:
127
6.50k
      return cmp == 0;
128
239
    case Token::LessThan:
129
239
      return cmp < 0;
130
170
    case Token::LessThanOrEqual:
131
170
      return cmp <= 0;
132
117
    case Token::GreaterThan:
133
117
      return cmp > 0;
134
3.04k
    case Token::GreaterThanOrEqual:
135
3.04k
      return cmp >= 0;
136
0
    default:
137
0
      solAssert(false, "Invalid SemVer expression");
138
10.0k
    }
139
0
    return false;
140
10.0k
  }
141
10.2k
}
142
143
bool SemVerMatchExpression::Conjunction::matches(SemVerVersion const& _version) const
144
3.57k
{
145
3.57k
  for (auto const& component: components)
146
9.92k
    if (!component.matches(_version))
147
245
      return false;
148
3.32k
  return true;
149
3.57k
}
150
151
bool SemVerMatchExpression::matches(SemVerVersion const& _version) const
152
3.44k
{
153
3.44k
  if (!isValid())
154
0
    return false;
155
3.44k
  for (auto const& range: m_disjunction)
156
3.57k
    if (range.matches(_version))
157
3.32k
      return true;
158
115
  return false;
159
3.44k
}
160
161
SemVerMatchExpression SemVerMatchExpressionParser::parse()
162
3.65k
{
163
3.65k
  reset();
164
165
3.65k
  if (m_tokens.empty())
166
3
    solThrow(SemVerError, "Empty version pragma.");
167
168
3.64k
  try
169
3.64k
  {
170
4.86k
    while (true)
171
4.66k
    {
172
4.66k
      parseMatchExpression();
173
4.66k
      if (m_pos >= m_tokens.size())
174
3.44k
        break;
175
1.21k
      if (currentToken() != Token::Or)
176
2
      {
177
2
        solThrow(
178
2
          SemVerError,
179
2
          "You can only combine version ranges using the || operator."
180
2
        );
181
2
      }
182
1.21k
      nextToken();
183
1.21k
    }
184
3.64k
  }
185
3.64k
  catch (SemVerError const& e)
186
3.64k
  {
187
207
    reset();
188
207
    throw e;
189
207
  }
190
191
3.44k
  return m_expression;
192
3.64k
}
193
194
195
void SemVerMatchExpressionParser::reset()
196
3.85k
{
197
3.85k
  m_expression = SemVerMatchExpression();
198
3.85k
  m_pos = 0;
199
3.85k
  m_posInside = 0;
200
3.85k
}
201
202
void SemVerMatchExpressionParser::parseMatchExpression()
203
4.66k
{
204
  // component - component (range)
205
  // or component component* (conjunction)
206
207
4.66k
  SemVerMatchExpression::Conjunction range;
208
4.66k
  range.components.push_back(parseMatchComponent());
209
4.66k
  if (currentToken() == Token::Sub)
210
42
  {
211
42
    range.components[0].prefix = Token::GreaterThanOrEqual;
212
42
    nextToken();
213
42
    range.components.push_back(parseMatchComponent());
214
42
    range.components[1].prefix = Token::LessThanOrEqual;
215
42
  }
216
4.61k
  else
217
17.9k
    while (currentToken() != Token::Or && currentToken() != Token::Illegal)
218
13.3k
      range.components.push_back(parseMatchComponent());
219
4.66k
  m_expression.m_disjunction.push_back(range);
220
4.66k
}
221
222
SemVerMatchExpression::MatchComponent SemVerMatchExpressionParser::parseMatchComponent()
223
18.0k
{
224
18.0k
  SemVerMatchExpression::MatchComponent component;
225
18.0k
  Token token = currentToken();
226
227
18.0k
  switch (token)
228
18.0k
  {
229
153
  case Token::BitXor:
230
197
  case Token::BitNot:
231
459
  case Token::LessThan:
232
467
  case Token::LessThanOrEqual:
233
708
  case Token::GreaterThan:
234
3.64k
  case Token::GreaterThanOrEqual:
235
3.77k
  case Token::Assign:
236
3.77k
    component.prefix = token;
237
3.77k
    nextToken();
238
3.77k
    break;
239
14.2k
  default:
240
14.2k
    component.prefix = Token::Assign;
241
18.0k
  }
242
243
18.0k
  component.levelsPresent = 0;
244
23.0k
  while (component.levelsPresent < 3)
245
22.9k
  {
246
22.9k
    component.version.numbers[component.levelsPresent] = parseVersionPart();
247
22.9k
    component.levelsPresent++;
248
22.9k
    if (currentChar() == '.')
249
5.02k
      nextChar();
250
17.9k
    else
251
17.9k
      break;
252
22.9k
  }
253
  // TODO we do not support pre and build version qualifiers for now in match expressions
254
  // (but we do support them in the actual versions)
255
18.0k
  return component;
256
18.0k
}
257
258
unsigned SemVerMatchExpressionParser::parseVersionPart()
259
22.9k
{
260
22.9k
  auto startPos = m_pos;
261
22.9k
  char c = currentChar();
262
22.9k
  nextChar();
263
22.9k
  if (c == 'x' || c == 'X' || c == '*')
264
13.7k
    return unsigned(-1);
265
9.26k
  else if (c == '0')
266
7.31k
    return 0;
267
1.94k
  else if ('1' <= c && c <= '9')
268
1.75k
  {
269
1.75k
    auto v = static_cast<unsigned>(c - '0');
270
    // If we skip to the next token, the current number is terminated.
271
3.51k
    while (m_pos == startPos && '0' <= currentChar() && currentChar() <= '9')
272
1.76k
    {
273
1.76k
      c = currentChar();
274
1.76k
      if (v * 10 < v || v * 10 + static_cast<unsigned>(c - '0') < v * 10)
275
18
        solThrow(SemVerError, "Integer too large to be used in a version number.");
276
1.75k
      v = v * 10 + static_cast<unsigned>(c - '0');
277
1.75k
      nextChar();
278
1.75k
    }
279
1.74k
    return v;
280
1.75k
  }
281
187
  else if (c == char(-1))
282
12
    solThrow(SemVerError, "Expected version number but reached end of pragma.");
283
175
  else
284
175
    solThrow(
285
22.9k
      SemVerError, fmt::format(
286
22.9k
        "Expected the start of a version number but instead found character '{}'. "
287
22.9k
        "Version number is invalid or the pragma is not terminated with a semicolon.",
288
22.9k
        c
289
22.9k
      )
290
22.9k
    );
291
22.9k
}
292
293
char SemVerMatchExpressionParser::currentChar() const
294
81.6k
{
295
81.6k
  if (m_pos >= m_literals.size())
296
6.89k
    return char(-1);
297
74.7k
  if (m_posInside >= m_literals[m_pos].size())
298
20
    return char(-1);
299
74.7k
  return m_literals[m_pos][m_posInside];
300
74.7k
}
301
302
char SemVerMatchExpressionParser::nextChar()
303
29.7k
{
304
29.7k
  if (m_pos < m_literals.size())
305
29.7k
  {
306
29.7k
    if (m_posInside + 1 >= m_literals[m_pos].size())
307
12.8k
      nextToken();
308
16.9k
    else
309
16.9k
      ++m_posInside;
310
29.7k
  }
311
29.7k
  return currentChar();
312
29.7k
}
313
314
Token SemVerMatchExpressionParser::currentToken() const
315
58.0k
{
316
58.0k
  if (m_pos < m_tokens.size())
317
48.5k
    return m_tokens[m_pos];
318
9.54k
  else
319
9.54k
    return Token::Illegal;
320
58.0k
}
321
322
void SemVerMatchExpressionParser::nextToken()
323
17.6k
{
324
17.6k
  ++m_pos;
325
17.6k
  m_posInside = 0;
326
17.6k
}