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