Source code

Revision control

Copy as Markdown

Other Tools

/* 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();
}
}