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 <c@ethdev.com>
20  * @date 2015
21  * Parses and analyses the doc strings.
22  * Stores the parsing results in the AST annotations and reports errors.
23  */
24 
25 #include <libsolidity/analysis/DocStringAnalyser.h>
26 
27 #include <libsolidity/ast/AST.h>
28 #include <liblangutil/ErrorReporter.h>
29 
30 #include <boost/algorithm/string.hpp>
31 
32 using namespace std;
33 using namespace solidity;
34 using namespace solidity::langutil;
35 using namespace solidity::frontend;
36 
37 namespace
38 {
39 
copyMissingTags(set<CallableDeclaration const * > const & _baseFunctions,StructurallyDocumentedAnnotation & _target,CallableDeclaration const * _declaration=nullptr)40 void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, StructurallyDocumentedAnnotation& _target, CallableDeclaration const* _declaration = nullptr)
41 {
42 	// Only copy if there is exactly one direct base function.
43 	if (_baseFunctions.size() != 1)
44 		return;
45 
46 	CallableDeclaration const& baseFunction = **_baseFunctions.begin();
47 
48 	auto hasReturnParameter = [](CallableDeclaration const& declaration, size_t _n)
49 	{
50 		return declaration.returnParameterList() &&
51 			declaration.returnParameters().size() > _n;
52 	};
53 
54 	auto& sourceDoc = dynamic_cast<StructurallyDocumentedAnnotation const&>(baseFunction.annotation());
55 
56 	for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();)
57 	{
58 		string const& tag = it->first;
59 		// Don't copy tag "inheritdoc", custom tags or already existing tags
60 		if (tag == "inheritdoc" || _target.docTags.count(tag) || boost::starts_with(tag, "custom"))
61 		{
62 			it++;
63 			continue;
64 		}
65 
66 		size_t n = 0;
67 		// Iterate over all values of the current tag (it's a multimap)
68 		for (auto next = sourceDoc.docTags.upper_bound(tag); it != next; it++, n++)
69 		{
70 			DocTag content = it->second;
71 
72 			// Update the parameter name for @return tags
73 			if (_declaration && tag == "return")
74 			{
75 				size_t docParaNameEndPos = content.content.find_first_of(" \t");
76 				string const docParameterName = content.content.substr(0, docParaNameEndPos);
77 
78 				if (
79 					hasReturnParameter(*_declaration, n) &&
80 					docParameterName != _declaration->returnParameters().at(n)->name()
81 				)
82 				{
83 					bool baseHasNoName =
84 						hasReturnParameter(baseFunction, n) &&
85 						baseFunction.returnParameters().at(n)->name().empty();
86 
87 					string paramName = _declaration->returnParameters().at(n)->name();
88 					content.content =
89 						(paramName.empty() ? "" : std::move(paramName) + " ") + (
90 							string::npos == docParaNameEndPos || baseHasNoName ?
91 							content.content :
92 							content.content.substr(docParaNameEndPos + 1)
93 						);
94 				}
95 			}
96 
97 			_target.docTags.emplace(tag, content);
98 		}
99 	}
100 }
101 
findBaseCallable(set<CallableDeclaration const * > const & _baseFunctions,int64_t _contractId)102 CallableDeclaration const* findBaseCallable(set<CallableDeclaration const*> const& _baseFunctions, int64_t _contractId)
103 {
104 	for (CallableDeclaration const* baseFuncCandidate: _baseFunctions)
105 		if (baseFuncCandidate->annotation().contract->id() == _contractId)
106 			return baseFuncCandidate;
107 		else if (auto callable = findBaseCallable(baseFuncCandidate->annotation().baseFunctions, _contractId))
108 			return callable;
109 
110 	return nullptr;
111 }
112 
parameterNamesEqual(CallableDeclaration const & _a,CallableDeclaration const & _b)113 bool parameterNamesEqual(CallableDeclaration const& _a, CallableDeclaration const& _b)
114 {
115 	return boost::range::equal(_a.parameters(), _b.parameters(), [](auto const& pa, auto const& pb) { return pa->name() == pb->name(); });
116 }
117 
118 }
119 
analyseDocStrings(SourceUnit const & _sourceUnit)120 bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
121 {
122 	auto errorWatcher = m_errorReporter.errorWatcher();
123 	_sourceUnit.accept(*this);
124 	return errorWatcher.ok();
125 }
126 
visit(FunctionDefinition const & _function)127 bool DocStringAnalyser::visit(FunctionDefinition const& _function)
128 {
129 	if (!_function.isConstructor())
130 		handleCallable(_function, _function, _function.annotation());
131 	return true;
132 }
133 
visit(VariableDeclaration const & _variable)134 bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
135 {
136 	if (!_variable.isStateVariable() && !_variable.isFileLevelVariable())
137 		return false;
138 
139 	if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
140 		copyMissingTags({baseFunction}, _variable.annotation());
141 	else if (_variable.annotation().docTags.empty())
142 		copyMissingTags(_variable.annotation().baseFunctions, _variable.annotation());
143 
144 	return false;
145 }
146 
visit(ModifierDefinition const & _modifier)147 bool DocStringAnalyser::visit(ModifierDefinition const& _modifier)
148 {
149 	handleCallable(_modifier, _modifier, _modifier.annotation());
150 
151 	return true;
152 }
153 
visit(EventDefinition const & _event)154 bool DocStringAnalyser::visit(EventDefinition const& _event)
155 {
156 	handleCallable(_event, _event, _event.annotation());
157 
158 	return true;
159 }
160 
visit(ErrorDefinition const & _error)161 bool DocStringAnalyser::visit(ErrorDefinition const& _error)
162 {
163 	handleCallable(_error, _error, _error.annotation());
164 
165 	return true;
166 }
167 
handleCallable(CallableDeclaration const & _callable,StructurallyDocumented const & _node,StructurallyDocumentedAnnotation & _annotation)168 void DocStringAnalyser::handleCallable(
169 	CallableDeclaration const& _callable,
170 	StructurallyDocumented const& _node,
171 	StructurallyDocumentedAnnotation& _annotation
172 )
173 {
174 	if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation))
175 		copyMissingTags({baseFunction}, _annotation, &_callable);
176 	else if (
177 		_annotation.docTags.empty() &&
178 		_callable.annotation().baseFunctions.size() == 1 &&
179 		parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin())
180 	)
181 		copyMissingTags(_callable.annotation().baseFunctions, _annotation, &_callable);
182 }
183 
resolveInheritDoc(set<CallableDeclaration const * > const & _baseFuncs,StructurallyDocumented const & _node,StructurallyDocumentedAnnotation & _annotation)184 CallableDeclaration const* DocStringAnalyser::resolveInheritDoc(
185 	set<CallableDeclaration const*> const& _baseFuncs,
186 	StructurallyDocumented const& _node,
187 	StructurallyDocumentedAnnotation& _annotation
188 )
189 {
190 	if (_annotation.inheritdocReference == nullptr)
191 		return nullptr;
192 
193 	if (auto const callable = findBaseCallable(_baseFuncs, _annotation.inheritdocReference->id()))
194 		return callable;
195 
196 	m_errorReporter.docstringParsingError(
197 		4682_error,
198 		_node.documentation()->location(),
199 		"Documentation tag @inheritdoc references contract \"" +
200 		_annotation.inheritdocReference->name() +
201 		"\", but the contract does not contain a function that is overridden by this function."
202 	);
203 
204 	return nullptr;
205 }
206