mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #11455 from ethereum/issue-11381
Fix: Allow multiple @return tags on public state variables
This commit is contained in:
		
						commit
						ad3bc71f27
					
				| @ -20,6 +20,7 @@ Bugfixes: | |||||||
|  * Code Generator: Fix internal error when super would have to skip an unimplemented function in the virtual resolution order. |  * Code Generator: Fix internal error when super would have to skip an unimplemented function in the virtual resolution order. | ||||||
|  * Control Flow Graph: Take internal calls to functions that always revert into account for reporting unused or unassigned variables. |  * Control Flow Graph: Take internal calls to functions that always revert into account for reporting unused or unassigned variables. | ||||||
|  * Control Flow Graph: Assume unimplemented modifiers use a placeholder. |  * Control Flow Graph: Assume unimplemented modifiers use a placeholder. | ||||||
|  |  * Natspec: Allow multiple ``@return`` tags on public state variable documentation. | ||||||
|  * SMTChecker: Fix internal error on struct constructor with fixed bytes member initialized with string literal. |  * SMTChecker: Fix internal error on struct constructor with fixed bytes member initialized with string literal. | ||||||
|  * SMTChecker: Fix internal error on external calls from the constructor. |  * SMTChecker: Fix internal error on external calls from the constructor. | ||||||
|  * SMTChecker: Fix internal error on conversion from ``bytes`` to ``fixed bytes``. |  * SMTChecker: Fix internal error on conversion from ``bytes`` to ``fixed bytes``. | ||||||
|  | |||||||
| @ -47,6 +47,68 @@ bool DocStringTagParser::parseDocStrings(SourceUnit const& _sourceUnit) | |||||||
| 	return errorWatcher.ok(); | 	return errorWatcher.ok(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool DocStringTagParser::validateDocStringsUsingTypes(SourceUnit const& _sourceUnit) | ||||||
|  | { | ||||||
|  | 	ErrorReporter::ErrorWatcher errorWatcher = m_errorReporter.errorWatcher(); | ||||||
|  | 
 | ||||||
|  | 	SimpleASTVisitor visitReturns( | ||||||
|  | 		[](ASTNode const&) { return true; }, | ||||||
|  | 		[&](ASTNode const& _node) | ||||||
|  | 		{ | ||||||
|  | 			if (auto const* annotation = dynamic_cast<StructurallyDocumentedAnnotation const*>(&_node.annotation())) | ||||||
|  | 			{ | ||||||
|  | 				auto const& documentationNode = dynamic_cast<StructurallyDocumented const&>(_node); | ||||||
|  | 
 | ||||||
|  | 				size_t returnTagsVisited = 0; | ||||||
|  | 
 | ||||||
|  | 				for (auto const& [tagName, tagValue]: annotation->docTags) | ||||||
|  | 					if (tagName == "return") | ||||||
|  | 					{ | ||||||
|  | 						returnTagsVisited++; | ||||||
|  | 						vector<string> returnParameterNames; | ||||||
|  | 
 | ||||||
|  | 						if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(&_node)) | ||||||
|  | 						{ | ||||||
|  | 							if (!varDecl->isPublic()) | ||||||
|  | 								continue; | ||||||
|  | 
 | ||||||
|  | 							// FunctionType() requires the DeclarationTypeChecker to have run.
 | ||||||
|  | 							returnParameterNames = FunctionType(*varDecl).returnParameterNames(); | ||||||
|  | 						} | ||||||
|  | 						else if (auto const* function = dynamic_cast<FunctionDefinition const*>(&_node)) | ||||||
|  | 							returnParameterNames = FunctionType(*function).returnParameterNames(); | ||||||
|  | 						else | ||||||
|  | 							continue; | ||||||
|  | 
 | ||||||
|  | 						string content = tagValue.content; | ||||||
|  | 						string firstWord = content.substr(0, content.find_first_of(" \t")); | ||||||
|  | 
 | ||||||
|  | 						if (returnTagsVisited > returnParameterNames.size()) | ||||||
|  | 							m_errorReporter.docstringParsingError( | ||||||
|  | 								2604_error, | ||||||
|  | 								documentationNode.documentation()->location(), | ||||||
|  | 								"Documentation tag \"@" + tagName + " " + content + "\"" + | ||||||
|  | 								" exceeds the number of return parameters." | ||||||
|  | 							); | ||||||
|  | 						else | ||||||
|  | 						{ | ||||||
|  | 							string const& parameter = returnParameterNames.at(returnTagsVisited - 1); | ||||||
|  | 							if (!parameter.empty() && parameter != firstWord) | ||||||
|  | 								m_errorReporter.docstringParsingError( | ||||||
|  | 									5856_error, | ||||||
|  | 									documentationNode.documentation()->location(), | ||||||
|  | 									"Documentation tag \"@" + tagName + " " + content + "\"" + | ||||||
|  | 									" does not contain the name of its return parameter." | ||||||
|  | 								); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 			} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	_sourceUnit.accept(visitReturns); | ||||||
|  | 	return errorWatcher.ok(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool DocStringTagParser::visit(ContractDefinition const& _contract) | bool DocStringTagParser::visit(ContractDefinition const& _contract) | ||||||
| { | { | ||||||
| 	static set<string> const validTags = set<string>{"author", "title", "dev", "notice"}; | 	static set<string> const validTags = set<string>{"author", "title", "dev", "notice"}; | ||||||
| @ -169,7 +231,6 @@ void DocStringTagParser::parseDocStrings( | |||||||
| 
 | 
 | ||||||
| 	_annotation.docTags = DocStringParser{*_node.documentation(), m_errorReporter}.parse(); | 	_annotation.docTags = DocStringParser{*_node.documentation(), m_errorReporter}.parse(); | ||||||
| 
 | 
 | ||||||
| 	size_t returnTagsVisited = 0; |  | ||||||
| 	for (auto const& [tagName, tagValue]: _annotation.docTags) | 	for (auto const& [tagName, tagValue]: _annotation.docTags) | ||||||
| 	{ | 	{ | ||||||
| 		string static const customPrefix("custom:"); | 		string static const customPrefix("custom:"); | ||||||
| @ -196,43 +257,6 @@ void DocStringTagParser::parseDocStrings( | |||||||
| 				_node.documentation()->location(), | 				_node.documentation()->location(), | ||||||
| 				"Documentation tag @" + tagName + " not valid for " + _nodeName + "." | 				"Documentation tag @" + tagName + " not valid for " + _nodeName + "." | ||||||
| 			); | 			); | ||||||
| 		else if (tagName == "return") |  | ||||||
| 		{ |  | ||||||
| 			returnTagsVisited++; |  | ||||||
| 			if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(&_node)) |  | ||||||
| 			{ |  | ||||||
| 				solAssert(varDecl->isPublic(), "@return is only allowed on public state-variables."); |  | ||||||
| 				if (returnTagsVisited > 1) |  | ||||||
| 					m_errorReporter.docstringParsingError( |  | ||||||
| 						5256_error, |  | ||||||
| 						_node.documentation()->location(), |  | ||||||
| 						"Documentation tag \"@" + tagName + "\" is only allowed once on state-variables." |  | ||||||
| 					); |  | ||||||
| 			} |  | ||||||
| 			else if (auto const* function = dynamic_cast<FunctionDefinition const*>(&_node)) |  | ||||||
| 			{ |  | ||||||
| 				string content = tagValue.content; |  | ||||||
| 				string firstWord = content.substr(0, content.find_first_of(" \t")); |  | ||||||
| 
 |  | ||||||
| 				if (returnTagsVisited > function->returnParameters().size()) |  | ||||||
| 					m_errorReporter.docstringParsingError( |  | ||||||
| 						2604_error, |  | ||||||
| 						_node.documentation()->location(), |  | ||||||
| 						"Documentation tag \"@" + tagName + " " + tagValue.content + "\"" + |  | ||||||
| 						" exceeds the number of return parameters." |  | ||||||
| 					); |  | ||||||
| 				else |  | ||||||
| 				{ |  | ||||||
| 					auto parameter = function->returnParameters().at(returnTagsVisited - 1); |  | ||||||
| 					if (!parameter->name().empty() && parameter->name() != firstWord) |  | ||||||
| 						m_errorReporter.docstringParsingError( |  | ||||||
| 							5856_error, |  | ||||||
| 							_node.documentation()->location(), |  | ||||||
| 							"Documentation tag \"@" + tagName + " " + tagValue.content + "\"" + |  | ||||||
| 							" does not contain the name of its return parameter." |  | ||||||
| 						); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -37,6 +37,9 @@ class DocStringTagParser: private ASTConstVisitor | |||||||
| public: | public: | ||||||
| 	explicit DocStringTagParser(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} | 	explicit DocStringTagParser(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} | ||||||
| 	bool parseDocStrings(SourceUnit const& _sourceUnit); | 	bool parseDocStrings(SourceUnit const& _sourceUnit); | ||||||
|  | 	/// Validate the parsed doc strings, requires parseDocStrings() and the
 | ||||||
|  | 	/// DeclarationTypeChecker to have run.
 | ||||||
|  | 	bool validateDocStringsUsingTypes(SourceUnit const& _sourceUnit); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	bool visit(ContractDefinition const& _contract) override; | 	bool visit(ContractDefinition const& _contract) override; | ||||||
|  | |||||||
| @ -416,11 +416,6 @@ bool CompilerStack::analyze() | |||||||
| 			if (source->ast && !syntaxChecker.checkSyntax(*source->ast)) | 			if (source->ast && !syntaxChecker.checkSyntax(*source->ast)) | ||||||
| 				noErrors = false; | 				noErrors = false; | ||||||
| 
 | 
 | ||||||
| 		DocStringTagParser docStringTagParser(m_errorReporter); |  | ||||||
| 		for (Source const* source: m_sourceOrder) |  | ||||||
| 			if (source->ast && !docStringTagParser.parseDocStrings(*source->ast)) |  | ||||||
| 				noErrors = false; |  | ||||||
| 
 |  | ||||||
| 		m_globalContext = make_shared<GlobalContext>(); | 		m_globalContext = make_shared<GlobalContext>(); | ||||||
| 		// We need to keep the same resolver during the whole process.
 | 		// We need to keep the same resolver during the whole process.
 | ||||||
| 		NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter); | 		NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter); | ||||||
| @ -437,6 +432,12 @@ bool CompilerStack::analyze() | |||||||
| 
 | 
 | ||||||
| 		resolver.warnHomonymDeclarations(); | 		resolver.warnHomonymDeclarations(); | ||||||
| 
 | 
 | ||||||
|  | 		DocStringTagParser docStringTagParser(m_errorReporter); | ||||||
|  | 		for (Source const* source: m_sourceOrder) | ||||||
|  | 			if (source->ast && !docStringTagParser.parseDocStrings(*source->ast)) | ||||||
|  | 				noErrors = false; | ||||||
|  | 
 | ||||||
|  | 		// Requires DocStringTagParser
 | ||||||
| 		for (Source const* source: m_sourceOrder) | 		for (Source const* source: m_sourceOrder) | ||||||
| 			if (source->ast && !resolver.resolveNamesAndTypes(*source->ast)) | 			if (source->ast && !resolver.resolveNamesAndTypes(*source->ast)) | ||||||
| 				return false; | 				return false; | ||||||
| @ -446,6 +447,11 @@ bool CompilerStack::analyze() | |||||||
| 			if (source->ast && !declarationTypeChecker.check(*source->ast)) | 			if (source->ast && !declarationTypeChecker.check(*source->ast)) | ||||||
| 				return false; | 				return false; | ||||||
| 
 | 
 | ||||||
|  | 		// Requires DeclarationTypeChecker to have run
 | ||||||
|  | 		for (Source const* source: m_sourceOrder) | ||||||
|  | 			if (source->ast && !docStringTagParser.validateDocStringsUsingTypes(*source->ast)) | ||||||
|  | 				noErrors = false; | ||||||
|  | 
 | ||||||
| 		// Next, we check inheritance, overrides, function collisions and other things at
 | 		// Next, we check inheritance, overrides, function collisions and other things at
 | ||||||
| 		// contract or function level.
 | 		// contract or function level.
 | ||||||
| 		// This also calculates whether a contract is abstract, which is needed by the
 | 		// This also calculates whether a contract is abstract, which is needed by the
 | ||||||
|  | |||||||
| @ -328,6 +328,73 @@ BOOST_AUTO_TEST_CASE(public_state_variable) | |||||||
| 	checkNatspec(sourceCode, "test", userDoc, true); | 	checkNatspec(sourceCode, "test", userDoc, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(public_state_variable_struct) | ||||||
|  | { | ||||||
|  | 	char const* sourceCode = R"( | ||||||
|  | 		contract Bank { | ||||||
|  | 			struct Coin { | ||||||
|  | 				string observeGraphicURL; | ||||||
|  | 				string reverseGraphicURL; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			/// @notice Get the n-th coin I own
 | ||||||
|  | 			/// @return observeGraphicURL Front pic
 | ||||||
|  | 			/// @return reverseGraphicURL Back pic
 | ||||||
|  | 			Coin[] public coinStack; | ||||||
|  | 		} | ||||||
|  | 	)"; | ||||||
|  | 
 | ||||||
|  | 	char const* devDoc = R"R( | ||||||
|  | 	{ | ||||||
|  | 		"methods" : {}, | ||||||
|  | 		"stateVariables" : | ||||||
|  | 		{ | ||||||
|  | 			"coinStack" : | ||||||
|  | 			{ | ||||||
|  | 				"returns" : | ||||||
|  | 				{ | ||||||
|  | 					"observeGraphicURL" : "Front pic", | ||||||
|  | 					"reverseGraphicURL" : "Back pic" | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	)R"; | ||||||
|  | 	checkNatspec(sourceCode, "Bank", devDoc, false); | ||||||
|  | 
 | ||||||
|  | 	char const* userDoc = R"R( | ||||||
|  | 	{ | ||||||
|  | 		"methods" : | ||||||
|  | 		{ | ||||||
|  | 			"coinStack(uint256)" : | ||||||
|  | 			{ | ||||||
|  | 				"notice": "Get the n-th coin I own" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	)R"; | ||||||
|  | 	checkNatspec(sourceCode, "Bank", userDoc, true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(public_state_variable_struct_repeated) | ||||||
|  | { | ||||||
|  | 	char const* sourceCode = R"( | ||||||
|  | 		contract Bank { | ||||||
|  | 			struct Coin { | ||||||
|  | 				string obverseGraphicURL; | ||||||
|  | 				string reverseGraphicURL; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			/// @notice Get the n-th coin I own
 | ||||||
|  | 			/// @return obverseGraphicURL Front pic
 | ||||||
|  | 			/// @return obverseGraphicURL Front pic
 | ||||||
|  | 			Coin[] public coinStack; | ||||||
|  | 		} | ||||||
|  | 	)"; | ||||||
|  | 
 | ||||||
|  | 	expectNatspecError(sourceCode); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| BOOST_AUTO_TEST_CASE(private_state_variable) | BOOST_AUTO_TEST_CASE(private_state_variable) | ||||||
| { | { | ||||||
| 	char const* sourceCode = R"( | 	char const* sourceCode = R"( | ||||||
| @ -1274,7 +1341,7 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit_variable) | |||||||
| 
 | 
 | ||||||
| 	char const *natspec1 = R"ABCDEF({ | 	char const *natspec1 = R"ABCDEF({ | ||||||
| 		"methods" : {}, | 		"methods" : {}, | ||||||
| 			"stateVariables" : | 		"stateVariables" : | ||||||
| 		{ | 		{ | ||||||
| 			"x" : | 			"x" : | ||||||
| 			{ | 			{ | ||||||
| @ -1340,7 +1407,7 @@ BOOST_AUTO_TEST_CASE(dev_explicit_inherit_variable) | |||||||
| 
 | 
 | ||||||
| 	char const *natspec1 = R"ABCDEF({ | 	char const *natspec1 = R"ABCDEF({ | ||||||
| 		"methods" : {}, | 		"methods" : {}, | ||||||
| 			"stateVariables" : | 		"stateVariables" : | ||||||
| 		{ | 		{ | ||||||
| 			"x" : | 			"x" : | ||||||
| 			{ | 			{ | ||||||
|  | |||||||
| @ -6,4 +6,4 @@ contract test { | |||||||
|   uint public state; |   uint public state; | ||||||
| } | } | ||||||
| // ---- | // ---- | ||||||
| // DocstringParsingError 5256: (18-137): Documentation tag "@return" is only allowed once on state-variables. | // DocstringParsingError 2604: (18-137): Documentation tag "@return returns something" exceeds the number of return parameters. | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user