summaryrefslogtreecommitdiffstats
path: root/build/clang-plugin/DanglingOnTemporaryChecker.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /build/clang-plugin/DanglingOnTemporaryChecker.cpp
parentInitial commit. (diff)
downloadfirefox-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.cpp256
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();
+ }
+}