Coverage Report

Created: 2022-08-24 06:55

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