161 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 "OptionalValueConversionCheck.h"10#include "../utils/LexerUtils.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 <array>17 18using namespace clang::ast_matchers;19using clang::ast_matchers::internal::Matcher;20 21namespace clang::tidy::bugprone {22 23namespace {24 25AST_MATCHER_P(QualType, hasCleanType, Matcher<QualType>, InnerMatcher) {26 return InnerMatcher.matches(27 Node.getNonReferenceType().getUnqualifiedType().getCanonicalType(),28 Finder, Builder);29}30 31constexpr std::array<StringRef, 2> MakeSmartPtrList{32 "::std::make_unique",33 "::std::make_shared",34};35constexpr StringRef MakeOptional = "::std::make_optional";36 37} // namespace38 39OptionalValueConversionCheck::OptionalValueConversionCheck(40 StringRef Name, ClangTidyContext *Context)41 : ClangTidyCheck(Name, Context),42 OptionalTypes(utils::options::parseStringList(43 Options.get("OptionalTypes",44 "::std::optional;::absl::optional;::boost::optional"))),45 ValueMethods(utils::options::parseStringList(46 Options.get("ValueMethods", "::value$;::get$"))) {}47 48std::optional<TraversalKind>49OptionalValueConversionCheck::getCheckTraversalKind() const {50 return TK_AsIs;51}52 53void OptionalValueConversionCheck::registerMatchers(MatchFinder *Finder) {54 auto BindOptionalType = qualType(55 hasCleanType(qualType(hasDeclaration(namedDecl(56 matchers::matchesAnyListedName(OptionalTypes))))57 .bind("optional-type")));58 59 auto EqualsBoundOptionalType =60 qualType(hasCleanType(equalsBoundNode("optional-type")));61 62 auto OptionalDerefMatcherImpl = callExpr(63 anyOf(64 cxxOperatorCallExpr(hasOverloadedOperatorName("*"),65 hasUnaryOperand(hasType(EqualsBoundOptionalType)))66 .bind("op-call"),67 cxxMemberCallExpr(thisPointerType(EqualsBoundOptionalType),68 callee(cxxMethodDecl(anyOf(69 hasOverloadedOperatorName("*"),70 matchers::matchesAnyListedName(ValueMethods)))))71 .bind("member-call")),72 hasType(qualType().bind("value-type")));73 74 auto StdMoveCallMatcher =75 callExpr(argumentCountIs(1), callee(functionDecl(hasName("::std::move"))),76 hasArgument(0, ignoringImpCasts(OptionalDerefMatcherImpl)));77 auto OptionalDerefMatcher =78 ignoringImpCasts(anyOf(OptionalDerefMatcherImpl, StdMoveCallMatcher));79 80 Finder->addMatcher(81 expr(anyOf(82 // construct optional83 cxxConstructExpr(argumentCountIs(1), hasType(BindOptionalType),84 hasArgument(0, OptionalDerefMatcher)),85 // known template methods in std86 callExpr(87 argumentCountIs(1),88 anyOf(89 // match std::make_unique std::make_shared90 callee(functionDecl(91 matchers::matchesAnyListedName(MakeSmartPtrList),92 hasTemplateArgument(93 0, refersToType(BindOptionalType)))),94 // match first std::make_optional by limit argument count95 // (1) and template count (1).96 // 1. template< class T > constexpr97 // std::optional<decay_t<T>> make_optional(T&& value);98 // 2. template< class T, class... Args > constexpr99 // std::optional<T> make_optional(Args&&... args);100 callee(functionDecl(templateArgumentCountIs(1),101 hasName(MakeOptional),102 returns(BindOptionalType)))),103 hasArgument(0, OptionalDerefMatcher)),104 callExpr(argumentCountIs(1),105 106 hasArgument(0, OptionalDerefMatcher))),107 unless(anyOf(hasAncestor(typeLoc()),108 hasAncestor(expr(matchers::hasUnevaluatedContext())))))109 .bind("expr"),110 this);111}112 113void OptionalValueConversionCheck::storeOptions(114 ClangTidyOptions::OptionMap &Opts) {115 Options.store(Opts, "OptionalTypes",116 utils::options::serializeStringList(OptionalTypes));117 Options.store(Opts, "ValueMethods",118 utils::options::serializeStringList(ValueMethods));119}120 121void OptionalValueConversionCheck::check(122 const MatchFinder::MatchResult &Result) {123 const auto *MatchedExpr = Result.Nodes.getNodeAs<Expr>("expr");124 const auto *OptionalType = Result.Nodes.getNodeAs<QualType>("optional-type");125 const auto *ValueType = Result.Nodes.getNodeAs<QualType>("value-type");126 127 diag(MatchedExpr->getExprLoc(),128 "conversion from %0 into %1 and back into %0, remove potentially "129 "error-prone optional dereference")130 << *OptionalType << ValueType->getUnqualifiedType();131 132 if (const auto *OperatorExpr =133 Result.Nodes.getNodeAs<CXXOperatorCallExpr>("op-call")) {134 diag(OperatorExpr->getExprLoc(), "remove '*' to silence this warning",135 DiagnosticIDs::Note)136 << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(137 OperatorExpr->getBeginLoc(), OperatorExpr->getExprLoc()));138 return;139 }140 if (const auto *CallExpr =141 Result.Nodes.getNodeAs<CXXMemberCallExpr>("member-call")) {142 const SourceLocation Begin =143 utils::lexer::getPreviousToken(CallExpr->getExprLoc(),144 *Result.SourceManager, getLangOpts())145 .getLocation();146 auto Diag =147 diag(CallExpr->getExprLoc(),148 "remove call to %0 to silence this warning", DiagnosticIDs::Note);149 Diag << CallExpr->getMethodDecl()150 << FixItHint::CreateRemoval(151 CharSourceRange::getTokenRange(Begin, CallExpr->getEndLoc()));152 if (const auto *Member =153 llvm::dyn_cast<MemberExpr>(CallExpr->getCallee()->IgnoreImplicit());154 Member && Member->isArrow())155 Diag << FixItHint::CreateInsertion(CallExpr->getBeginLoc(), "*");156 return;157 }158}159 160} // namespace clang::tidy::bugprone161