brintos

brintos / llvm-project-archived public Read only

0
0
Text · 11.1 KiB · 8c25e92 Raw
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