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