/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 | } |