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/. */
4 
5 #include "DanglingOnTemporaryChecker.h"
6 #include "CustomMatchers.h"
7 #include "VariableUsageHelpers.h"
8 
registerMatchers(MatchFinder * AstMatcher)9 void DanglingOnTemporaryChecker::registerMatchers(MatchFinder *AstMatcher) {
10   ////////////////////////////////////////
11   // Quick annotation conflict checkers //
12   ////////////////////////////////////////
13 
14   AstMatcher->addMatcher(
15       // This is a matcher on a method declaration,
16       cxxMethodDecl(
17           // which is marked as no dangling on temporaries,
18           noDanglingOnTemporaries(),
19 
20           // and which is && ref-qualified.
21           isRValueRefQualified(),
22 
23           decl().bind("invalidMethodRefQualified")),
24       this);
25 
26   AstMatcher->addMatcher(
27       // This is a matcher on a method declaration,
28       cxxMethodDecl(
29           // which is marked as no dangling on temporaries,
30           noDanglingOnTemporaries(),
31 
32           // which returns a primitive type,
33           returns(builtinType()),
34 
35           // and which doesn't return a pointer.
36           unless(returns(pointerType())),
37 
38           decl().bind("invalidMethodPointer")),
39       this);
40 
41   //////////////////
42   // Main checker //
43   //////////////////
44 
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
50                 // constructor call.
51                 unless(has(expr(ignoreTrivials(lambdaExpr())))),
52                 expr().bind("parentOperatorCallExpr")),
53             callExpr(
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
57                 // function call.
58                 unless(has(expr(ignoreTrivials(lambdaExpr())))),
59                 expr().bind("parentCallExpr")),
60             objcMessageExpr(
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
64                 // function call.
65                 unless(has(expr(ignoreTrivials(lambdaExpr())))),
66                 expr().bind("parentObjCMessageExpr")),
67             cxxConstructExpr(
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
71                 // constructor call.
72                 unless(has(expr(ignoreTrivials(lambdaExpr())))),
73                 expr().bind("parentConstructExpr")))));
74 
75   AstMatcher->addMatcher(
76       // This is a matcher on a method call,
77       cxxMemberCallExpr(
78           // which is in first party code,
79           isFirstParty(),
80 
81           // and which is performed on a temporary,
82           on(allOf(unless(hasType(pointerType())), isTemporary(),
83                    // but which is not `this`.
84                    unless(cxxThisExpr()))),
85 
86           // and which is marked as no dangling on temporaries.
87           callee(cxxMethodDecl(noDanglingOnTemporaries())),
88 
89           expr().bind("memberCallExpr"),
90 
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.
94           anyOf(
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")),
98 
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.
103               anything())),
104       this);
105 }
106 
check(const MatchFinder::MatchResult & Result)107 void DanglingOnTemporaryChecker::check(const MatchFinder::MatchResult &Result) {
108   ///////////////////////////////////////
109   // Quick annotation conflict checker //
110   ///////////////////////////////////////
111 
112   const char *ErrorInvalidRefQualified = "methods annotated with "
113                                          "MOZ_NO_DANGLING_ON_TEMPORARIES "
114                                          "cannot be && ref-qualified";
115 
116   const char *ErrorInvalidPointer = "methods annotated with "
117                                     "MOZ_NO_DANGLING_ON_TEMPORARIES must "
118                                     "return a pointer";
119 
120   if (auto InvalidRefQualified =
121           Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodRefQualified")) {
122     diag(InvalidRefQualified->getLocation(), ErrorInvalidRefQualified,
123          DiagnosticIDs::Error);
124     return;
125   }
126 
127   if (auto InvalidPointer =
128           Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodPointer")) {
129     diag(InvalidPointer->getLocation(), ErrorInvalidPointer,
130          DiagnosticIDs::Error);
131     return;
132   }
133 
134   //////////////////
135   // Main checker //
136   //////////////////
137 
138   const char *Error = "calling `%0` on a temporary, potentially allowing use "
139                       "after free of the raw pointer";
140 
141   const char *EscapeStmtNote =
142       "the raw pointer escapes the function scope here";
143 
144   const ObjCMessageExpr *ParentObjCMessageExpr =
145       Result.Nodes.getNodeAs<ObjCMessageExpr>("parentObjCMessageExpr");
146 
147   // We don't care about cases in ObjC message expressions.
148   if (ParentObjCMessageExpr) {
149     return;
150   }
151 
152   const CXXMemberCallExpr *MemberCall =
153       Result.Nodes.getNodeAs<CXXMemberCallExpr>("memberCallExpr");
154 
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");
162 
163   // Just in case.
164   if (!MemberCall) {
165     return;
166   }
167 
168   // If we have a parent call, we check whether or not we escape the function
169   // being called.
170   if (ParentOperatorCallExpr || ParentCallExpr || ParentConstructExpr) {
171     // Just in case.
172     if (!ParentCallArg) {
173       return;
174     }
175 
176     // No default constructor so we can't construct it using if/else.
177     auto FunctionEscapeData =
178         ParentOperatorCallExpr
179             ? escapesFunction(ParentCallArg, ParentOperatorCallExpr)
180             : ParentCallExpr
181                   ? escapesFunction(ParentCallArg, ParentCallExpr)
182                   : escapesFunction(ParentCallArg, ParentConstructExpr);
183 
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) {
195         return;
196       }
197 
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);
202       return;
203     }
204 
205     // We deconstruct the function escape data.
206     const Stmt *EscapeStmt;
207     const Decl *EscapeDecl;
208     std::tie(EscapeStmt, EscapeDecl) = *FunctionEscapeData;
209 
210     // If we didn't escape a parent function, we're done: we don't emit any
211     // diagnostic.
212     if (!EscapeStmt || !EscapeDecl) {
213       return;
214     }
215 
216     // We emit the error diagnostic indicating that we are calling the method
217     // temporary.
218     diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
219         << MemberCall->getMethodDecl()->getName()
220         << MemberCall->getSourceRange();
221 
222     // We indicate the escape statement.
223     diag(EscapeStmt->getLocStart(), EscapeStmtNote, DiagnosticIDs::Note)
224         << EscapeStmt->getSourceRange();
225 
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();
241     } else {
242       return;
243     }
244 
245     // We emit the declaration note indicating through which decl the argument
246     // escapes.
247     diag(EscapeDecl->getLocation(), EscapeDeclNote, DiagnosticIDs::Note)
248         << EscapeDeclRange;
249   } else {
250     // We emit the error diagnostic indicating that we are calling the method
251     // temporary.
252     diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
253         << MemberCall->getMethodDecl()->getName()
254         << MemberCall->getSourceRange();
255   }
256 }
257