xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/OptionalValueConversionCheck.cpp (revision ba373a222fe6f65c45a05e9e1114c92580953b79)
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