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