clang-tools  4.0.0
DanglingHandleCheck.cpp
Go to the documentation of this file.
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 misc {
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 cxxConstructExpr(hasDeclaration(cxxMethodDecl(ofClass(IsAHandle))),
29  hasArgument(0, Arg));
30 }
31 
32 ast_matchers::internal::Matcher<Stmt> handleFromTemporaryValue(
33  const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) {
34  // If a ternary operator returns a temporary value, then both branches hold a
35  // temporary value. If one of them is not a temporary then it must be copied
36  // into one to satisfy the type of the operator.
37  const auto TemporaryTernary =
38  conditionalOperator(hasTrueExpression(cxxBindTemporaryExpr()),
39  hasFalseExpression(cxxBindTemporaryExpr()));
40 
41  return handleFrom(IsAHandle, anyOf(cxxBindTemporaryExpr(), TemporaryTernary));
42 }
43 
44 ast_matchers::internal::Matcher<RecordDecl> isASequence() {
45  return hasAnyName("::std::deque", "::std::forward_list", "::std::list",
46  "::std::vector");
47 }
48 
49 ast_matchers::internal::Matcher<RecordDecl> isASet() {
50  return hasAnyName("::std::set", "::std::multiset", "::std::unordered_set",
51  "::std::unordered_multiset");
52 }
53 
54 ast_matchers::internal::Matcher<RecordDecl> isAMap() {
55  return hasAnyName("::std::map", "::std::multimap", "::std::unordered_map",
56  "::std::unordered_multimap");
57 }
58 
59 ast_matchers::internal::BindableMatcher<Stmt> makeContainerMatcher(
60  const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) {
61  // This matcher could be expanded to detect:
62  // - Constructors: eg. vector<string_view>(3, string("A"));
63  // - emplace*(): This requires a different logic to determine that
64  // the conversion will happen inside the container.
65  // - map's insert: This requires detecting that the pair conversion triggers
66  // the bug. A little more complicated than what we have now.
67  return callExpr(
68  hasAnyArgument(
69  ignoringParenImpCasts(handleFromTemporaryValue(IsAHandle))),
70  anyOf(
71  // For sequences: assign, push_back, resize.
72  cxxMemberCallExpr(
73  callee(functionDecl(hasAnyName("assign", "push_back", "resize"))),
74  on(expr(hasType(recordDecl(isASequence()))))),
75  // For sequences and sets: insert.
76  cxxMemberCallExpr(
77  callee(functionDecl(hasName("insert"))),
78  on(expr(hasType(recordDecl(anyOf(isASequence(), isASet())))))),
79  // For maps: operator[].
80  cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(isAMap()))),
81  hasOverloadedOperatorName("[]"))));
82 }
83 
84 } // anonymous namespace
85 
86 DanglingHandleCheck::DanglingHandleCheck(StringRef Name,
88  : ClangTidyCheck(Name, Context),
89  HandleClasses(utils::options::parseStringList(Options.get(
90  "HandleClasses",
91  "std::basic_string_view;std::experimental::basic_string_view"))),
92  IsAHandle(cxxRecordDecl(hasAnyName(std::vector<StringRef>(
93  HandleClasses.begin(), HandleClasses.end())))
94  .bind("handle")) {}
95 
97  Options.store(Opts, "HandleClasses",
99 }
100 
101 void DanglingHandleCheck::registerMatchersForVariables(MatchFinder *Finder) {
102  const auto ConvertedHandle = handleFromTemporaryValue(IsAHandle);
103 
104  // Find 'Handle foo(ReturnsAValue());'
105  Finder->addMatcher(
106  varDecl(hasType(cxxRecordDecl(IsAHandle)),
107  hasInitializer(
108  exprWithCleanups(has(ignoringParenImpCasts(ConvertedHandle)))
109  .bind("bad_stmt"))),
110  this);
111 
112  // Find 'Handle foo = ReturnsAValue();'
113  Finder->addMatcher(
114  varDecl(
115  hasType(cxxRecordDecl(IsAHandle)), unless(parmVarDecl()),
116  hasInitializer(exprWithCleanups(has(ignoringParenImpCasts(handleFrom(
117  IsAHandle, ConvertedHandle))))
118  .bind("bad_stmt"))),
119  this);
120  // Find 'foo = ReturnsAValue(); // foo is Handle'
121  Finder->addMatcher(
122  cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(IsAHandle))),
123  hasOverloadedOperatorName("="),
124  hasArgument(1, ConvertedHandle))
125  .bind("bad_stmt"),
126  this);
127 
128  // Container insertions that will dangle.
129  Finder->addMatcher(makeContainerMatcher(IsAHandle).bind("bad_stmt"), this);
130 }
131 
132 void DanglingHandleCheck::registerMatchersForReturn(MatchFinder *Finder) {
133  // Return a local.
134  Finder->addMatcher(
135  returnStmt(
136  // The AST contains two constructor calls:
137  // 1. Value to Handle conversion.
138  // 2. Handle copy construction.
139  // We have to match both.
140  has(ignoringImplicit(handleFrom(
141  IsAHandle,
142  handleFrom(IsAHandle, declRefExpr(to(varDecl(
143  // Is function scope ...
144  hasAutomaticStorageDuration(),
145  // ... and it is a local array or Value.
146  anyOf(hasType(arrayType()),
147  hasType(recordDecl(
148  unless(IsAHandle))))))))))),
149  // Temporary fix for false positives inside lambdas.
150  unless(hasAncestor(lambdaExpr())))
151  .bind("bad_stmt"),
152  this);
153 
154  // Return a temporary.
155  Finder->addMatcher(
156  returnStmt(
157  has(ignoringParenImpCasts(exprWithCleanups(has(ignoringParenImpCasts(
158  handleFrom(IsAHandle, handleFromTemporaryValue(IsAHandle))))))))
159  .bind("bad_stmt"),
160  this);
161 }
162 
163 void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) {
164  registerMatchersForVariables(Finder);
165  registerMatchersForReturn(Finder);
166 }
167 
168 void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) {
169  auto *Handle = Result.Nodes.getNodeAs<CXXRecordDecl>("handle");
170  diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getLocStart(),
171  "%0 outlives its value")
172  << Handle->getQualifiedNameAsString();
173 }
174 
175 } // namespace misc
176 } // namespace tidy
177 } // namespace clang
const std::string Name
Definition: USRFinder.cpp:164
std::string serializeStringList(ArrayRef< std::string > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:262
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidy.cpp:436
std::map< std::string, std::string > OptionMap
ClangTidyContext & Context
Definition: ClangTidy.cpp:87
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidy.cpp:403
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
const NamedDecl * Result
Definition: USRFinder.cpp:162