mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
4f5c3e9078
Differential Revision: https://phabricator.services.mozilla.com/D10806 --HG-- extra : moz-landing-system : lando
257 lines
9.8 KiB
C++
257 lines
9.8 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "DanglingOnTemporaryChecker.h"
|
|
#include "CustomMatchers.h"
|
|
#include "VariableUsageHelpers.h"
|
|
|
|
void DanglingOnTemporaryChecker::registerMatchers(MatchFinder *AstMatcher) {
|
|
////////////////////////////////////////
|
|
// Quick annotation conflict checkers //
|
|
////////////////////////////////////////
|
|
|
|
AstMatcher->addMatcher(
|
|
// This is a matcher on a method declaration,
|
|
cxxMethodDecl(
|
|
// which is marked as no dangling on temporaries,
|
|
noDanglingOnTemporaries(),
|
|
|
|
// and which is && ref-qualified.
|
|
isRValueRefQualified(),
|
|
|
|
decl().bind("invalidMethodRefQualified")),
|
|
this);
|
|
|
|
AstMatcher->addMatcher(
|
|
// This is a matcher on a method declaration,
|
|
cxxMethodDecl(
|
|
// which is marked as no dangling on temporaries,
|
|
noDanglingOnTemporaries(),
|
|
|
|
// which returns a primitive type,
|
|
returns(builtinType()),
|
|
|
|
// and which doesn't return a pointer.
|
|
unless(returns(pointerType())),
|
|
|
|
decl().bind("invalidMethodPointer")),
|
|
this);
|
|
|
|
//////////////////
|
|
// Main checker //
|
|
//////////////////
|
|
|
|
auto hasParentCall = hasParent(expr(
|
|
anyOf(cxxOperatorCallExpr(
|
|
// If we're in a lamda, we may have an operator call expression
|
|
// ancestor in the AST, but the temporary we're matching
|
|
// against is not going to have the same lifetime as the
|
|
// constructor call.
|
|
unless(has(expr(ignoreTrivials(lambdaExpr())))),
|
|
expr().bind("parentOperatorCallExpr")),
|
|
callExpr(
|
|
// If we're in a lamda, we may have a call expression
|
|
// ancestor in the AST, but the temporary we're matching
|
|
// against is not going to have the same lifetime as the
|
|
// function call.
|
|
unless(has(expr(ignoreTrivials(lambdaExpr())))),
|
|
expr().bind("parentCallExpr")),
|
|
objcMessageExpr(
|
|
// If we're in a lamda, we may have an objc message expression
|
|
// ancestor in the AST, but the temporary we're matching
|
|
// against is not going to have the same lifetime as the
|
|
// function call.
|
|
unless(has(expr(ignoreTrivials(lambdaExpr())))),
|
|
expr().bind("parentObjCMessageExpr")),
|
|
cxxConstructExpr(
|
|
// If we're in a lamda, we may have a construct expression
|
|
// ancestor in the AST, but the temporary we're matching
|
|
// against is not going to have the same lifetime as the
|
|
// constructor call.
|
|
unless(has(expr(ignoreTrivials(lambdaExpr())))),
|
|
expr().bind("parentConstructExpr")))));
|
|
|
|
AstMatcher->addMatcher(
|
|
// This is a matcher on a method call,
|
|
cxxMemberCallExpr(
|
|
// which is in first party code,
|
|
isFirstParty(),
|
|
|
|
// and which is performed on a temporary,
|
|
on(allOf(unless(hasType(pointerType())), isTemporary(),
|
|
// but which is not `this`.
|
|
unless(cxxThisExpr()))),
|
|
|
|
// and which is marked as no dangling on temporaries.
|
|
callee(cxxMethodDecl(noDanglingOnTemporaries())),
|
|
|
|
expr().bind("memberCallExpr"),
|
|
|
|
// We optionally match a parent call expression or a parent construct
|
|
// expression because using a temporary inside a call is fine as long
|
|
// as the pointer doesn't escape the function call.
|
|
anyOf(
|
|
// This is the case where the call is the direct parent, so we
|
|
// know that the member call expression is the argument.
|
|
allOf(hasParentCall, expr().bind("parentCallArg")),
|
|
|
|
// This is the case where the call is not the direct parent, so we
|
|
// get its child to know in which argument tree we are.
|
|
hasAncestor(expr(hasParentCall, expr().bind("parentCallArg"))),
|
|
// To make it optional.
|
|
anything())),
|
|
this);
|
|
}
|
|
|
|
void DanglingOnTemporaryChecker::check(const MatchFinder::MatchResult &Result) {
|
|
///////////////////////////////////////
|
|
// Quick annotation conflict checker //
|
|
///////////////////////////////////////
|
|
|
|
const char *ErrorInvalidRefQualified = "methods annotated with "
|
|
"MOZ_NO_DANGLING_ON_TEMPORARIES "
|
|
"cannot be && ref-qualified";
|
|
|
|
const char *ErrorInvalidPointer = "methods annotated with "
|
|
"MOZ_NO_DANGLING_ON_TEMPORARIES must "
|
|
"return a pointer";
|
|
|
|
if (auto InvalidRefQualified =
|
|
Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodRefQualified")) {
|
|
diag(InvalidRefQualified->getLocation(), ErrorInvalidRefQualified,
|
|
DiagnosticIDs::Error);
|
|
return;
|
|
}
|
|
|
|
if (auto InvalidPointer =
|
|
Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodPointer")) {
|
|
diag(InvalidPointer->getLocation(), ErrorInvalidPointer,
|
|
DiagnosticIDs::Error);
|
|
return;
|
|
}
|
|
|
|
//////////////////
|
|
// Main checker //
|
|
//////////////////
|
|
|
|
const char *Error = "calling `%0` on a temporary, potentially allowing use "
|
|
"after free of the raw pointer";
|
|
|
|
const char *EscapeStmtNote =
|
|
"the raw pointer escapes the function scope here";
|
|
|
|
const ObjCMessageExpr *ParentObjCMessageExpr =
|
|
Result.Nodes.getNodeAs<ObjCMessageExpr>("parentObjCMessageExpr");
|
|
|
|
// We don't care about cases in ObjC message expressions.
|
|
if (ParentObjCMessageExpr) {
|
|
return;
|
|
}
|
|
|
|
const CXXMemberCallExpr *MemberCall =
|
|
Result.Nodes.getNodeAs<CXXMemberCallExpr>("memberCallExpr");
|
|
|
|
const CallExpr *ParentCallExpr =
|
|
Result.Nodes.getNodeAs<CallExpr>("parentCallExpr");
|
|
const CXXConstructExpr *ParentConstructExpr =
|
|
Result.Nodes.getNodeAs<CXXConstructExpr>("parentConstructExpr");
|
|
const CXXOperatorCallExpr *ParentOperatorCallExpr =
|
|
Result.Nodes.getNodeAs<CXXOperatorCallExpr>("parentOperatorCallExpr");
|
|
const Expr *ParentCallArg = Result.Nodes.getNodeAs<Expr>("parentCallArg");
|
|
|
|
// Just in case.
|
|
if (!MemberCall) {
|
|
return;
|
|
}
|
|
|
|
// If we have a parent call, we check whether or not we escape the function
|
|
// being called.
|
|
if (ParentOperatorCallExpr || ParentCallExpr || ParentConstructExpr) {
|
|
// Just in case.
|
|
if (!ParentCallArg) {
|
|
return;
|
|
}
|
|
|
|
// No default constructor so we can't construct it using if/else.
|
|
auto FunctionEscapeData =
|
|
ParentOperatorCallExpr
|
|
? escapesFunction(ParentCallArg, ParentOperatorCallExpr)
|
|
: ParentCallExpr
|
|
? escapesFunction(ParentCallArg, ParentCallExpr)
|
|
: escapesFunction(ParentCallArg, ParentConstructExpr);
|
|
|
|
// If there was an error in the escapesFunction call.
|
|
if (std::error_code ec = FunctionEscapeData.getError()) {
|
|
// FIXME: For now we ignore the variadic case and just consider that the
|
|
// argument doesn't escape the function. Same for the case where we can't
|
|
// find the function declaration or if the function is builtin.
|
|
if (static_cast<EscapesFunctionError>(ec.value()) ==
|
|
EscapesFunctionError::FunctionIsVariadic ||
|
|
static_cast<EscapesFunctionError>(ec.value()) ==
|
|
EscapesFunctionError::FunctionDeclNotFound ||
|
|
static_cast<EscapesFunctionError>(ec.value()) ==
|
|
EscapesFunctionError::FunctionIsBuiltin) {
|
|
return;
|
|
}
|
|
|
|
// We emit the internal checker error and return.
|
|
diag(MemberCall->getExprLoc(),
|
|
std::string(ec.category().name()) + " error: " + ec.message(),
|
|
DiagnosticIDs::Error);
|
|
return;
|
|
}
|
|
|
|
// We deconstruct the function escape data.
|
|
const Stmt *EscapeStmt;
|
|
const Decl *EscapeDecl;
|
|
std::tie(EscapeStmt, EscapeDecl) = *FunctionEscapeData;
|
|
|
|
// If we didn't escape a parent function, we're done: we don't emit any
|
|
// diagnostic.
|
|
if (!EscapeStmt || !EscapeDecl) {
|
|
return;
|
|
}
|
|
|
|
// We emit the error diagnostic indicating that we are calling the method
|
|
// temporary.
|
|
diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
|
|
<< MemberCall->getMethodDecl()->getName()
|
|
<< MemberCall->getSourceRange();
|
|
|
|
// We indicate the escape statement.
|
|
diag(EscapeStmt->getBeginLoc(), EscapeStmtNote, DiagnosticIDs::Note)
|
|
<< EscapeStmt->getSourceRange();
|
|
|
|
// We build the escape note along with its source range.
|
|
StringRef EscapeDeclNote;
|
|
SourceRange EscapeDeclRange;
|
|
if (isa<ParmVarDecl>(EscapeDecl)) {
|
|
EscapeDeclNote = "through the parameter declared here";
|
|
EscapeDeclRange = EscapeDecl->getSourceRange();
|
|
} else if (isa<VarDecl>(EscapeDecl)) {
|
|
EscapeDeclNote = "through the variable declared here";
|
|
EscapeDeclRange = EscapeDecl->getSourceRange();
|
|
} else if (isa<FieldDecl>(EscapeDecl)) {
|
|
EscapeDeclNote = "through the field declared here";
|
|
EscapeDeclRange = EscapeDecl->getSourceRange();
|
|
} else if (auto FuncDecl = dyn_cast<FunctionDecl>(EscapeDecl)) {
|
|
EscapeDeclNote = "through the return value of the function declared here";
|
|
EscapeDeclRange = FuncDecl->getReturnTypeSourceRange();
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// We emit the declaration note indicating through which decl the argument
|
|
// escapes.
|
|
diag(EscapeDecl->getLocation(), EscapeDeclNote, DiagnosticIDs::Note)
|
|
<< EscapeDeclRange;
|
|
} else {
|
|
// We emit the error diagnostic indicating that we are calling the method
|
|
// temporary.
|
|
diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
|
|
<< MemberCall->getMethodDecl()->getName()
|
|
<< MemberCall->getSourceRange();
|
|
}
|
|
}
|