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