1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "DanglingOnTemporaryChecker.h"
6 #include "CustomMatchers.h"
7 #include "VariableUsageHelpers.h"
9 void DanglingOnTemporaryChecker::registerMatchers(MatchFinder
*AstMatcher
) {
10 ////////////////////////////////////////
11 // Quick annotation conflict checkers //
12 ////////////////////////////////////////
14 AstMatcher
->addMatcher(
15 // This is a matcher on a method declaration,
17 // which is marked as no dangling on temporaries,
18 noDanglingOnTemporaries(),
20 // and which is && ref-qualified.
21 isRValueRefQualified(),
23 decl().bind("invalidMethodRefQualified")),
26 AstMatcher
->addMatcher(
27 // This is a matcher on a method declaration,
29 // which is marked as no dangling on temporaries,
30 noDanglingOnTemporaries(),
32 // which returns a primitive type,
33 returns(builtinType()),
35 // and which doesn't return a pointer.
36 unless(returns(pointerType())),
38 decl().bind("invalidMethodPointer")),
45 auto hasParentCall
= hasParent(expr(
46 anyOf(cxxOperatorCallExpr(
47 // If we're in a lamda, we may have an operator call expression
48 // ancestor in the AST, but the temporary we're matching
49 // against is not going to have the same lifetime as the
51 unless(has(expr(ignoreTrivials(lambdaExpr())))),
52 expr().bind("parentOperatorCallExpr")),
54 // If we're in a lamda, we may have a call expression
55 // ancestor in the AST, but the temporary we're matching
56 // against is not going to have the same lifetime as the
58 unless(has(expr(ignoreTrivials(lambdaExpr())))),
59 expr().bind("parentCallExpr")),
61 // If we're in a lamda, we may have an objc message expression
62 // ancestor in the AST, but the temporary we're matching
63 // against is not going to have the same lifetime as the
65 unless(has(expr(ignoreTrivials(lambdaExpr())))),
66 expr().bind("parentObjCMessageExpr")),
68 // If we're in a lamda, we may have a construct expression
69 // ancestor in the AST, but the temporary we're matching
70 // against is not going to have the same lifetime as the
72 unless(has(expr(ignoreTrivials(lambdaExpr())))),
73 expr().bind("parentConstructExpr")))));
75 AstMatcher
->addMatcher(
76 // This is a matcher on a method call,
78 // which is in first party code,
81 // and which is performed on a temporary,
82 on(allOf(unless(hasType(pointerType())), isTemporary(),
83 // but which is not `this`.
84 unless(cxxThisExpr()))),
86 // and which is marked as no dangling on temporaries.
87 callee(cxxMethodDecl(noDanglingOnTemporaries())),
89 expr().bind("memberCallExpr"),
91 // We optionally match a parent call expression or a parent construct
92 // expression because using a temporary inside a call is fine as long
93 // as the pointer doesn't escape the function call.
95 // This is the case where the call is the direct parent, so we
96 // know that the member call expression is the argument.
97 allOf(hasParentCall
, expr().bind("parentCallArg")),
99 // This is the case where the call is not the direct parent, so we
100 // get its child to know in which argument tree we are.
101 hasAncestor(expr(hasParentCall
, expr().bind("parentCallArg"))),
102 // To make it optional.
107 void DanglingOnTemporaryChecker::check(const MatchFinder::MatchResult
&Result
) {
108 ///////////////////////////////////////
109 // Quick annotation conflict checker //
110 ///////////////////////////////////////
112 const char *ErrorInvalidRefQualified
= "methods annotated with "
113 "MOZ_NO_DANGLING_ON_TEMPORARIES "
114 "cannot be && ref-qualified";
116 const char *ErrorInvalidPointer
= "methods annotated with "
117 "MOZ_NO_DANGLING_ON_TEMPORARIES must "
120 if (auto InvalidRefQualified
=
121 Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("invalidMethodRefQualified")) {
122 diag(InvalidRefQualified
->getLocation(), ErrorInvalidRefQualified
,
123 DiagnosticIDs::Error
);
127 if (auto InvalidPointer
=
128 Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("invalidMethodPointer")) {
129 diag(InvalidPointer
->getLocation(), ErrorInvalidPointer
,
130 DiagnosticIDs::Error
);
138 const char *Error
= "calling `%0` on a temporary, potentially allowing use "
139 "after free of the raw pointer";
141 const char *EscapeStmtNote
=
142 "the raw pointer escapes the function scope here";
144 const ObjCMessageExpr
*ParentObjCMessageExpr
=
145 Result
.Nodes
.getNodeAs
<ObjCMessageExpr
>("parentObjCMessageExpr");
147 // We don't care about cases in ObjC message expressions.
148 if (ParentObjCMessageExpr
) {
152 const CXXMemberCallExpr
*MemberCall
=
153 Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("memberCallExpr");
155 const CallExpr
*ParentCallExpr
=
156 Result
.Nodes
.getNodeAs
<CallExpr
>("parentCallExpr");
157 const CXXConstructExpr
*ParentConstructExpr
=
158 Result
.Nodes
.getNodeAs
<CXXConstructExpr
>("parentConstructExpr");
159 const CXXOperatorCallExpr
*ParentOperatorCallExpr
=
160 Result
.Nodes
.getNodeAs
<CXXOperatorCallExpr
>("parentOperatorCallExpr");
161 const Expr
*ParentCallArg
= Result
.Nodes
.getNodeAs
<Expr
>("parentCallArg");
168 // If we have a parent call, we check whether or not we escape the function
170 if (ParentOperatorCallExpr
|| ParentCallExpr
|| ParentConstructExpr
) {
172 if (!ParentCallArg
) {
176 // No default constructor so we can't construct it using if/else.
177 auto FunctionEscapeData
=
178 ParentOperatorCallExpr
179 ? escapesFunction(ParentCallArg
, ParentOperatorCallExpr
)
181 ? escapesFunction(ParentCallArg
, ParentCallExpr
)
182 : escapesFunction(ParentCallArg
, ParentConstructExpr
);
184 // If there was an error in the escapesFunction call.
185 if (std::error_code ec
= FunctionEscapeData
.getError()) {
186 // FIXME: For now we ignore the variadic case and just consider that the
187 // argument doesn't escape the function. Same for the case where we can't
188 // find the function declaration or if the function is builtin.
189 if (static_cast<EscapesFunctionError
>(ec
.value()) ==
190 EscapesFunctionError::FunctionIsVariadic
||
191 static_cast<EscapesFunctionError
>(ec
.value()) ==
192 EscapesFunctionError::FunctionDeclNotFound
||
193 static_cast<EscapesFunctionError
>(ec
.value()) ==
194 EscapesFunctionError::FunctionIsBuiltin
) {
198 // We emit the internal checker error and return.
199 diag(MemberCall
->getExprLoc(),
200 std::string(ec
.category().name()) + " error: " + ec
.message(),
201 DiagnosticIDs::Error
);
205 // We deconstruct the function escape data.
206 const Stmt
*EscapeStmt
;
207 const Decl
*EscapeDecl
;
208 std::tie(EscapeStmt
, EscapeDecl
) = *FunctionEscapeData
;
210 // If we didn't escape a parent function, we're done: we don't emit any
212 if (!EscapeStmt
|| !EscapeDecl
) {
216 // We emit the error diagnostic indicating that we are calling the method
218 diag(MemberCall
->getExprLoc(), Error
, DiagnosticIDs::Error
)
219 << MemberCall
->getMethodDecl()->getName()
220 << MemberCall
->getSourceRange();
222 // We indicate the escape statement.
223 diag(EscapeStmt
->getLocStart(), EscapeStmtNote
, DiagnosticIDs::Note
)
224 << EscapeStmt
->getSourceRange();
226 // We build the escape note along with its source range.
227 StringRef EscapeDeclNote
;
228 SourceRange EscapeDeclRange
;
229 if (isa
<ParmVarDecl
>(EscapeDecl
)) {
230 EscapeDeclNote
= "through the parameter declared here";
231 EscapeDeclRange
= EscapeDecl
->getSourceRange();
232 } else if (isa
<VarDecl
>(EscapeDecl
)) {
233 EscapeDeclNote
= "through the variable declared here";
234 EscapeDeclRange
= EscapeDecl
->getSourceRange();
235 } else if (isa
<FieldDecl
>(EscapeDecl
)) {
236 EscapeDeclNote
= "through the field declared here";
237 EscapeDeclRange
= EscapeDecl
->getSourceRange();
238 } else if (auto FuncDecl
= dyn_cast
<FunctionDecl
>(EscapeDecl
)) {
239 EscapeDeclNote
= "through the return value of the function declared here";
240 EscapeDeclRange
= FuncDecl
->getReturnTypeSourceRange();
245 // We emit the declaration note indicating through which decl the argument
247 diag(EscapeDecl
->getLocation(), EscapeDeclNote
, DiagnosticIDs::Note
)
250 // We emit the error diagnostic indicating that we are calling the method
252 diag(MemberCall
->getExprLoc(), Error
, DiagnosticIDs::Error
)
253 << MemberCall
->getMethodDecl()->getName()
254 << MemberCall
->getSourceRange();