275 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "ConstCorrectnessCheck.h"10#include "../utils/FixItHintUtils.h"11#include "../utils/Matchers.h"12#include "../utils/OptionsUtils.h"13#include "clang/AST/ASTContext.h"14#include "clang/ASTMatchers/ASTMatchFinder.h"15#include "clang/ASTMatchers/ASTMatchers.h"16#include <cassert>17 18using namespace clang::ast_matchers;19 20namespace clang::tidy::misc {21 22namespace {23// FIXME: This matcher exists in some other code-review as well.24// It should probably move to ASTMatchers.25AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); }26AST_MATCHER_P(DeclStmt, containsAnyDeclaration,27 ast_matchers::internal::Matcher<Decl>, InnerMatcher) {28 return ast_matchers::internal::matchesFirstInPointerRange(29 InnerMatcher, Node.decl_begin(), Node.decl_end(), Finder,30 Builder) != Node.decl_end();31}32AST_MATCHER(ReferenceType, isSpelledAsLValue) {33 return Node.isSpelledAsLValue();34}35AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }36} // namespace37 38ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,39 ClangTidyContext *Context)40 : ClangTidyCheck(Name, Context),41 AnalyzePointers(Options.get("AnalyzePointers", true)),42 AnalyzeReferences(Options.get("AnalyzeReferences", true)),43 AnalyzeValues(Options.get("AnalyzeValues", true)),44 45 WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),46 WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),47 48 TransformPointersAsPointers(49 Options.get("TransformPointersAsPointers", true)),50 TransformPointersAsValues(51 Options.get("TransformPointersAsValues", false)),52 TransformReferences(Options.get("TransformReferences", true)),53 TransformValues(Options.get("TransformValues", true)),54 55 AllowedTypes(56 utils::options::parseStringList(Options.get("AllowedTypes", ""))) {57 if (AnalyzeValues == false && AnalyzeReferences == false &&58 AnalyzePointers == false)59 this->configurationDiag(60 "The check 'misc-const-correctness' will not "61 "perform any analysis because 'AnalyzeValues', "62 "'AnalyzeReferences' and 'AnalyzePointers' are false.");63}64 65void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {66 Options.store(Opts, "AnalyzePointers", AnalyzePointers);67 Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);68 Options.store(Opts, "AnalyzeValues", AnalyzeValues);69 70 Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);71 Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);72 73 Options.store(Opts, "TransformPointersAsPointers",74 TransformPointersAsPointers);75 Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);76 Options.store(Opts, "TransformReferences", TransformReferences);77 Options.store(Opts, "TransformValues", TransformValues);78 79 Options.store(Opts, "AllowedTypes",80 utils::options::serializeStringList(AllowedTypes));81}82 83void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {84 const auto ConstType =85 hasType(qualType(isConstQualified(),86 // pointee check will check the constness of pointer87 unless(pointerType())));88 89 const auto ConstReference = hasType(references(isConstQualified()));90 const auto RValueReference = hasType(91 referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));92 93 const auto TemplateType = anyOf(94 hasType(hasCanonicalType(templateTypeParmType())),95 hasType(substTemplateTypeParmType()), hasType(isDependentType()),96 // References to template types, their substitutions or typedefs to97 // template types need to be considered as well.98 hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))),99 hasType(referenceType(pointee(substTemplateTypeParmType()))));100 101 auto AllowedTypeDecl = namedDecl(102 anyOf(matchers::matchesAnyListedName(AllowedTypes), usingShadowDecl()));103 104 const auto AllowedType = hasType(qualType(105 anyOf(hasDeclaration(AllowedTypeDecl), references(AllowedTypeDecl),106 pointerType(pointee(hasDeclaration(AllowedTypeDecl))))));107 108 const auto AutoTemplateType = varDecl(109 anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),110 hasType(pointerType(pointee(autoType())))));111 112 const auto FunctionPointerRef =113 hasType(hasCanonicalType(referenceType(pointee(functionType()))));114 115 // Match local variables which could be 'const' if not modified later.116 // Example: `int i = 10` would match `int i`.117 const auto LocalValDecl = varDecl(118 isLocal(), hasInitializer(anything()),119 unless(anyOf(ConstType, ConstReference, TemplateType,120 hasInitializer(isInstantiationDependent()), AutoTemplateType,121 RValueReference, FunctionPointerRef,122 hasType(cxxRecordDecl(isLambda())), isImplicit(),123 AllowedType)));124 125 // Match the function scope for which the analysis of all local variables126 // shall be run.127 const auto FunctionScope =128 functionDecl(129 hasBody(stmt(forEachDescendant(130 declStmt(containsAnyDeclaration(131 LocalValDecl.bind("local-value")),132 unless(has(decompositionDecl())))133 .bind("decl-stmt")))134 .bind("scope")))135 .bind("function-decl");136 137 Finder->addMatcher(FunctionScope, this);138}139 140/// Classify for a variable in what the Const-Check is interested.141enum class VariableCategory { Value, Reference, Pointer };142 143void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {144 const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");145 const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");146 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");147 const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");148 // It can not be guaranteed that the variable is declared isolated,149 // therefore a transformation might effect the other variables as well and150 // be incorrect.151 const bool CanBeFixIt = VarDeclStmt != nullptr && VarDeclStmt->isSingleDecl();152 153 /// If the variable was declared in a template it might be analyzed multiple154 /// times. Only one of those instantiations shall emit a warning. NOTE: This155 /// shall only deduplicate warnings for variables that are not instantiation156 /// dependent. Variables like 'int x = 42;' in a template that can become157 /// const emit multiple warnings otherwise.158 bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();159 if (IsNormalVariableInTemplate &&160 TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))161 return;162 163 VariableCategory VC = VariableCategory::Value;164 const QualType VT = Variable->getType();165 if (VT->isReferenceType()) {166 VC = VariableCategory::Reference;167 } else if (VT->isPointerType()) {168 VC = VariableCategory::Pointer;169 } else if (const auto *ArrayT = dyn_cast<ArrayType>(VT)) {170 if (ArrayT->getElementType()->isPointerType())171 VC = VariableCategory::Pointer;172 }173 174 auto CheckValue = [&]() {175 // The scope is only registered if the analysis shall be run.176 registerScope(LocalScope, Result.Context);177 178 // Offload const-analysis to utility function.179 if (ScopesCache[LocalScope]->isMutated(Variable))180 return;181 182 auto Diag = diag(Variable->getBeginLoc(),183 "variable %0 of type %1 can be declared 'const'")184 << Variable << VT;185 if (IsNormalVariableInTemplate)186 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());187 if (!CanBeFixIt)188 return;189 using namespace utils::fixit;190 if (VC == VariableCategory::Value && TransformValues) {191 Diag << addQualifierToVarDecl(*Variable, *Result.Context,192 Qualifiers::Const, QualifierTarget::Value,193 QualifierPolicy::Right);194 // FIXME: Add '{}' for default initialization if no user-defined default195 // constructor exists and there is no initializer.196 return;197 }198 199 if (VC == VariableCategory::Reference && TransformReferences) {200 Diag << addQualifierToVarDecl(*Variable, *Result.Context,201 Qualifiers::Const, QualifierTarget::Value,202 QualifierPolicy::Right);203 return;204 }205 206 if (VC == VariableCategory::Pointer && TransformPointersAsValues) {207 Diag << addQualifierToVarDecl(*Variable, *Result.Context,208 Qualifiers::Const, QualifierTarget::Value,209 QualifierPolicy::Right);210 return;211 }212 };213 214 auto CheckPointee = [&]() {215 assert(VC == VariableCategory::Pointer);216 registerScope(LocalScope, Result.Context);217 if (ScopesCache[LocalScope]->isPointeeMutated(Variable))218 return;219 auto Diag =220 diag(Variable->getBeginLoc(),221 "pointee of variable %0 of type %1 can be declared 'const'")222 << Variable << VT;223 if (IsNormalVariableInTemplate)224 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());225 if (!CanBeFixIt)226 return;227 using namespace utils::fixit;228 if (TransformPointersAsPointers) {229 Diag << addQualifierToVarDecl(*Variable, *Result.Context,230 Qualifiers::Const, QualifierTarget::Pointee,231 QualifierPolicy::Right);232 }233 };234 235 // Each variable can only be in one category: Value, Pointer, Reference.236 // Analysis can be controlled for every category.237 if (VC == VariableCategory::Value && AnalyzeValues) {238 CheckValue();239 return;240 }241 if (VC == VariableCategory::Reference && AnalyzeReferences) {242 if (VT->getPointeeType()->isPointerType() && !WarnPointersAsValues)243 return;244 CheckValue();245 return;246 }247 if (VC == VariableCategory::Pointer && AnalyzePointers) {248 if (WarnPointersAsValues && !VT.isConstQualified())249 CheckValue();250 if (WarnPointersAsPointers) {251 if (const auto *PT = dyn_cast<PointerType>(VT)) {252 if (!PT->getPointeeType().isConstQualified() &&253 !PT->getPointeeType()->isFunctionType())254 CheckPointee();255 }256 if (const auto *AT = dyn_cast<ArrayType>(VT)) {257 if (!AT->getElementType().isConstQualified()) {258 assert(AT->getElementType()->isPointerType());259 CheckPointee();260 }261 }262 }263 return;264 }265}266 267void ConstCorrectnessCheck::registerScope(const Stmt *LocalScope,268 ASTContext *Context) {269 auto &Analyzer = ScopesCache[LocalScope];270 if (!Analyzer)271 Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);272}273 274} // namespace clang::tidy::misc275