1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
//===--- ConstCorrectnessCheck.cpp - clang-tidy -----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ConstCorrectnessCheck.h"
#include "../utils/FixItHintUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace misc {
namespace {
// FIXME: This matcher exists in some other code-review as well.
// It should probably move to ASTMatchers.
AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); }
AST_MATCHER_P(DeclStmt, containsAnyDeclaration,
ast_matchers::internal::Matcher<Decl>, InnerMatcher) {
return ast_matchers::internal::matchesFirstInPointerRange(
InnerMatcher, Node.decl_begin(), Node.decl_end(), Finder,
Builder) != Node.decl_end();
}
AST_MATCHER(ReferenceType, isSpelledAsLValue) {
return Node.isSpelledAsLValue();
}
AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
} // namespace
ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AnalyzeValues(Options.get("AnalyzeValues", true)),
AnalyzeReferences(Options.get("AnalyzeReferences", true)),
WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
TransformValues(Options.get("TransformValues", true)),
TransformReferences(Options.get("TransformReferences", true)),
TransformPointersAsValues(
Options.get("TransformPointersAsValues", false)) {
if (AnalyzeValues == false && AnalyzeReferences == false)
this->configurationDiag(
"The check 'misc-const-correctness' will not "
"perform any analysis because both 'AnalyzeValues' and "
"'AnalyzeReferences' are false.");
}
void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
Options.store(Opts, "TransformValues", TransformValues);
Options.store(Opts, "TransformReferences", TransformReferences);
Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
}
void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
const auto ConstType = hasType(isConstQualified());
const auto ConstReference = hasType(references(isConstQualified()));
const auto RValueReference = hasType(
referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
const auto TemplateType = anyOf(
hasType(hasCanonicalType(templateTypeParmType())),
hasType(substTemplateTypeParmType()), hasType(isDependentType()),
// References to template types, their substitutions or typedefs to
// template types need to be considered as well.
hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))),
hasType(referenceType(pointee(substTemplateTypeParmType()))));
const auto AutoTemplateType = varDecl(
anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
hasType(pointerType(pointee(autoType())))));
const auto FunctionPointerRef =
hasType(hasCanonicalType(referenceType(pointee(functionType()))));
// Match local variables which could be 'const' if not modified later.
// Example: `int i = 10` would match `int i`.
const auto LocalValDecl = varDecl(
allOf(isLocal(), hasInitializer(anything()),
unless(anyOf(ConstType, ConstReference, TemplateType,
hasInitializer(isInstantiationDependent()),
AutoTemplateType, RValueReference, FunctionPointerRef,
hasType(cxxRecordDecl(isLambda())), isImplicit()))));
// Match the function scope for which the analysis of all local variables
// shall be run.
const auto FunctionScope =
functionDecl(
hasBody(
compoundStmt(forEachDescendant(
declStmt(containsAnyDeclaration(
LocalValDecl.bind("local-value")),
unless(has(decompositionDecl())))
.bind("decl-stmt")))
.bind("scope")))
.bind("function-decl");
Finder->addMatcher(FunctionScope, this);
}
/// Classify for a variable in what the Const-Check is interested.
enum class VariableCategory { Value, Reference, Pointer };
void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
const auto *LocalScope = Result.Nodes.getNodeAs<CompoundStmt>("scope");
const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
/// If the variable was declared in a template it might be analyzed multiple
/// times. Only one of those instantiations shall emit a warning. NOTE: This
/// shall only deduplicate warnings for variables that are not instantiation
/// dependent. Variables like 'int x = 42;' in a template that can become
/// const emit multiple warnings otherwise.
bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
if (IsNormalVariableInTemplate &&
TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
return;
VariableCategory VC = VariableCategory::Value;
if (Variable->getType()->isReferenceType())
VC = VariableCategory::Reference;
if (Variable->getType()->isPointerType())
VC = VariableCategory::Pointer;
if (Variable->getType()->isArrayType()) {
if (const auto *ArrayT = dyn_cast<ArrayType>(Variable->getType())) {
if (ArrayT->getElementType()->isPointerType())
VC = VariableCategory::Pointer;
}
}
// Each variable can only be in one category: Value, Pointer, Reference.
// Analysis can be controlled for every category.
if (VC == VariableCategory::Reference && !AnalyzeReferences)
return;
if (VC == VariableCategory::Reference &&
Variable->getType()->getPointeeType()->isPointerType() &&
!WarnPointersAsValues)
return;
if (VC == VariableCategory::Pointer && !WarnPointersAsValues)
return;
if (VC == VariableCategory::Value && !AnalyzeValues)
return;
// The scope is only registered if the analysis shall be run.
registerScope(LocalScope, Result.Context);
// Offload const-analysis to utility function.
if (ScopesCache[LocalScope]->isMutated(Variable))
return;
auto Diag = diag(Variable->getBeginLoc(),
"variable %0 of type %1 can be declared 'const'")
<< Variable << Variable->getType();
if (IsNormalVariableInTemplate)
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
// It can not be guaranteed that the variable is declared isolated, therefore
// a transformation might effect the other variables as well and be incorrect.
if (VarDeclStmt == nullptr || !VarDeclStmt->isSingleDecl())
return;
using namespace utils::fixit;
if (VC == VariableCategory::Value && TransformValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
DeclSpec::TQ_const, QualifierTarget::Value,
QualifierPolicy::Right);
// FIXME: Add '{}' for default initialization if no user-defined default
// constructor exists and there is no initializer.
return;
}
if (VC == VariableCategory::Reference && TransformReferences) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
DeclSpec::TQ_const, QualifierTarget::Value,
QualifierPolicy::Right);
return;
}
if (VC == VariableCategory::Pointer) {
if (WarnPointersAsValues && TransformPointersAsValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
DeclSpec::TQ_const, QualifierTarget::Value,
QualifierPolicy::Right);
}
return;
}
}
void ConstCorrectnessCheck::registerScope(const CompoundStmt *LocalScope,
ASTContext *Context) {
auto &Analyzer = ScopesCache[LocalScope];
if (!Analyzer)
Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
}
} // namespace misc
} // namespace tidy
} // namespace clang
|