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