diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /build/clang-plugin/DanglingOnTemporaryChecker.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | build/clang-plugin/DanglingOnTemporaryChecker.cpp | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/build/clang-plugin/DanglingOnTemporaryChecker.cpp b/build/clang-plugin/DanglingOnTemporaryChecker.cpp new file mode 100644 index 0000000000..96d85ef4c0 --- /dev/null +++ b/build/clang-plugin/DanglingOnTemporaryChecker.cpp @@ -0,0 +1,256 @@ +/* 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(); + } +} |