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