/* 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("invalidMethodRefQualified")) { diag(InvalidRefQualified->getLocation(), ErrorInvalidRefQualified, DiagnosticIDs::Error); return; } if (auto InvalidPointer = Result.Nodes.getNodeAs("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("parentObjCMessageExpr"); // We don't care about cases in ObjC message expressions. if (ParentObjCMessageExpr) { return; } const CXXMemberCallExpr *MemberCall = Result.Nodes.getNodeAs("memberCallExpr"); const CallExpr *ParentCallExpr = Result.Nodes.getNodeAs("parentCallExpr"); const CXXConstructExpr *ParentConstructExpr = Result.Nodes.getNodeAs("parentConstructExpr"); const CXXOperatorCallExpr *ParentOperatorCallExpr = Result.Nodes.getNodeAs("parentOperatorCallExpr"); const Expr *ParentCallArg = Result.Nodes.getNodeAs("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(ec.value()) == EscapesFunctionError::FunctionIsVariadic || static_cast(ec.value()) == EscapesFunctionError::FunctionDeclNotFound || static_cast(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(EscapeDecl)) { EscapeDeclNote = "through the parameter declared here"; EscapeDeclRange = EscapeDecl->getSourceRange(); } else if (isa(EscapeDecl)) { EscapeDeclNote = "through the variable declared here"; EscapeDeclRange = EscapeDecl->getSourceRange(); } else if (isa(EscapeDecl)) { EscapeDeclNote = "through the field declared here"; EscapeDeclRange = EscapeDecl->getSourceRange(); } else if (auto FuncDecl = dyn_cast(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(); } }