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 /**
18  * @author Christian <c@ethdev.com>
19  * @date 2014
20  * Unit tests for the solidity parser.
21  */
22 
23 #include <string>
24 #include <memory>
25 #include <liblangutil/Scanner.h>
26 #include <libsolidity/parsing/Parser.h>
27 #include <liblangutil/ErrorReporter.h>
28 #include <test/Common.h>
29 #include <test/libsolidity/ErrorCheck.h>
30 #include <libsolidity/ast/ASTVisitor.h>
31 
32 #include <boost/test/unit_test.hpp>
33 
34 using namespace std;
35 using namespace solidity::langutil;
36 
37 namespace solidity::frontend::test
38 {
39 
40 namespace
41 {
parseText(std::string const & _source,ErrorList & _errors,bool errorRecovery=false)42 ASTPointer<ContractDefinition> parseText(std::string const& _source, ErrorList& _errors, bool errorRecovery = false)
43 {
44 	ErrorReporter errorReporter(_errors);
45 	auto charStream = CharStream(_source, "");
46 	ASTPointer<SourceUnit> sourceUnit = Parser(
47 		errorReporter,
48 		solidity::test::CommonOptions::get().evmVersion(),
49 		errorRecovery
50 	).parse(charStream);
51 	if (!sourceUnit)
52 		return ASTPointer<ContractDefinition>();
53 	for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
54 		if (ASTPointer<ContractDefinition> contract = dynamic_pointer_cast<ContractDefinition>(node))
55 			return contract;
56 	BOOST_FAIL("No contract found in source.");
57 	return ASTPointer<ContractDefinition>();
58 }
59 
successParse(std::string const & _source)60 bool successParse(std::string const& _source)
61 {
62 	ErrorList errors;
63 	try
64 	{
65 		auto sourceUnit = parseText(_source, errors);
66 		if (!sourceUnit)
67 			return false;
68 	}
69 	catch (FatalError const& /*_exception*/)
70 	{
71 		if (Error::containsErrorOfType(errors, Error::Type::ParserError))
72 			return false;
73 	}
74 	if (Error::containsErrorOfType(errors, Error::Type::ParserError))
75 		return false;
76 
77 	BOOST_CHECK(!Error::containsErrors(errors));
78 	return true;
79 }
80 
getError(std::string const & _source,bool errorRecovery=false)81 Error getError(std::string const& _source, bool errorRecovery = false)
82 {
83 	ErrorList errors;
84 	try
85 	{
86 		parseText(_source, errors, errorRecovery);
87 	}
88 	catch (FatalError const& /*_exception*/)
89 	{
90 		// no-op
91 	}
92 	Error const* error = Error::containsErrorOfType(errors, Error::Type::ParserError);
93 	BOOST_REQUIRE(error);
94 	return *error;
95 }
96 
checkFunctionNatspec(FunctionDefinition const * _function,std::string const & _expectedDoc)97 void checkFunctionNatspec(
98 	FunctionDefinition const* _function,
99 	std::string const& _expectedDoc
100 )
101 {
102 	auto doc = _function->documentation()->text();
103 	BOOST_CHECK_MESSAGE(doc != nullptr, "Function does not have Natspec Doc as expected");
104 	BOOST_CHECK_EQUAL(*doc, _expectedDoc);
105 }
106 
107 }
108 
109 #define CHECK_PARSE_ERROR(source, substring) \
110 do \
111 {\
112 	Error err = getError((source)); \
113 	BOOST_CHECK(searchErrorMessage(err, (substring))); \
114 }\
115 while(0)
116 
117 
118 BOOST_AUTO_TEST_SUITE(SolidityParser)
119 
BOOST_AUTO_TEST_CASE(reserved_keywords)120 BOOST_AUTO_TEST_CASE(reserved_keywords)
121 {
122 	BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Identifier));
123 	BOOST_CHECK(TokenTraits::isReservedKeyword(Token::After));
124 	BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Unchecked));
125 	BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Var));
126 	BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Reference));
127 	BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Illegal));
128 }
129 
BOOST_AUTO_TEST_CASE(function_natspec_documentation)130 BOOST_AUTO_TEST_CASE(function_natspec_documentation)
131 {
132 	char const* text = R"(
133 		contract test {
134 			uint256 stateVar;
135 			/// This is a test function
136 			function functionName(bytes32 input) public returns (bytes32 out) {}
137 		}
138 	)";
139 	BOOST_CHECK(successParse(text));
140 	ErrorList errors;
141 	ASTPointer<ContractDefinition> contract = parseText(text, errors);
142 	FunctionDefinition const* function = nullptr;
143 	auto functions = contract->definedFunctions();
144 
145 	BOOST_REQUIRE_MESSAGE(function = functions.at(0), "Failed to retrieve function");
146 	checkFunctionNatspec(function, "This is a test function");
147 }
148 
BOOST_AUTO_TEST_CASE(function_normal_comments)149 BOOST_AUTO_TEST_CASE(function_normal_comments)
150 {
151 	FunctionDefinition const* function = nullptr;
152 	char const* text = R"(
153 		contract test {
154 			uint256 stateVar;
155 			// We won't see this comment
156 			function functionName(bytes32 input) public returns (bytes32 out) {}
157 		}
158 	)";
159 	BOOST_CHECK(successParse(text));
160 	ErrorList errors;
161 	ASTPointer<ContractDefinition> contract = parseText(text, errors);
162 	auto functions = contract->definedFunctions();
163 	BOOST_REQUIRE_MESSAGE(function = functions.at(0), "Failed to retrieve function");
164 	BOOST_CHECK_MESSAGE(function->documentation() == nullptr,
165 						"Should not have gotten a Natspecc comment for this function");
166 }
167 
BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation)168 BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation)
169 {
170 	FunctionDefinition const* function = nullptr;
171 	char const* text = R"(
172 		contract test {
173 			uint256 stateVar;
174 			/// This is test function 1
175 			function functionName1(bytes32 input) public returns (bytes32 out) {}
176 			/// This is test function 2
177 			function functionName2(bytes32 input) public returns (bytes32 out) {}
178 			// nothing to see here
179 			function functionName3(bytes32 input) public returns (bytes32 out) {}
180 			/// This is test function 4
181 			function functionName4(bytes32 input) public returns (bytes32 out) {}
182 		}
183 	)";
184 	BOOST_CHECK(successParse(text));
185 	ErrorList errors;
186 	ASTPointer<ContractDefinition> contract = parseText(text, errors);
187 	auto functions = contract->definedFunctions();
188 
189 	BOOST_REQUIRE_MESSAGE(function = functions.at(0), "Failed to retrieve function");
190 	checkFunctionNatspec(function, "This is test function 1");
191 
192 	BOOST_REQUIRE_MESSAGE(function = functions.at(1), "Failed to retrieve function");
193 	checkFunctionNatspec(function, "This is test function 2");
194 
195 	BOOST_REQUIRE_MESSAGE(function = functions.at(2), "Failed to retrieve function");
196 	BOOST_CHECK_MESSAGE(function->documentation() == nullptr,
197 						"Should not have gotten natspec comment for functionName3()");
198 
199 	BOOST_REQUIRE_MESSAGE(function = functions.at(3), "Failed to retrieve function");
200 	checkFunctionNatspec(function, "This is test function 4");
201 }
202 
BOOST_AUTO_TEST_CASE(multiline_function_documentation)203 BOOST_AUTO_TEST_CASE(multiline_function_documentation)
204 {
205 	FunctionDefinition const* function = nullptr;
206 	char const* text = R"(
207 		contract test {
208 			uint256 stateVar;
209 			/// This is a test function
210 			/// and it has 2 lines
211 			function functionName1(bytes32 input) public returns (bytes32 out) {}
212 		}
213 	)";
214 	BOOST_CHECK(successParse(text));
215 	ErrorList errors;
216 	ASTPointer<ContractDefinition> contract = parseText(text, errors);
217 	auto functions = contract->definedFunctions();
218 	BOOST_REQUIRE_MESSAGE(function = functions.at(0), "Failed to retrieve function");
219 	checkFunctionNatspec(function, "This is a test function\n"
220 						 " and it has 2 lines");
221 }
222 
BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body)223 BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body)
224 {
225 	FunctionDefinition const* function = nullptr;
226 	char const* text = R"(
227 		contract test {
228 			/// fun1 description
229 			function fun1(uint256 a) {
230 				uint b;
231 				// I should not interfere with actual natspec comments (natspec comments on local variables not allowed anymore)
232 				uint256 c;
233 				mapping(address=>bytes32) d;
234 				bytes7 name = "Solidity";
235 			}
236 			/// This is a test function
237 			/// and it has 2 lines
238 			function fun(bytes32 input) public returns (bytes32 out) {}
239 		}
240 	)";
241 	BOOST_CHECK(successParse(text));
242 	ErrorList errors;
243 	ASTPointer<ContractDefinition> contract = parseText(text, errors);
244 	auto functions = contract->definedFunctions();
245 
246 	BOOST_REQUIRE_MESSAGE(function = functions.at(0), "Failed to retrieve function");
247 	checkFunctionNatspec(function, "fun1 description");
248 
249 	BOOST_REQUIRE_MESSAGE(function = functions.at(1), "Failed to retrieve function");
250 	checkFunctionNatspec(function, "This is a test function\n"
251 						 " and it has 2 lines");
252 }
253 
BOOST_AUTO_TEST_CASE(natspec_docstring_between_keyword_and_signature)254 BOOST_AUTO_TEST_CASE(natspec_docstring_between_keyword_and_signature)
255 {
256 	FunctionDefinition const* function = nullptr;
257 	char const* text = R"(
258 		contract test {
259 			uint256 stateVar;
260 			function ///I am in the wrong place
261 			fun1(uint256 a) {
262 				uint b;
263 				// I should not interfere with actual natspec comments (natspec comments on local variables not allowed anymore)
264 				uint256 c;
265 				mapping(address=>bytes32) d;
266 				bytes7 name = "Solidity";
267 			}
268 		}
269 	)";
270 	BOOST_CHECK(successParse(text));
271 	ErrorList errors;
272 	ASTPointer<ContractDefinition> contract = parseText(text, errors);
273 	auto functions = contract->definedFunctions();
274 
275 	BOOST_REQUIRE_MESSAGE(function = functions.at(0), "Failed to retrieve function");
276 	BOOST_CHECK_MESSAGE(!function->documentation(),
277 						"Shouldn't get natspec docstring for this function");
278 }
279 
BOOST_AUTO_TEST_CASE(natspec_docstring_after_signature)280 BOOST_AUTO_TEST_CASE(natspec_docstring_after_signature)
281 {
282 	FunctionDefinition const* function = nullptr;
283 	char const* text = R"(
284 		contract test {
285 			uint256 stateVar;
286 			function fun1(uint256 a) {
287 				// I should have been above the function signature (natspec comments on local variables not allowed anymore)
288 				uint b;
289 				// I should not interfere with actual natspec comments (natspec comments on local variables not allowed anymore)
290 				uint256 c;
291 				mapping(address=>bytes32) d;
292 				bytes7 name = "Solidity";
293 			}
294 		}
295 	)";
296 	BOOST_CHECK(successParse(text));
297 	ErrorList errors;
298 	ASTPointer<ContractDefinition> contract = parseText(text, errors);
299 	auto functions = contract->definedFunctions();
300 
301 	BOOST_REQUIRE_MESSAGE(function = functions.at(0), "Failed to retrieve function");
302 	BOOST_CHECK_MESSAGE(!function->documentation(),
303 						"Shouldn't get natspec docstring for this function");
304 }
305 
BOOST_AUTO_TEST_CASE(variable_definition)306 BOOST_AUTO_TEST_CASE(variable_definition)
307 {
308 	char const* text = R"(
309 		contract test {
310 			function fun(uint256 a) {
311 				uint b;
312 				uint256 c;
313 				mapping(address=>bytes32) d;
314 				customtype varname;
315 			}
316 		}
317 	)";
318 	BOOST_CHECK(successParse(text));
319 }
320 
BOOST_AUTO_TEST_CASE(variable_definition_with_initialization)321 BOOST_AUTO_TEST_CASE(variable_definition_with_initialization)
322 {
323 	char const* text = R"(
324 		contract test {
325 			function fun(uint256 a) {
326 				uint b = 2;
327 				uint256 c = 0x87;
328 				mapping(address=>bytes32) d;
329 				bytes7 name = "Solidity";
330 				customtype varname;
331 			}
332 		}
333 	)";
334 	BOOST_CHECK(successParse(text));
335 }
336 
BOOST_AUTO_TEST_CASE(operator_expression)337 BOOST_AUTO_TEST_CASE(operator_expression)
338 {
339 	char const* text = R"(
340 		contract test {
341 			function fun(uint256 a) {
342 				uint256 x = (1 + 4) || false && (1 - 12) + -9;
343 			}
344 		}
345 	)";
346 	BOOST_CHECK(successParse(text));
347 }
348 
BOOST_AUTO_TEST_CASE(complex_expression)349 BOOST_AUTO_TEST_CASE(complex_expression)
350 {
351 	char const* text = R"(
352 		contract test {
353 			function fun(uint256 a) {
354 				uint256 x = (1 + 4).member(++67)[a/=9] || true;
355 			}
356 		}
357 	)";
358 	BOOST_CHECK(successParse(text));
359 }
360 
BOOST_AUTO_TEST_CASE(statement_starting_with_type_conversion)361 BOOST_AUTO_TEST_CASE(statement_starting_with_type_conversion)
362 {
363 	char const* text = R"(
364 		contract test {
365 			function fun() {
366 				uint64(2);
367 				uint64[7](3);
368 				uint64[](3);
369 			}
370 		}
371 	)";
372 	BOOST_CHECK(successParse(text));
373 }
374 
BOOST_AUTO_TEST_CASE(type_conversion_to_dynamic_array)375 BOOST_AUTO_TEST_CASE(type_conversion_to_dynamic_array)
376 {
377 	char const* text = R"(
378 		contract test {
379 			function fun() {
380 				uint x = uint64[](3);
381 			}
382 		}
383 	)";
384 	BOOST_CHECK(successParse(text));
385 }
386 
BOOST_AUTO_TEST_CASE(import_directive)387 BOOST_AUTO_TEST_CASE(import_directive)
388 {
389 	char const* text = R"(
390 		import "abc";
391 		contract test {
392 			function fun() {
393 				uint64(2);
394 			}
395 		}
396 	)";
397 	BOOST_CHECK(successParse(text));
398 }
399 
BOOST_AUTO_TEST_CASE(multiple_contracts)400 BOOST_AUTO_TEST_CASE(multiple_contracts)
401 {
402 	char const* text = R"(
403 		contract test {
404 			function fun() {
405 				uint64(2);
406 			}
407 		}
408 		contract test2 {
409 			function fun() {
410 				uint64(2);
411 			}
412 		}
413 	)";
414 	BOOST_CHECK(successParse(text));
415 }
416 
BOOST_AUTO_TEST_CASE(multiple_contracts_and_imports)417 BOOST_AUTO_TEST_CASE(multiple_contracts_and_imports)
418 {
419 	char const* text = R"(
420 		import "abc";
421 		contract test {
422 			function fun() {
423 				uint64(2);
424 			}
425 		}
426 		import "def";
427 		contract test2 {
428 			function fun() {
429 				uint64(2);
430 			}
431 		}
432 		import "ghi";
433 	)";
434 	BOOST_CHECK(successParse(text));
435 }
436 
BOOST_AUTO_TEST_CASE(contract_inheritance)437 BOOST_AUTO_TEST_CASE(contract_inheritance)
438 {
439 	char const* text = R"(
440 		contract base {
441 			function fun() {
442 				uint64(2);
443 			}
444 		}
445 		contract derived is base {
446 			function fun() {
447 				uint64(2);
448 			}
449 		}
450 	)";
451 	BOOST_CHECK(successParse(text));
452 }
453 
BOOST_AUTO_TEST_CASE(contract_multiple_inheritance)454 BOOST_AUTO_TEST_CASE(contract_multiple_inheritance)
455 {
456 	char const* text = R"(
457 		contract base {
458 			function fun() {
459 				uint64(2);
460 			}
461 		}
462 		contract derived is base, nonExisting {
463 			function fun() {
464 				uint64(2);
465 			}
466 		}
467 	)";
468 	BOOST_CHECK(successParse(text));
469 }
470 
BOOST_AUTO_TEST_CASE(contract_multiple_inheritance_with_arguments)471 BOOST_AUTO_TEST_CASE(contract_multiple_inheritance_with_arguments)
472 {
473 	char const* text = R"(
474 		contract base {
475 			function fun() {
476 				uint64(2);
477 			}
478 		}
479 		contract derived is base(2), nonExisting("abc", "def", base.fun()) {
480 			function fun() {
481 				uint64(2);
482 			}
483 		}
484 	)";
485 	BOOST_CHECK(successParse(text));
486 }
487 
BOOST_AUTO_TEST_CASE(keyword_is_reserved)488 BOOST_AUTO_TEST_CASE(keyword_is_reserved)
489 {
490 	auto keywords = {
491 		"after",
492 		"alias",
493 		"apply",
494 		"auto",
495 		"byte",
496 		"case",
497 		"copyof",
498 		"default",
499 		"define",
500 		"final",
501 		"implements",
502 		"in",
503 		"inline",
504 		"let",
505 		"macro",
506 		"match",
507 		"mutable",
508 		"null",
509 		"of",
510 		"partial",
511 		"promise",
512 		"reference",
513 		"relocatable",
514 		"sealed",
515 		"sizeof",
516 		"static",
517 		"supports",
518 		"switch",
519 		"typedef",
520 		"typeof",
521 		"var"
522 	};
523 
524 	BOOST_CHECK_EQUAL(std::size(keywords), static_cast<int>(Token::Var) - static_cast<int>(Token::After) + 1);
525 
526 	for (auto const& keyword: keywords)
527 	{
528 		auto text = std::string("contract ") + keyword + " {}";
529 		CHECK_PARSE_ERROR(text.c_str(), string("Expected identifier but got reserved keyword '") + keyword + "'");
530 	}
531 }
532 
BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity)533 BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity)
534 {
535 	char const* text = R"(
536 		contract C {
537 			struct S { uint a; uint b; uint[][][] c; }
538 			function f() {
539 				C.S x;
540 				C.S memory y;
541 				C.S[10] memory z;
542 				C.S[10](x);
543 				x.a = 2;
544 				x.c[1][2][3] = 9;
545 			}
546 		}
547 	)";
548 	BOOST_CHECK(successParse(text));
549 }
550 
BOOST_AUTO_TEST_CASE(using_for)551 BOOST_AUTO_TEST_CASE(using_for)
552 {
553 	char const* text = R"(
554 		contract C {
555 			struct s { uint a; }
556 			using LibraryName for uint;
557 			using Library2 for *;
558 			using Lib for s;
559 			function f() {
560 			}
561 		}
562 	)";
563 	BOOST_CHECK(successParse(text));
564 }
565 
BOOST_AUTO_TEST_CASE(complex_import)566 BOOST_AUTO_TEST_CASE(complex_import)
567 {
568 	char const* text = R"(
569 		import "abc" as x;
570 		import * as x from "abc";
571 		import {a as b, c as d, f} from "def";
572 		contract x {}
573 	)";
574 	BOOST_CHECK(successParse(text));
575 }
576 
BOOST_AUTO_TEST_CASE(inline_asm_end_location)577 BOOST_AUTO_TEST_CASE(inline_asm_end_location)
578 {
579 	auto sourceCode = std::string(R"(
580 	contract C {
581 		function f() public pure returns (uint y) {
582 			uint a;
583 			assembly { a := 0x12345678 }
584 			uint z = a;
585 			y = z;
586 		}
587 	}
588 	)");
589 	ErrorList errors;
590 	auto contract = parseText(sourceCode, errors);
591 
592 	class CheckInlineAsmLocation: public ASTConstVisitor
593 	{
594 	public:
595 		explicit CheckInlineAsmLocation(string _sourceCode): m_sourceCode(_sourceCode) {}
596 		bool visited = false;
597 		bool visit(InlineAssembly const& _inlineAsm) override
598 		{
599 			auto loc = _inlineAsm.location();
600 			auto asmStr = m_sourceCode.substr(static_cast<size_t>(loc.start), static_cast<size_t>(loc.end - loc.start));
601 			BOOST_CHECK_EQUAL(asmStr, "assembly { a := 0x12345678 }");
602 			visited = true;
603 
604 			return false;
605 		}
606 		string m_sourceCode;
607 	};
608 
609 	CheckInlineAsmLocation visitor{sourceCode};
610 	contract->accept(visitor);
611 
612 	BOOST_CHECK_MESSAGE(visitor.visited, "No inline asm block found?!");
613 }
614 
615 BOOST_AUTO_TEST_SUITE_END()
616 
617 } // end namespaces
618