Coverage Report

Created: 2026-07-13 07:08

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