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