1 | //===--- DanglingHandleCheck.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 "DanglingHandleCheck.h" |
10 | #include "../utils/Matchers.h" |
11 | #include "../utils/OptionsUtils.h" |
12 | #include "clang/AST/ASTContext.h" |
13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
14 | |
15 | using namespace clang::ast_matchers; |
16 | using namespace clang::tidy::matchers; |
17 | |
18 | namespace clang::tidy::bugprone { |
19 | |
20 | namespace { |
21 | |
22 | ast_matchers::internal::BindableMatcher<Stmt> |
23 | handleFrom(const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle, |
24 | const ast_matchers::internal::Matcher<Expr> &Arg) { |
25 | return expr( |
26 | anyOf(cxxConstructExpr(hasDeclaration(InnerMatcher: cxxMethodDecl(ofClass(InnerMatcher: IsAHandle))), |
27 | hasArgument(N: 0, InnerMatcher: Arg)), |
28 | cxxMemberCallExpr(hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType( |
29 | hasDeclaration(InnerMatcher: cxxRecordDecl(IsAHandle))))), |
30 | callee(InnerMatcher: memberExpr(member(InnerMatcher: cxxConversionDecl()))), |
31 | on(InnerMatcher: Arg)))); |
32 | } |
33 | |
34 | ast_matchers::internal::Matcher<Stmt> handleFromTemporaryValue( |
35 | const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) { |
36 | |
37 | const auto TemporaryExpr = anyOf( |
38 | cxxBindTemporaryExpr(), |
39 | cxxFunctionalCastExpr( |
40 | hasCastKind(Kind: CK_ConstructorConversion), |
41 | hasSourceExpression(InnerMatcher: ignoringParenImpCasts(InnerMatcher: cxxBindTemporaryExpr())))); |
42 | // If a ternary operator returns a temporary value, then both branches hold a |
43 | // temporary value. If one of them is not a temporary then it must be copied |
44 | // into one to satisfy the type of the operator. |
45 | const auto TemporaryTernary = conditionalOperator( |
46 | hasTrueExpression(InnerMatcher: ignoringParenImpCasts(InnerMatcher: TemporaryExpr)), |
47 | hasFalseExpression(InnerMatcher: ignoringParenImpCasts(InnerMatcher: TemporaryExpr))); |
48 | |
49 | return handleFrom(IsAHandle, Arg: anyOf(TemporaryExpr, TemporaryTernary)); |
50 | } |
51 | |
52 | ast_matchers::internal::Matcher<RecordDecl> isASequence() { |
53 | return hasAnyName("::std::deque" , "::std::forward_list" , "::std::list" , |
54 | "::std::vector" ); |
55 | } |
56 | |
57 | ast_matchers::internal::Matcher<RecordDecl> isASet() { |
58 | return hasAnyName("::std::set" , "::std::multiset" , "::std::unordered_set" , |
59 | "::std::unordered_multiset" ); |
60 | } |
61 | |
62 | ast_matchers::internal::Matcher<RecordDecl> isAMap() { |
63 | return hasAnyName("::std::map" , "::std::multimap" , "::std::unordered_map" , |
64 | "::std::unordered_multimap" ); |
65 | } |
66 | |
67 | ast_matchers::internal::BindableMatcher<Stmt> makeContainerMatcher( |
68 | const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) { |
69 | // This matcher could be expanded to detect: |
70 | // - Constructors: eg. vector<string_view>(3, string("A")); |
71 | // - emplace*(): This requires a different logic to determine that |
72 | // the conversion will happen inside the container. |
73 | // - map's insert: This requires detecting that the pair conversion triggers |
74 | // the bug. A little more complicated than what we have now. |
75 | return callExpr( |
76 | hasAnyArgument( |
77 | InnerMatcher: ignoringParenImpCasts(InnerMatcher: handleFromTemporaryValue(IsAHandle))), |
78 | anyOf( |
79 | // For sequences: assign, push_back, resize. |
80 | cxxMemberCallExpr( |
81 | callee(InnerMatcher: functionDecl(hasAnyName("assign" , "push_back" , "resize" ))), |
82 | on(InnerMatcher: expr(hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
83 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: recordDecl(isASequence())))))))), |
84 | // For sequences and sets: insert. |
85 | cxxMemberCallExpr(callee(InnerMatcher: functionDecl(hasName(Name: "insert" ))), |
86 | on(InnerMatcher: expr(hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
87 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: recordDecl( |
88 | anyOf(isASequence(), isASet()))))))))), |
89 | // For maps: operator[]. |
90 | cxxOperatorCallExpr(callee(InnerMatcher: cxxMethodDecl(ofClass(InnerMatcher: isAMap()))), |
91 | hasOverloadedOperatorName(Name: "[]" )))); |
92 | } |
93 | |
94 | } // anonymous namespace |
95 | |
96 | DanglingHandleCheck::DanglingHandleCheck(StringRef Name, |
97 | ClangTidyContext *Context) |
98 | : ClangTidyCheck(Name, Context), |
99 | HandleClasses(utils::options::parseStringList(Option: Options.get( |
100 | LocalName: "HandleClasses" , |
101 | Default: "std::basic_string_view;std::experimental::basic_string_view" ))), |
102 | IsAHandle(cxxRecordDecl(hasAnyName(HandleClasses)).bind(ID: "handle" )) {} |
103 | |
104 | void DanglingHandleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
105 | Options.store(Options&: Opts, LocalName: "HandleClasses" , |
106 | Value: utils::options::serializeStringList(Strings: HandleClasses)); |
107 | } |
108 | |
109 | void DanglingHandleCheck::registerMatchersForVariables(MatchFinder *Finder) { |
110 | const auto ConvertedHandle = handleFromTemporaryValue(IsAHandle); |
111 | |
112 | // Find 'Handle foo(ReturnsAValue());', 'Handle foo = ReturnsAValue();' |
113 | Finder->addMatcher( |
114 | NodeMatch: varDecl(hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
115 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: cxxRecordDecl(IsAHandle))))), |
116 | unless(parmVarDecl()), |
117 | hasInitializer( |
118 | InnerMatcher: exprWithCleanups(ignoringElidableConstructorCall(InnerMatcher: has( |
119 | ignoringParenImpCasts(InnerMatcher: ConvertedHandle)))) |
120 | .bind(ID: "bad_stmt" ))), |
121 | Action: this); |
122 | |
123 | // Find 'foo = ReturnsAValue(); // foo is Handle' |
124 | Finder->addMatcher( |
125 | NodeMatch: traverse(TK: TK_AsIs, |
126 | InnerMatcher: cxxOperatorCallExpr(callee(InnerMatcher: cxxMethodDecl(ofClass(InnerMatcher: IsAHandle))), |
127 | hasOverloadedOperatorName(Name: "=" ), |
128 | hasArgument(N: 1, InnerMatcher: ConvertedHandle)) |
129 | .bind(ID: "bad_stmt" )), |
130 | Action: this); |
131 | |
132 | // Container insertions that will dangle. |
133 | Finder->addMatcher( |
134 | NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: makeContainerMatcher(IsAHandle).bind(ID: "bad_stmt" )), |
135 | Action: this); |
136 | } |
137 | |
138 | void DanglingHandleCheck::registerMatchersForReturn(MatchFinder *Finder) { |
139 | // Return a local. |
140 | Finder->addMatcher( |
141 | NodeMatch: traverse(TK: TK_AsIs, |
142 | InnerMatcher: returnStmt( |
143 | // The AST contains two constructor calls: |
144 | // 1. Value to Handle conversion. |
145 | // 2. Handle copy construction (elided in C++17+). |
146 | // We have to match both. |
147 | has(ignoringImplicit(InnerMatcher: ignoringElidableConstructorCall( |
148 | InnerMatcher: ignoringImplicit(InnerMatcher: handleFrom( |
149 | IsAHandle, |
150 | Arg: declRefExpr(to(InnerMatcher: varDecl( |
151 | // Is function scope ... |
152 | hasAutomaticStorageDuration(), |
153 | // ... and it is a local array or Value. |
154 | anyOf(hasType(InnerMatcher: arrayType()), |
155 | hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
156 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: recordDecl( |
157 | unless(IsAHandle))))))))))))))), |
158 | // Temporary fix for false positives inside lambdas. |
159 | unless(hasAncestor(lambdaExpr()))) |
160 | .bind(ID: "bad_stmt" )), |
161 | Action: this); |
162 | |
163 | // Return a temporary. |
164 | Finder->addMatcher( |
165 | NodeMatch: traverse(TK: TK_AsIs, |
166 | InnerMatcher: returnStmt(has(exprWithCleanups(ignoringElidableConstructorCall( |
167 | InnerMatcher: has(ignoringParenImpCasts( |
168 | InnerMatcher: handleFromTemporaryValue(IsAHandle))))))) |
169 | .bind(ID: "bad_stmt" )), |
170 | Action: this); |
171 | } |
172 | |
173 | void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) { |
174 | registerMatchersForVariables(Finder); |
175 | registerMatchersForReturn(Finder); |
176 | } |
177 | |
178 | void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) { |
179 | auto *Handle = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "handle" ); |
180 | diag(Loc: Result.Nodes.getNodeAs<Stmt>(ID: "bad_stmt" )->getBeginLoc(), |
181 | Description: "%0 outlives its value" ) |
182 | << Handle->getQualifiedNameAsString(); |
183 | } |
184 | |
185 | } // namespace clang::tidy::bugprone |
186 | |