/src/solidity/libyul/AsmAnalysis.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 | | * Analyzer part of inline assembly. |
20 | | */ |
21 | | |
22 | | #include <libyul/AsmAnalysis.h> |
23 | | |
24 | | #include <libyul/AST.h> |
25 | | #include <libyul/AsmAnalysisInfo.h> |
26 | | #include <libyul/Utilities.h> |
27 | | #include <libyul/Exceptions.h> |
28 | | #include <libyul/Object.h> |
29 | | #include <libyul/Scope.h> |
30 | | #include <libyul/ScopeFiller.h> |
31 | | |
32 | | #include <liblangutil/ErrorReporter.h> |
33 | | |
34 | | #include <libsolutil/CommonData.h> |
35 | | #include <libsolutil/StringUtils.h> |
36 | | #include <libsolutil/Visitor.h> |
37 | | |
38 | | #include <boost/algorithm/string.hpp> |
39 | | |
40 | | #include <fmt/format.h> |
41 | | |
42 | | #include <memory> |
43 | | #include <functional> |
44 | | |
45 | | using namespace std; |
46 | | using namespace solidity; |
47 | | using namespace solidity::yul; |
48 | | using namespace solidity::util; |
49 | | using namespace solidity::langutil; |
50 | | |
51 | | namespace |
52 | | { |
53 | | inline string to_string(LiteralKind _kind) |
54 | 0 | { |
55 | 0 | switch (_kind) |
56 | 0 | { |
57 | 0 | case LiteralKind::Number: return "number"; |
58 | 0 | case LiteralKind::Boolean: return "boolean"; |
59 | 0 | case LiteralKind::String: return "string"; |
60 | 0 | default: yulAssert(false, ""); |
61 | 0 | } |
62 | 0 | } |
63 | | } |
64 | | |
65 | | bool AsmAnalyzer::analyze(Block const& _block) |
66 | 1.43k | { |
67 | 1.43k | auto watcher = m_errorReporter.errorWatcher(); |
68 | 1.43k | try |
69 | 1.43k | { |
70 | 1.43k | if (!(ScopeFiller(m_info, m_errorReporter))(_block)) |
71 | 0 | return false; |
72 | | |
73 | 1.43k | (*this)(_block); |
74 | 1.43k | } |
75 | 1.43k | catch (FatalError const&) |
76 | 1.43k | { |
77 | | // This FatalError con occur if the errorReporter has too many errors. |
78 | 0 | yulAssert(!watcher.ok(), "Fatal error detected, but no error is reported."); |
79 | 0 | } |
80 | 1.43k | return watcher.ok(); |
81 | 1.43k | } |
82 | | |
83 | | AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object) |
84 | 0 | { |
85 | 0 | ErrorList errorList; |
86 | 0 | langutil::ErrorReporter errors(errorList); |
87 | 0 | AsmAnalysisInfo analysisInfo; |
88 | 0 | bool success = yul::AsmAnalyzer( |
89 | 0 | analysisInfo, |
90 | 0 | errors, |
91 | 0 | _dialect, |
92 | 0 | {}, |
93 | 0 | _object.qualifiedDataNames() |
94 | 0 | ).analyze(*_object.code); |
95 | 0 | yulAssert(success && !errors.hasErrors(), "Invalid assembly/yul code."); |
96 | 0 | return analysisInfo; |
97 | 0 | } |
98 | | |
99 | | vector<YulString> AsmAnalyzer::operator()(Literal const& _literal) |
100 | 2.86k | { |
101 | 2.86k | expectValidType(_literal.type, nativeLocationOf(_literal)); |
102 | 2.86k | if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32) |
103 | 0 | m_errorReporter.typeError( |
104 | 0 | 3069_error, |
105 | 0 | nativeLocationOf(_literal), |
106 | 0 | "String literal too long (" + to_string(_literal.value.str().size()) + " > 32)" |
107 | 0 | ); |
108 | 2.86k | else if (_literal.kind == LiteralKind::Number && bigint(_literal.value.str()) > u256(-1)) |
109 | 0 | m_errorReporter.typeError(6708_error, nativeLocationOf(_literal), "Number literal too large (> 256 bits)"); |
110 | 2.86k | else if (_literal.kind == LiteralKind::Boolean) |
111 | 2.86k | yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, ""); |
112 | | |
113 | 2.86k | if (!m_dialect.validTypeForLiteral(_literal.kind, _literal.value, _literal.type)) |
114 | 0 | m_errorReporter.typeError( |
115 | 0 | 5170_error, |
116 | 0 | nativeLocationOf(_literal), |
117 | 0 | "Invalid type \"" + _literal.type.str() + "\" for literal \"" + _literal.value.str() + "\"." |
118 | 0 | ); |
119 | | |
120 | | |
121 | 2.86k | return {_literal.type}; |
122 | 2.86k | } |
123 | | |
124 | | vector<YulString> AsmAnalyzer::operator()(Identifier const& _identifier) |
125 | 2.86k | { |
126 | 2.86k | yulAssert(!_identifier.name.empty(), ""); |
127 | 2.86k | auto watcher = m_errorReporter.errorWatcher(); |
128 | 2.86k | YulString type = m_dialect.defaultType; |
129 | | |
130 | 2.86k | if (m_currentScope->lookup(_identifier.name, GenericVisitor{ |
131 | 2.86k | [&](Scope::Variable const& _var) |
132 | 2.86k | { |
133 | 2.14k | if (!m_activeVariables.count(&_var)) |
134 | 0 | m_errorReporter.declarationError( |
135 | 0 | 4990_error, |
136 | 0 | nativeLocationOf(_identifier), |
137 | 0 | "Variable " + _identifier.name.str() + " used before it was declared." |
138 | 0 | ); |
139 | 2.14k | type = _var.type; |
140 | 2.14k | }, |
141 | 2.86k | [&](Scope::Function const&) |
142 | 2.86k | { |
143 | 0 | m_errorReporter.typeError( |
144 | 0 | 6041_error, |
145 | 0 | nativeLocationOf(_identifier), |
146 | 0 | "Function " + _identifier.name.str() + " used without being called." |
147 | 0 | ); |
148 | 0 | } |
149 | 2.86k | })) |
150 | 2.14k | { |
151 | 2.14k | if (m_resolver) |
152 | | // We found a local reference, make sure there is no external reference. |
153 | 2.14k | m_resolver( |
154 | 2.14k | _identifier, |
155 | 2.14k | yul::IdentifierContext::NonExternal, |
156 | 2.14k | m_currentScope->insideFunction() |
157 | 2.14k | ); |
158 | 2.14k | } |
159 | 716 | else |
160 | 716 | { |
161 | 716 | bool found = m_resolver && m_resolver( |
162 | 716 | _identifier, |
163 | 716 | yul::IdentifierContext::RValue, |
164 | 716 | m_currentScope->insideFunction() |
165 | 716 | ); |
166 | 716 | if (!found && watcher.ok()) |
167 | | // Only add an error message if the callback did not do it. |
168 | 0 | m_errorReporter.declarationError( |
169 | 0 | 8198_error, |
170 | 0 | nativeLocationOf(_identifier), |
171 | 0 | "Identifier \"" + _identifier.name.str() + "\" not found." |
172 | 0 | ); |
173 | | |
174 | 716 | } |
175 | | |
176 | 2.86k | return {type}; |
177 | 2.86k | } |
178 | | |
179 | | void AsmAnalyzer::operator()(ExpressionStatement const& _statement) |
180 | 1.79k | { |
181 | 1.79k | auto watcher = m_errorReporter.errorWatcher(); |
182 | 1.79k | vector<YulString> types = std::visit(*this, _statement.expression); |
183 | 1.79k | if (watcher.ok() && !types.empty()) |
184 | 0 | m_errorReporter.typeError( |
185 | 0 | 3083_error, |
186 | 0 | nativeLocationOf(_statement), |
187 | 0 | "Top-level expressions are not supposed to return values (this expression returns " + |
188 | 0 | to_string(types.size()) + |
189 | 0 | " value" + |
190 | 0 | (types.size() == 1 ? "" : "s") + |
191 | 0 | "). Use ``pop()`` or assign them." |
192 | 0 | ); |
193 | 1.79k | } |
194 | | |
195 | | void AsmAnalyzer::operator()(Assignment const& _assignment) |
196 | 716 | { |
197 | 716 | yulAssert(_assignment.value, ""); |
198 | 716 | size_t const numVariables = _assignment.variableNames.size(); |
199 | 716 | yulAssert(numVariables >= 1, ""); |
200 | | |
201 | 716 | set<YulString> variables; |
202 | 716 | for (auto const& _variableName: _assignment.variableNames) |
203 | 716 | if (!variables.insert(_variableName.name).second) |
204 | 0 | m_errorReporter.declarationError( |
205 | 0 | 9005_error, |
206 | 0 | nativeLocationOf(_assignment), |
207 | 0 | "Variable " + |
208 | 0 | _variableName.name.str() + |
209 | 0 | " occurs multiple times on the left-hand side of the assignment." |
210 | 0 | ); |
211 | | |
212 | 716 | vector<YulString> types = std::visit(*this, *_assignment.value); |
213 | | |
214 | 716 | if (types.size() != numVariables) |
215 | 0 | m_errorReporter.declarationError( |
216 | 0 | 8678_error, |
217 | 0 | nativeLocationOf(_assignment), |
218 | 0 | "Variable count for assignment to \"" + |
219 | 0 | joinHumanReadable(applyMap(_assignment.variableNames, [](auto const& _identifier){ return _identifier.name.str(); })) + |
220 | 0 | "\" does not match number of values (" + |
221 | 0 | to_string(numVariables) + |
222 | 0 | " vs. " + |
223 | 0 | to_string(types.size()) + |
224 | 0 | ")" |
225 | 0 | ); |
226 | | |
227 | 1.43k | for (size_t i = 0; i < numVariables; ++i) |
228 | 716 | if (i < types.size()) |
229 | 716 | checkAssignment(_assignment.variableNames[i], types[i]); |
230 | 716 | } |
231 | | |
232 | | void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) |
233 | 0 | { |
234 | 0 | size_t const numVariables = _varDecl.variables.size(); |
235 | 0 | if (m_resolver) |
236 | 0 | for (auto const& variable: _varDecl.variables) |
237 | | // Call the resolver for variable declarations to allow it to raise errors on shadowing. |
238 | 0 | m_resolver( |
239 | 0 | yul::Identifier{variable.debugData, variable.name}, |
240 | 0 | yul::IdentifierContext::VariableDeclaration, |
241 | 0 | m_currentScope->insideFunction() |
242 | 0 | ); |
243 | 0 | for (auto const& variable: _varDecl.variables) |
244 | 0 | { |
245 | 0 | expectValidIdentifier(variable.name, nativeLocationOf(variable)); |
246 | 0 | expectValidType(variable.type, nativeLocationOf(variable)); |
247 | 0 | } |
248 | |
|
249 | 0 | if (_varDecl.value) |
250 | 0 | { |
251 | 0 | vector<YulString> types = std::visit(*this, *_varDecl.value); |
252 | 0 | if (types.size() != numVariables) |
253 | 0 | m_errorReporter.declarationError( |
254 | 0 | 3812_error, |
255 | 0 | nativeLocationOf(_varDecl), |
256 | 0 | "Variable count mismatch for declaration of \"" + |
257 | 0 | joinHumanReadable(applyMap(_varDecl.variables, [](auto const& _identifier){ return _identifier.name.str(); })) + |
258 | 0 | + "\": " + |
259 | 0 | to_string(numVariables) + |
260 | 0 | " variables and " + |
261 | 0 | to_string(types.size()) + |
262 | 0 | " values." |
263 | 0 | ); |
264 | |
|
265 | 0 | for (size_t i = 0; i < _varDecl.variables.size(); ++i) |
266 | 0 | { |
267 | 0 | YulString givenType = m_dialect.defaultType; |
268 | 0 | if (i < types.size()) |
269 | 0 | givenType = types[i]; |
270 | 0 | TypedName const& variable = _varDecl.variables[i]; |
271 | 0 | if (variable.type != givenType) |
272 | 0 | m_errorReporter.typeError( |
273 | 0 | 3947_error, |
274 | 0 | nativeLocationOf(variable), |
275 | 0 | "Assigning value of type \"" + givenType.str() + "\" to variable of type \"" + variable.type.str() + "\"." |
276 | 0 | ); |
277 | 0 | } |
278 | 0 | } |
279 | |
|
280 | 0 | for (TypedName const& variable: _varDecl.variables) |
281 | 0 | m_activeVariables.insert(&std::get<Scope::Variable>( |
282 | 0 | m_currentScope->identifiers.at(variable.name)) |
283 | 0 | ); |
284 | 0 | } |
285 | | |
286 | | void AsmAnalyzer::operator()(FunctionDefinition const& _funDef) |
287 | 1.07k | { |
288 | 1.07k | yulAssert(!_funDef.name.empty(), ""); |
289 | 1.07k | expectValidIdentifier(_funDef.name, nativeLocationOf(_funDef)); |
290 | 1.07k | Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); |
291 | 1.07k | yulAssert(virtualBlock, ""); |
292 | 1.07k | Scope& varScope = scope(virtualBlock); |
293 | 1.07k | for (auto const& var: _funDef.parameters + _funDef.returnVariables) |
294 | 2.50k | { |
295 | 2.50k | expectValidIdentifier(var.name, nativeLocationOf(var)); |
296 | 2.50k | expectValidType(var.type, nativeLocationOf(var)); |
297 | 2.50k | m_activeVariables.insert(&std::get<Scope::Variable>(varScope.identifiers.at(var.name))); |
298 | 2.50k | } |
299 | | |
300 | 1.07k | (*this)(_funDef.body); |
301 | 1.07k | } |
302 | | |
303 | | vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall) |
304 | 2.86k | { |
305 | 2.86k | yulAssert(!_funCall.functionName.name.empty(), ""); |
306 | 2.86k | auto watcher = m_errorReporter.errorWatcher(); |
307 | 2.86k | vector<YulString> const* parameterTypes = nullptr; |
308 | 2.86k | vector<YulString> const* returnTypes = nullptr; |
309 | 2.86k | vector<optional<LiteralKind>> const* literalArguments = nullptr; |
310 | | |
311 | 2.86k | if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name)) |
312 | 2.14k | { |
313 | 2.14k | parameterTypes = &f->parameters; |
314 | 2.14k | returnTypes = &f->returns; |
315 | 2.14k | if (!f->literalArguments.empty()) |
316 | 0 | literalArguments = &f->literalArguments; |
317 | | |
318 | 2.14k | validateInstructions(_funCall); |
319 | 2.14k | m_sideEffects += f->sideEffects; |
320 | 2.14k | } |
321 | 716 | else if (m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{ |
322 | 716 | [&](Scope::Variable const&) |
323 | 716 | { |
324 | 0 | m_errorReporter.typeError( |
325 | 0 | 4202_error, |
326 | 0 | nativeLocationOf(_funCall.functionName), |
327 | 0 | "Attempt to call variable instead of function." |
328 | 0 | ); |
329 | 0 | }, |
330 | 716 | [&](Scope::Function const& _fun) |
331 | 716 | { |
332 | 716 | parameterTypes = &_fun.arguments; |
333 | 716 | returnTypes = &_fun.returns; |
334 | 716 | } |
335 | 716 | })) |
336 | 716 | { |
337 | 716 | if (m_resolver) |
338 | | // We found a local reference, make sure there is no external reference. |
339 | 716 | m_resolver( |
340 | 716 | _funCall.functionName, |
341 | 716 | yul::IdentifierContext::NonExternal, |
342 | 716 | m_currentScope->insideFunction() |
343 | 716 | ); |
344 | 716 | } |
345 | 0 | else |
346 | 0 | { |
347 | 0 | if (!validateInstructions(_funCall)) |
348 | 0 | m_errorReporter.declarationError( |
349 | 0 | 4619_error, |
350 | 0 | nativeLocationOf(_funCall.functionName), |
351 | 0 | "Function \"" + _funCall.functionName.name.str() + "\" not found." |
352 | 0 | ); |
353 | 0 | yulAssert(!watcher.ok(), "Expected a reported error."); |
354 | 0 | } |
355 | | |
356 | 2.86k | if (parameterTypes && _funCall.arguments.size() != parameterTypes->size()) |
357 | 0 | m_errorReporter.typeError( |
358 | 0 | 7000_error, |
359 | 0 | nativeLocationOf(_funCall.functionName), |
360 | 0 | "Function \"" + _funCall.functionName.name.str() + "\" expects " + |
361 | 0 | to_string(parameterTypes->size()) + |
362 | 0 | " arguments but got " + |
363 | 0 | to_string(_funCall.arguments.size()) + "." |
364 | 0 | ); |
365 | | |
366 | 2.86k | vector<YulString> argTypes; |
367 | 8.23k | for (size_t i = _funCall.arguments.size(); i > 0; i--) |
368 | 5.37k | { |
369 | 5.37k | Expression const& arg = _funCall.arguments[i - 1]; |
370 | 5.37k | if ( |
371 | 5.37k | auto literalArgumentKind = (literalArguments && i <= literalArguments->size()) ? |
372 | 5.37k | literalArguments->at(i - 1) : |
373 | 5.37k | std::nullopt |
374 | 5.37k | ) |
375 | 0 | { |
376 | 0 | if (!holds_alternative<Literal>(arg)) |
377 | 0 | m_errorReporter.typeError( |
378 | 0 | 9114_error, |
379 | 0 | nativeLocationOf(_funCall.functionName), |
380 | 0 | "Function expects direct literals as arguments." |
381 | 0 | ); |
382 | 0 | else if (*literalArgumentKind != get<Literal>(arg).kind) |
383 | 0 | m_errorReporter.typeError( |
384 | 0 | 5859_error, |
385 | 0 | nativeLocationOf(arg), |
386 | 0 | "Function expects " + to_string(*literalArgumentKind) + " literal." |
387 | 0 | ); |
388 | 0 | else if (*literalArgumentKind == LiteralKind::String) |
389 | 0 | { |
390 | 0 | string functionName = _funCall.functionName.name.str(); |
391 | 0 | if (functionName == "datasize" || functionName == "dataoffset") |
392 | 0 | { |
393 | 0 | if (!m_dataNames.count(get<Literal>(arg).value)) |
394 | 0 | m_errorReporter.typeError( |
395 | 0 | 3517_error, |
396 | 0 | nativeLocationOf(arg), |
397 | 0 | "Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"." |
398 | 0 | ); |
399 | 0 | } |
400 | 0 | else if (functionName.substr(0, "verbatim_"s.size()) == "verbatim_") |
401 | 0 | { |
402 | 0 | if (get<Literal>(arg).value.empty()) |
403 | 0 | m_errorReporter.typeError( |
404 | 0 | 1844_error, |
405 | 0 | nativeLocationOf(arg), |
406 | 0 | "The \"verbatim_*\" builtins cannot be used with empty bytecode." |
407 | 0 | ); |
408 | 0 | } |
409 | |
|
410 | 0 | argTypes.emplace_back(expectUnlimitedStringLiteral(get<Literal>(arg))); |
411 | 0 | continue; |
412 | 0 | } |
413 | 0 | } |
414 | 5.37k | argTypes.emplace_back(expectExpression(arg)); |
415 | 5.37k | } |
416 | 2.86k | std::reverse(argTypes.begin(), argTypes.end()); |
417 | | |
418 | 2.86k | if (parameterTypes && parameterTypes->size() == argTypes.size()) |
419 | 8.23k | for (size_t i = 0; i < parameterTypes->size(); ++i) |
420 | 5.37k | expectType((*parameterTypes)[i], argTypes[i], nativeLocationOf(_funCall.arguments[i])); |
421 | | |
422 | 2.86k | if (watcher.ok()) |
423 | 2.86k | { |
424 | 2.86k | yulAssert(parameterTypes && parameterTypes->size() == argTypes.size(), ""); |
425 | 2.86k | yulAssert(returnTypes, ""); |
426 | 2.86k | return *returnTypes; |
427 | 2.86k | } |
428 | 0 | else if (returnTypes) |
429 | 0 | return vector<YulString>(returnTypes->size(), m_dialect.defaultType); |
430 | 0 | else |
431 | 0 | return {}; |
432 | 2.86k | } |
433 | | |
434 | | void AsmAnalyzer::operator()(If const& _if) |
435 | 716 | { |
436 | 716 | expectBoolExpression(*_if.condition); |
437 | | |
438 | 716 | (*this)(_if.body); |
439 | 716 | } |
440 | | |
441 | | void AsmAnalyzer::operator()(Switch const& _switch) |
442 | 0 | { |
443 | 0 | yulAssert(_switch.expression, ""); |
444 | | |
445 | 0 | if (_switch.cases.size() == 1 && !_switch.cases[0].value) |
446 | 0 | m_errorReporter.warning( |
447 | 0 | 9592_error, |
448 | 0 | nativeLocationOf(_switch), |
449 | 0 | "\"switch\" statement with only a default case." |
450 | 0 | ); |
451 | |
|
452 | 0 | YulString valueType = expectExpression(*_switch.expression); |
453 | |
|
454 | 0 | set<u256> cases; |
455 | 0 | for (auto const& _case: _switch.cases) |
456 | 0 | { |
457 | 0 | if (_case.value) |
458 | 0 | { |
459 | 0 | auto watcher = m_errorReporter.errorWatcher(); |
460 | |
|
461 | 0 | expectType(valueType, _case.value->type, nativeLocationOf(*_case.value)); |
462 | | |
463 | | // We cannot use "expectExpression" here because *_case.value is not an |
464 | | // Expression and would be converted to an Expression otherwise. |
465 | 0 | (*this)(*_case.value); |
466 | | |
467 | | /// Note: the parser ensures there is only one default case |
468 | 0 | if (watcher.ok() && !cases.insert(valueOfLiteral(*_case.value)).second) |
469 | 0 | m_errorReporter.declarationError( |
470 | 0 | 6792_error, |
471 | 0 | nativeLocationOf(_case), |
472 | 0 | "Duplicate case \"" + |
473 | 0 | valueOfLiteral(*_case.value).str() + |
474 | 0 | "\" defined." |
475 | 0 | ); |
476 | 0 | } |
477 | |
|
478 | 0 | (*this)(_case.body); |
479 | 0 | } |
480 | 0 | } |
481 | | |
482 | | void AsmAnalyzer::operator()(ForLoop const& _for) |
483 | 0 | { |
484 | 0 | yulAssert(_for.condition, ""); |
485 | | |
486 | 0 | Scope* outerScope = m_currentScope; |
487 | |
|
488 | 0 | (*this)(_for.pre); |
489 | | |
490 | | // The block was closed already, but we re-open it again and stuff the |
491 | | // condition, the body and the post part inside. |
492 | 0 | m_currentScope = &scope(&_for.pre); |
493 | |
|
494 | 0 | expectBoolExpression(*_for.condition); |
495 | | // backup outer for-loop & create new state |
496 | 0 | auto outerForLoop = m_currentForLoop; |
497 | 0 | m_currentForLoop = &_for; |
498 | |
|
499 | 0 | (*this)(_for.body); |
500 | 0 | (*this)(_for.post); |
501 | |
|
502 | 0 | m_currentScope = outerScope; |
503 | 0 | m_currentForLoop = outerForLoop; |
504 | 0 | } |
505 | | |
506 | | void AsmAnalyzer::operator()(Block const& _block) |
507 | 3.22k | { |
508 | 3.22k | auto previousScope = m_currentScope; |
509 | 3.22k | m_currentScope = &scope(&_block); |
510 | | |
511 | 3.22k | for (auto const& s: _block.statements) |
512 | 4.29k | std::visit(*this, s); |
513 | | |
514 | 3.22k | m_currentScope = previousScope; |
515 | 3.22k | } |
516 | | |
517 | | YulString AsmAnalyzer::expectExpression(Expression const& _expr) |
518 | 6.08k | { |
519 | 6.08k | vector<YulString> types = std::visit(*this, _expr); |
520 | 6.08k | if (types.size() != 1) |
521 | 0 | m_errorReporter.typeError( |
522 | 0 | 3950_error, |
523 | 0 | nativeLocationOf(_expr), |
524 | 0 | "Expected expression to evaluate to one value, but got " + |
525 | 0 | to_string(types.size()) + |
526 | 0 | " values instead." |
527 | 0 | ); |
528 | 6.08k | return types.empty() ? m_dialect.defaultType : types.front(); |
529 | 6.08k | } |
530 | | |
531 | | YulString AsmAnalyzer::expectUnlimitedStringLiteral(Literal const& _literal) |
532 | 0 | { |
533 | 0 | yulAssert(_literal.kind == LiteralKind::String, ""); |
534 | 0 | yulAssert(m_dialect.validTypeForLiteral(LiteralKind::String, _literal.value, _literal.type), ""); |
535 | | |
536 | 0 | return {_literal.type}; |
537 | 0 | } |
538 | | |
539 | | void AsmAnalyzer::expectBoolExpression(Expression const& _expr) |
540 | 716 | { |
541 | 716 | YulString type = expectExpression(_expr); |
542 | 716 | if (type != m_dialect.boolType) |
543 | 0 | m_errorReporter.typeError( |
544 | 0 | 1733_error, |
545 | 0 | nativeLocationOf(_expr), |
546 | 0 | "Expected a value of boolean type \"" + |
547 | 0 | m_dialect.boolType.str() + |
548 | 0 | "\" but got \"" + |
549 | 0 | type.str() + |
550 | 0 | "\"" |
551 | 0 | ); |
552 | 716 | } |
553 | | |
554 | | void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueType) |
555 | 716 | { |
556 | 716 | yulAssert(!_variable.name.empty(), ""); |
557 | 716 | auto watcher = m_errorReporter.errorWatcher(); |
558 | 716 | YulString const* variableType = nullptr; |
559 | 716 | bool found = false; |
560 | 716 | if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) |
561 | 716 | { |
562 | 716 | if (m_resolver) |
563 | | // We found a local reference, make sure there is no external reference. |
564 | 716 | m_resolver( |
565 | 716 | _variable, |
566 | 716 | yul::IdentifierContext::NonExternal, |
567 | 716 | m_currentScope->insideFunction() |
568 | 716 | ); |
569 | | |
570 | 716 | if (!holds_alternative<Scope::Variable>(*var)) |
571 | 0 | m_errorReporter.typeError(2657_error, nativeLocationOf(_variable), "Assignment requires variable."); |
572 | 716 | else if (!m_activeVariables.count(&std::get<Scope::Variable>(*var))) |
573 | 0 | m_errorReporter.declarationError( |
574 | 0 | 1133_error, |
575 | 0 | nativeLocationOf(_variable), |
576 | 0 | "Variable " + _variable.name.str() + " used before it was declared." |
577 | 0 | ); |
578 | 716 | else |
579 | 716 | variableType = &std::get<Scope::Variable>(*var).type; |
580 | 716 | found = true; |
581 | 716 | } |
582 | 0 | else if (m_resolver) |
583 | 0 | { |
584 | 0 | bool insideFunction = m_currentScope->insideFunction(); |
585 | 0 | if (m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction)) |
586 | 0 | { |
587 | 0 | found = true; |
588 | 0 | variableType = &m_dialect.defaultType; |
589 | 0 | } |
590 | 0 | } |
591 | | |
592 | 716 | if (!found && watcher.ok()) |
593 | | // Only add message if the callback did not. |
594 | 0 | m_errorReporter.declarationError(4634_error, nativeLocationOf(_variable), "Variable not found or variable not lvalue."); |
595 | 716 | if (variableType && *variableType != _valueType) |
596 | 0 | m_errorReporter.typeError( |
597 | 0 | 9547_error, |
598 | 0 | nativeLocationOf(_variable), |
599 | 0 | "Assigning a value of type \"" + |
600 | 0 | _valueType.str() + |
601 | 0 | "\" to a variable of type \"" + |
602 | 0 | variableType->str() + |
603 | 0 | "\"." |
604 | 0 | ); |
605 | | |
606 | 716 | yulAssert(!watcher.ok() || variableType, ""); |
607 | 716 | } |
608 | | |
609 | | Scope& AsmAnalyzer::scope(Block const* _block) |
610 | 4.29k | { |
611 | 4.29k | yulAssert(m_info.scopes.count(_block) == 1, "Scope requested but not present."); |
612 | 4.29k | auto scopePtr = m_info.scopes.at(_block); |
613 | 4.29k | yulAssert(scopePtr, "Scope requested but not present."); |
614 | 4.29k | return *scopePtr; |
615 | 4.29k | } |
616 | | |
617 | | void AsmAnalyzer::expectValidIdentifier(YulString _identifier, SourceLocation const& _location) |
618 | 3.58k | { |
619 | | // NOTE: the leading dot case is handled by the parser not allowing it. |
620 | | |
621 | 3.58k | if (boost::ends_with(_identifier.str(), ".")) |
622 | 0 | m_errorReporter.syntaxError( |
623 | 0 | 3384_error, |
624 | 0 | _location, |
625 | 0 | "\"" + _identifier.str() + "\" is not a valid identifier (ends with a dot)." |
626 | 0 | ); |
627 | | |
628 | 3.58k | if (_identifier.str().find("..") != std::string::npos) |
629 | 0 | m_errorReporter.syntaxError( |
630 | 0 | 7771_error, |
631 | 0 | _location, |
632 | 0 | "\"" + _identifier.str() + "\" is not a valid identifier (contains consecutive dots)." |
633 | 0 | ); |
634 | | |
635 | 3.58k | if (m_dialect.reservedIdentifier(_identifier)) |
636 | 0 | m_errorReporter.declarationError( |
637 | 0 | 5017_error, |
638 | 0 | _location, |
639 | 0 | "The identifier \"" + _identifier.str() + "\" is reserved and can not be used." |
640 | 0 | ); |
641 | 3.58k | } |
642 | | |
643 | | void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location) |
644 | 5.37k | { |
645 | 5.37k | if (!m_dialect.types.count(_type)) |
646 | 0 | m_errorReporter.typeError( |
647 | 0 | 5473_error, |
648 | 0 | _location, |
649 | 0 | fmt::format("\"{}\" is not a valid type (user defined types are not yet supported).", _type) |
650 | 0 | ); |
651 | 5.37k | } |
652 | | |
653 | | void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, SourceLocation const& _location) |
654 | 5.37k | { |
655 | 5.37k | if (_expectedType != _givenType) |
656 | 0 | m_errorReporter.typeError( |
657 | 0 | 3781_error, |
658 | 0 | _location, |
659 | 0 | fmt::format("Expected a value of type \"{}\" but got \"{}\".", _expectedType, _givenType) |
660 | 0 | ); |
661 | 5.37k | } |
662 | | |
663 | | bool AsmAnalyzer::validateInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location) |
664 | 2.14k | { |
665 | 2.14k | auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier)); |
666 | 2.14k | if (builtin && builtin->instruction.has_value()) |
667 | 2.14k | return validateInstructions(builtin->instruction.value(), _location); |
668 | 0 | else |
669 | 0 | return false; |
670 | 2.14k | } |
671 | | |
672 | | bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocation const& _location) |
673 | 2.14k | { |
674 | | // We assume that returndatacopy, returndatasize and staticcall are either all available |
675 | | // or all not available. |
676 | 2.14k | yulAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), ""); |
677 | | // Similarly we assume bitwise shifting and create2 go together. |
678 | 2.14k | yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), ""); |
679 | | |
680 | | // These instructions are disabled in the dialect. |
681 | 2.14k | yulAssert( |
682 | 2.14k | _instr != evmasm::Instruction::JUMP && |
683 | 2.14k | _instr != evmasm::Instruction::JUMPI && |
684 | 2.14k | _instr != evmasm::Instruction::JUMPDEST, |
685 | 2.14k | ""); |
686 | | |
687 | 2.14k | auto errorForVM = [&](ErrorId _errorId, string const& vmKindMessage) { |
688 | 0 | m_errorReporter.typeError( |
689 | 0 | _errorId, |
690 | 0 | _location, |
691 | 0 | fmt::format( |
692 | 0 | "The \"{instruction}\" instruction is {kind} VMs (you are currently compiling for \"{version}\").", |
693 | 0 | fmt::arg("instruction", boost::to_lower_copy(instructionInfo(_instr).name)), |
694 | 0 | fmt::arg("kind", vmKindMessage), |
695 | 0 | fmt::arg("version", m_evmVersion.name()) |
696 | 0 | ) |
697 | 0 | ); |
698 | 0 | }; |
699 | | |
700 | 2.14k | if (_instr == evmasm::Instruction::RETURNDATACOPY && !m_evmVersion.supportsReturndata()) |
701 | 0 | errorForVM(7756_error, "only available for Byzantium-compatible"); |
702 | 2.14k | else if (_instr == evmasm::Instruction::RETURNDATASIZE && !m_evmVersion.supportsReturndata()) |
703 | 0 | errorForVM(4778_error, "only available for Byzantium-compatible"); |
704 | 2.14k | else if (_instr == evmasm::Instruction::STATICCALL && !m_evmVersion.hasStaticCall()) |
705 | 0 | errorForVM(1503_error, "only available for Byzantium-compatible"); |
706 | 2.14k | else if (_instr == evmasm::Instruction::SHL && !m_evmVersion.hasBitwiseShifting()) |
707 | 0 | errorForVM(6612_error, "only available for Constantinople-compatible"); |
708 | 2.14k | else if (_instr == evmasm::Instruction::SHR && !m_evmVersion.hasBitwiseShifting()) |
709 | 0 | errorForVM(7458_error, "only available for Constantinople-compatible"); |
710 | 2.14k | else if (_instr == evmasm::Instruction::SAR && !m_evmVersion.hasBitwiseShifting()) |
711 | 0 | errorForVM(2054_error, "only available for Constantinople-compatible"); |
712 | 2.14k | else if (_instr == evmasm::Instruction::CREATE2 && !m_evmVersion.hasCreate2()) |
713 | 0 | errorForVM(6166_error, "only available for Constantinople-compatible"); |
714 | 2.14k | else if (_instr == evmasm::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) |
715 | 0 | errorForVM(7110_error, "only available for Constantinople-compatible"); |
716 | 2.14k | else if (_instr == evmasm::Instruction::CHAINID && !m_evmVersion.hasChainID()) |
717 | 0 | errorForVM(1561_error, "only available for Istanbul-compatible"); |
718 | 2.14k | else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance()) |
719 | 0 | errorForVM(7721_error, "only available for Istanbul-compatible"); |
720 | 2.14k | else if (_instr == evmasm::Instruction::BASEFEE && !m_evmVersion.hasBaseFee()) |
721 | 0 | errorForVM(5430_error, "only available for London-compatible"); |
722 | 2.14k | else if (_instr == evmasm::Instruction::PC) |
723 | 0 | m_errorReporter.error( |
724 | 0 | 2450_error, |
725 | 0 | Error::Type::SyntaxError, |
726 | 0 | _location, |
727 | 0 | "PC instruction is a low-level EVM feature. " |
728 | 0 | "Because of that PC is disallowed in strict assembly." |
729 | 0 | ); |
730 | 2.14k | else |
731 | 2.14k | return false; |
732 | | |
733 | 0 | return true; |
734 | 2.14k | } |
735 | | |
736 | | bool AsmAnalyzer::validateInstructions(FunctionCall const& _functionCall) |
737 | 2.14k | { |
738 | 2.14k | return validateInstructions(_functionCall.functionName.name.str(), nativeLocationOf(_functionCall.functionName)); |
739 | 2.14k | } |