Bug 1452883 [wpt PR 10394] - Stringify RequestInit.body, a=testonly
[gecko.git] / build / clang-plugin / DanglingOnTemporaryChecker.cpp
blob45220e4e4223a94a4f6dda5ea569763bf74539b9
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,
16 cxxMethodDecl(
17 // which is marked as no dangling on temporaries,
18 noDanglingOnTemporaries(),
20 // and which is && ref-qualified.
21 isRValueRefQualified(),
23 decl().bind("invalidMethodRefQualified")),
24 this);
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(),
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")),
39 this);
41 //////////////////
42 // Main checker //
43 //////////////////
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")))));
75 AstMatcher->addMatcher(
76 // This is a matcher on a method call,
77 cxxMemberCallExpr(
78 // which is in first party code,
79 isFirstParty(),
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.
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")),
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);
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 "
118 "return a pointer";
120 if (auto InvalidRefQualified =
121 Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodRefQualified")) {
122 diag(InvalidRefQualified->getLocation(), ErrorInvalidRefQualified,
123 DiagnosticIDs::Error);
124 return;
127 if (auto InvalidPointer =
128 Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodPointer")) {
129 diag(InvalidPointer->getLocation(), ErrorInvalidPointer,
130 DiagnosticIDs::Error);
131 return;
134 //////////////////
135 // Main checker //
136 //////////////////
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) {
149 return;
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");
163 // Just in case.
164 if (!MemberCall) {
165 return;
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;
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);
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;
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;
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
211 // diagnostic.
212 if (!EscapeStmt || !EscapeDecl) {
213 return;
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();
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();
241 } else {
242 return;
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();