Bug 1374024 - add checker to prevent dangling pointers returned by method calls
☠☠ backed out by fc27e4fc79a3 ☠ ☠
authorTristan Bourvon <tbourvon@mozilla.com>
Wed, 05 Jul 2017 16:14:21 +0200
changeset 368639 1a49d403a9a49313fbeabbb9d84203fe33701173
parent 368638 cadef5ef9c4454f2454ac5044586eb6a647f1569
child 368640 7f768b83c9288c751c1e8a90870c51154b8b798b
push id92519
push userbpostelnicu@mozilla.com
push dateThu, 13 Jul 2017 09:02:38 +0000
treeherdermozilla-inbound@1eb2623a06c8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1374024
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1374024 - add checker to prevent dangling pointers returned by method calls on temporaries. r=mystor MozReview-Commit-ID: 9khNt59ONFE
build/clang-plugin/Checks.inc
build/clang-plugin/ChecksIncludes.inc
build/clang-plugin/CustomMatchers.h
build/clang-plugin/DanglingOnTemporaryChecker.cpp
build/clang-plugin/DanglingOnTemporaryChecker.h
build/clang-plugin/Utils.h
build/clang-plugin/VariableUsageHelpers.cpp
build/clang-plugin/VariableUsageHelpers.h
build/clang-plugin/moz.build
build/clang-plugin/tests/TestDanglingOnTemporary.cpp
build/clang-plugin/tests/moz.build
mfbt/Attributes.h
--- a/build/clang-plugin/Checks.inc
+++ b/build/clang-plugin/Checks.inc
@@ -1,16 +1,17 @@
 /* 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/. */
 
 // The list of checker classes that are compatible with clang-tidy.
 
 CHECK(ArithmeticArgChecker, "arithmetic-argument")
 CHECK(AssertAssignmentChecker, "assignment-in-assert")
+CHECK(DanglingOnTemporaryChecker, "dangling-on-temporary")
 CHECK(ExplicitImplicitChecker, "implicit-constructor")
 CHECK(ExplicitOperatorBoolChecker, "explicit-operator-bool")
 CHECK(KungFuDeathGripChecker, "kungfu-death-grip")
 CHECK(MustOverrideChecker, "must-override")
 CHECK(MustReturnFromCallerChecker, "must-return-from-caller")
 CHECK(MustUseChecker, "must-use")
 CHECK(NaNExprChecker, "nan-expr")
 CHECK(NeedsNoVTableTypeChecker, "needs-no-vtable-type")
--- a/build/clang-plugin/ChecksIncludes.inc
+++ b/build/clang-plugin/ChecksIncludes.inc
@@ -2,16 +2,17 @@
  * 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/. */
 
 // The list of #include directives necessary for the checker classes that
 // are compatible with clang-tidy.
 
 #include "ArithmeticArgChecker.h"
 #include "AssertAssignmentChecker.h"
+#include "DanglingOnTemporaryChecker.h"
 #include "ExplicitImplicitChecker.h"
 #include "ExplicitOperatorBoolChecker.h"
 #include "KungFuDeathGripChecker.h"
 #include "MustOverrideChecker.h"
 #include "MustReturnFromCallerChecker.h"
 #include "MustUseChecker.h"
 #include "NaNExprChecker.h"
 #include "NeedsNoVTableTypeChecker.h"
--- a/build/clang-plugin/CustomMatchers.h
+++ b/build/clang-plugin/CustomMatchers.h
@@ -2,16 +2,17 @@
  * 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/. */
 
 #ifndef CustomMatchers_h__
 #define CustomMatchers_h__
 
 #include "MemMoveAnnotation.h"
 #include "Utils.h"
+#include "VariableUsageHelpers.h"
 
 namespace clang {
 namespace ast_matchers {
 
 /// This matcher will match any function declaration that is declared as a heap
 /// allocator.
 AST_MATCHER(FunctionDecl, heapAllocator) {
   return hasCustomAnnotation(&Node, "moz_heap_allocator");
@@ -24,16 +25,88 @@ AST_MATCHER(Decl, noArithmeticExprInArgs
 }
 
 /// This matcher will match any C++ class that is marked as having a trivial
 /// constructor and destructor.
 AST_MATCHER(CXXRecordDecl, hasTrivialCtorDtor) {
   return hasCustomAnnotation(&Node, "moz_trivial_ctor_dtor");
 }
 
+/// This matcher will match lvalue-ref-qualified methods.
+AST_MATCHER(CXXMethodDecl, isLValueRefQualified) {
+  return Node.getRefQualifier() == RQ_LValue;
+}
+
+/// This matcher will match rvalue-ref-qualified methods.
+AST_MATCHER(CXXMethodDecl, isRValueRefQualified) {
+  return Node.getRefQualifier() == RQ_RValue;
+}
+
+/// This matcher will match any method declaration that is marked as returning
+/// a pointer deleted by the destructor of the class.
+AST_MATCHER(CXXMethodDecl, noDanglingOnTemporaries) {
+  return hasCustomAnnotation(&Node, "moz_no_dangling_on_temporaries");
+}
+
+/// This matcher will match an expression if it escapes the scope of the callee
+/// of a parent call expression (skipping trivial parents).
+/// The first inner matcher matches the statement where the escape happens, and
+/// the second inner matcher corresponds to the declaration through which it
+/// happens.
+AST_MATCHER_P2(Expr, escapesParentFunctionCall, \
+               internal::Matcher<Stmt>, EscapeStmtMatcher, \
+               internal::Matcher<Decl>, EscapeDeclMatcher) {
+  auto Call =
+      IgnoreParentTrivials(Node, &Finder->getASTContext()).get<CallExpr>();
+  if (!Call) {
+    return false;
+  }
+
+  auto FunctionEscapeData = escapesFunction(&Node, Call);
+  assert(FunctionEscapeData && "escapesFunction() returned NoneType: there is a"
+                               " logic bug in the matcher");
+
+  const Stmt* EscapeStmt;
+  const Decl* EscapeDecl;
+  std::tie(EscapeStmt, EscapeDecl) = *FunctionEscapeData;
+
+  return EscapeStmt && EscapeDecl
+      && EscapeStmtMatcher.matches(*EscapeStmt, Finder, Builder)
+      && EscapeDeclMatcher.matches(*EscapeDecl, Finder, Builder);
+}
+
+/// This is the custom matcher class corresponding to hasNonTrivialParent.
+template <typename T, typename ParentT>
+class HasNonTrivialParentMatcher : public internal::WrapperMatcherInterface<T> {
+  static_assert(internal::IsBaseType<ParentT>::value,
+                "has parent only accepts base type matcher");
+
+public:
+  explicit HasNonTrivialParentMatcher(
+      const internal::Matcher<ParentT> &NonTrivialParentMatcher)
+      : HasNonTrivialParentMatcher::WrapperMatcherInterface(
+            NonTrivialParentMatcher) {}
+
+  bool matches(const T &Node, internal::ASTMatchFinder *Finder,
+               internal::BoundNodesTreeBuilder *Builder) const override {
+    auto NewNode = IgnoreParentTrivials(Node, &Finder->getASTContext());
+
+    // We return the result of the inner matcher applied to the new node.
+    return this->InnerMatcher.matches(NewNode, Finder, Builder);
+  }
+};
+
+/// This matcher acts like hasParent, except it skips trivial constructs by
+/// traversing the AST tree upwards.
+const internal::ArgumentAdaptingMatcherFunc<
+    HasNonTrivialParentMatcher,
+    internal::TypeList<Decl, NestedNameSpecifierLoc, Stmt, TypeLoc>,
+    internal::TypeList<Decl, NestedNameSpecifierLoc, Stmt, TypeLoc>>
+    LLVM_ATTRIBUTE_UNUSED hasNonTrivialParent = {};
+
 /// This matcher will match any function declaration that is marked to prohibit
 /// calling AddRef or Release on its return value.
 AST_MATCHER(FunctionDecl, hasNoAddRefReleaseOnReturnAttr) {
   return hasCustomAnnotation(&Node,
                                          "moz_no_addref_release_on_return");
 }
 
 /// This matcher will match all arithmetic binary operators.
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/DanglingOnTemporaryChecker.cpp
@@ -0,0 +1,149 @@
+/* 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"
+
+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(),
+
+          // and which doesn't return a pointer.
+          unless(returns(pointerType())),
+
+          decl().bind("invalidMethodPointer")),
+      this);
+
+  //////////////////
+  // Main checker //
+  //////////////////
+
+  AstMatcher->addMatcher(
+      // This is a matcher on a method call,
+      cxxMemberCallExpr(
+          // which is performed on a temporary,
+          onImplicitObjectArgument(materializeTemporaryExpr()),
+
+          // and which is marked as no dangling on temporaries.
+          callee(cxxMethodDecl(noDanglingOnTemporaries())),
+
+          anyOf(
+              // We care only about the cases where the method call is NOT an
+              // argument in a call expression. If it is in a call expression,
+              // the temporary lives long enough so that it's valid to use the
+              // pointer.
+              unless(hasNonTrivialParent(callExpr())),
+              // Unless the argument somehow escapes the function scope through
+              // globals/statics/black magic.
+              escapesParentFunctionCall(
+                  stmt().bind("escapeStatement"),
+                  decl().bind("escapeDeclaration"))),
+
+          expr().bind("memberCallExpr")),
+      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 CXXMemberCallExpr *MemberCall =
+      Result.Nodes.getNodeAs<CXXMemberCallExpr>("memberCallExpr");
+
+  // If we escaped the a parent function call, we get the statement and the
+  // associated declaration.
+  const Stmt *EscapeStmt =
+      Result.Nodes.getNodeAs<Stmt>("escapeStatement");
+  const Decl *EscapeDecl =
+      Result.Nodes.getNodeAs<Decl>("escapeDeclaration");
+
+  // Just in case.
+  if (!MemberCall) {
+    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();
+
+  // If we didn't escape a parent function, we're done.
+  if (!EscapeStmt || !EscapeDecl) {
+    return;
+  }
+
+  diag(EscapeStmt->getLocStart(), EscapeStmtNote, DiagnosticIDs::Note)
+      << EscapeStmt->getSourceRange();
+
+  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 (auto FuncDecl = dyn_cast<FunctionDecl>(EscapeDecl)) {
+    EscapeDeclNote = "through the return value of the function declared here";
+    EscapeDeclRange = FuncDecl->getReturnTypeSourceRange();
+  } else {
+    return;
+  }
+
+  diag(EscapeDecl->getLocation(), EscapeDeclNote, DiagnosticIDs::Note)
+      << EscapeDeclRange;
+}
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/DanglingOnTemporaryChecker.h
@@ -0,0 +1,19 @@
+/* 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/. */
+
+#ifndef DanglingOnTemporaryChecker_h__
+#define DanglingOnTemporaryChecker_h__
+
+#include "plugin.h"
+
+class DanglingOnTemporaryChecker : public BaseCheck {
+public:
+  DanglingOnTemporaryChecker(StringRef CheckName,
+                             ContextType *Context = nullptr)
+      : BaseCheck(CheckName, Context) {}
+  void registerMatchers(MatchFinder *AstMatcher) override;
+  void check(const MatchFinder::MatchResult &Result) override;
+};
+
+#endif
--- a/build/clang-plugin/Utils.h
+++ b/build/clang-plugin/Utils.h
@@ -302,16 +302,23 @@ inline bool typeIsRefPtr(QualType Q) {
 
   StringRef name = D->getName();
   if (name == "RefPtr" || name == "nsCOMPtr") {
     return true;
   }
   return false;
 }
 
+// This method returns true if the statement is trivial.
+inline bool IsTrivial(const Stmt *s) {
+  return s && (isa<ExprWithCleanups>(s) || isa<MaterializeTemporaryExpr>(s) ||
+               isa<CXXBindTemporaryExpr>(s) || isa<ImplicitCastExpr>(s) ||
+               isa<ParenExpr>(s));
+}
+
 // The method defined in clang for ignoring implicit nodes doesn't work with
 // some AST trees. To get around this, we define our own implementation of
 // IgnoreTrivials.
 inline const Stmt *IgnoreTrivials(const Stmt *s) {
   while (true) {
     if (!s) {
       return nullptr;
     } else if (auto *ewc = dyn_cast<ExprWithCleanups>(s)) {
@@ -324,23 +331,47 @@ inline const Stmt *IgnoreTrivials(const 
       s = ice->getSubExpr();
     } else if (auto *pe = dyn_cast<ParenExpr>(s)) {
       s = pe->getSubExpr();
     } else {
       break;
     }
   }
 
+  assert(!IsTrivial(s));
   return s;
 }
 
 inline const Expr *IgnoreTrivials(const Expr *e) {
   return cast<Expr>(IgnoreTrivials(static_cast<const Stmt *>(e)));
 }
 
+// This method is like IgnoreTrivials but ignores the nodes upwards instead of
+// downwards.
+template <typename T>
+inline ast_type_traits::DynTypedNode IgnoreParentTrivials(const T &Node,
+                                                          ASTContext *Context) {
+  // We traverse the AST upward until we encounter a non-trivial node.
+  auto CurrentNode = ast_type_traits::DynTypedNode::create(Node);
+  do {
+    // We get the parents of the current node from the AST context.
+    auto Parents = Context->getParents(CurrentNode);
+
+    // Not implemented yet, but probably not very useful for the cases where
+    // we use this matcher.
+    if (Parents.size() != 1) {
+      break;
+    }
+
+    CurrentNode = Parents[0];
+  } while (IsTrivial(CurrentNode.template get<Stmt>()));
+
+  return CurrentNode;
+}
+
 const FieldDecl *getBaseRefCntMember(QualType T);
 
 inline const FieldDecl *getBaseRefCntMember(const CXXRecordDecl *D) {
   const FieldDecl *RefCntMember = getClassRefCntMember(D);
   if (RefCntMember && isClassRefCounted(D)) {
     return RefCntMember;
   }
 
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/VariableUsageHelpers.cpp
@@ -0,0 +1,163 @@
+#include "VariableUsageHelpers.h"
+#include "Utils.h"
+
+std::vector<const Stmt*>
+getUsageAsRvalue(const ValueDecl* ValueDeclaration,
+                 const FunctionDecl* FuncDecl) {
+  std::vector<const Stmt*> UsageStatements;
+
+  // We check the function declaration has a body.
+  auto Body = FuncDecl->getBody();
+  if (!Body) {
+    return std::vector<const Stmt*>();
+  }
+
+  // We build a Control Flow Graph (CFG) fron the body of the function
+  // declaration.
+  std::unique_ptr<CFG> StatementCFG
+      = CFG::buildCFG(FuncDecl, Body, &FuncDecl->getASTContext(),
+                      CFG::BuildOptions());
+
+  // We iterate through all the CFGBlocks, which basically means that we go over
+  // all the possible branches of the code and therefore cover all statements.
+  for (auto& Block : *StatementCFG) {
+    // We iterate through all the statements of the block.
+    for (auto& BlockItem : *Block) {
+      Optional<CFGStmt> CFGStatement = BlockItem.getAs<CFGStmt>();
+      if (!CFGStatement) {
+        continue;
+      }
+
+      // FIXME: Right now this function/if chain is very basic and only covers
+      // the cases we need for escapesFunction()
+      if (auto BinOp = dyn_cast<BinaryOperator>(CFGStatement->getStmt())) {
+        // We only care about assignments.
+        if (BinOp->getOpcode() != BO_Assign) {
+          continue;
+        }
+
+        // We want our declaration to be used on the right hand side of the
+        // assignment.
+        auto DeclRef = dyn_cast<DeclRefExpr>(IgnoreTrivials(BinOp->getRHS()));
+        if (!DeclRef) {
+          continue;
+        }
+
+        if (DeclRef->getDecl() != ValueDeclaration) {
+          continue;
+        }
+      } else if (auto Return = dyn_cast<ReturnStmt>(CFGStatement->getStmt())) {
+        // We want our declaration to be used as the expression of the return
+        // statement.
+        auto DeclRef = dyn_cast_or_null<DeclRefExpr>(
+                            IgnoreTrivials(Return->getRetValue()));
+        if (!DeclRef) {
+          continue;
+        }
+
+        if (DeclRef->getDecl() != ValueDeclaration) {
+          continue;
+        }
+      } else {
+        continue;
+      }
+
+      // We didn't early-continue, so we add the statement to the list.
+      UsageStatements.push_back(CFGStatement->getStmt());
+    }
+  }
+
+  return UsageStatements;
+}
+
+Optional<std::tuple<const Stmt*, const Decl*>>
+escapesFunction(const Expr* Arg, const CallExpr* Call) {
+  // We get the function declaration corresponding to the call.
+  auto FuncDecl = Call->getDirectCallee();
+  if (!FuncDecl) {
+    return NoneType();
+  }
+
+  // We find the argument number corresponding to the Arg expression.
+  unsigned ArgNum = 0;
+  for (auto CallArg : Call->arguments()) {
+    if (IgnoreTrivials(Arg) == IgnoreTrivials(CallArg)) {
+      break;
+    }
+    ++ArgNum;
+  }
+  // If we don't find it, we early-return NoneType.
+  if (ArgNum >= Call->getNumArgs()) {
+    return NoneType();
+  }
+
+  // Now we get the associated parameter.
+  if (ArgNum >= FuncDecl->getNumParams()) {
+    return NoneType();
+  }
+  auto Param = FuncDecl->getParamDecl(ArgNum);
+
+  // We want both the argument and the parameter to be of pointer type.
+  // FIXME: this is enough for the DanglingOnTemporaryChecker, because the
+  // analysed methods only return pointers, but more cases should probably be
+  // handled when we want to use this function more broadly.
+  if (!Arg->getType()->isPointerType()
+      || !Param->getType()->isPointerType()) {
+    return NoneType();
+  }
+
+  // We retrieve the usages of the parameter in the function.
+  auto Usages = getUsageAsRvalue(Param, FuncDecl);
+
+  // For each usage, we check if it doesn't allow the parameter to escape the
+  // function scope.
+  for (auto Usage : Usages) {
+    // In the case of an assignment.
+    if (auto BinOp = dyn_cast<BinaryOperator>(Usage)) {
+      // We retrieve the declaration the parameter is assigned to.
+      auto DeclRef = dyn_cast<DeclRefExpr>(BinOp->getLHS());
+      if (!DeclRef) {
+        continue;
+      }
+
+      if (auto ParamDeclaration = dyn_cast<ParmVarDecl>(DeclRef->getDecl())) {
+        // This is the case where the parameter escapes through another
+        // parameter.
+
+        // FIXME: for now we only care about references because we only detect
+        // trivial LHS with just a DeclRefExpr, and not more complex cases like:
+        // void func(Type* param1, Type** param2) {
+        //   *param2 = param1;
+        // }
+        // This should be fixed when we have better/more helper functions to
+        // help deal with this kind of lvalue expressions.
+        if (!ParamDeclaration->getType()->isReferenceType()) {
+          continue;
+        }
+
+        return {{Usage, ParamDeclaration}};
+      } else if (auto VarDeclaration = dyn_cast<VarDecl>(DeclRef->getDecl())) {
+        // This is the case where the parameter escapes through a global/static
+        // variable.
+        if (!VarDeclaration->hasGlobalStorage()) {
+          continue;
+        }
+
+        return {{Usage, VarDeclaration}};
+      }
+    } else if (auto Return = dyn_cast<ReturnStmt>(Usage)) {
+      // This is the case where the parameter escapes through the return value
+      // of the function.
+      if (!FuncDecl->getReturnType()->isPointerType()
+          && !FuncDecl->getReturnType()->isReferenceType()) {
+        continue;
+      }
+
+      return {{Usage, FuncDecl}};
+    }
+  }
+
+  // No early-return, this means that we haven't found any case of funciton
+  // escaping and that therefore the parameter remains in the function scope.
+  return {{nullptr, nullptr}};
+}
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/VariableUsageHelpers.h
@@ -0,0 +1,36 @@
+/* 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 <vector>
+
+#ifndef VariableUsageHelpers_h__
+#define VariableUsageHelpers_h__
+
+#include "plugin.h"
+
+/// Returns a list of the statements where the given declaration is used as an
+/// rvalue (within the provided function).
+///
+/// WARNING: incomplete behaviour/implementation for general-purpose use outside
+/// of escapesFunction(). This only detects very basic usages (see
+/// implementation for more details).
+std::vector<const Stmt*>
+getUsageAsRvalue(const ValueDecl* ValueDeclaration,
+                 const FunctionDecl* FuncDecl);
+
+/// Returns a (statement, decl) tuple if an argument from a call expression
+/// escapes the function scope through globals/statics/other things. The
+/// statement is where the value escapes the function, while the declaration
+/// points to what it escapes through. If the argument doesn't escape the
+/// function, the tuple will only contain nullptrs.
+/// If the analysis runs into an unexpected error or into an unimplemented
+/// configuration, this returns NoneType.
+///
+/// WARNING: incomplete behaviour/implementation for general-purpose use outside
+/// of DanglingOnTemporaryChecker. This only covers a limited set of cases,
+/// mainly in terms of arguments and parameter types.
+Optional<std::tuple<const Stmt*, const Decl*>>
+escapesFunction(const Expr* Arg, const CallExpr* Call);
+
+#endif
--- a/build/clang-plugin/moz.build
+++ b/build/clang-plugin/moz.build
@@ -7,16 +7,17 @@
 SharedLibrary('clang-plugin')
 
 SOURCES += ['!ThirdPartyPaths.cpp']
 
 UNIFIED_SOURCES += [
     'ArithmeticArgChecker.cpp',
     'AssertAssignmentChecker.cpp',
     'CustomTypeAnnotation.cpp',
+    'DanglingOnTemporaryChecker.cpp',
     'DiagnosticsMatcher.cpp',
     'ExplicitImplicitChecker.cpp',
     'ExplicitOperatorBoolChecker.cpp',
     'KungFuDeathGripChecker.cpp',
     'MozCheckAction.cpp',
     'MustOverrideChecker.cpp',
     'MustReturnFromCallerChecker.cpp',
     'MustUseChecker.cpp',
@@ -31,16 +32,17 @@ UNIFIED_SOURCES += [
     'NonParamInsideFunctionDeclChecker.cpp',
     'OverrideBaseCallChecker.cpp',
     'OverrideBaseCallUsageChecker.cpp',
     'RefCountedCopyConstructorChecker.cpp',
     'RefCountedInsideLambdaChecker.cpp',
     'ScopeChecker.cpp',
     'SprintfLiteralChecker.cpp',
     'TrivialCtorDtorChecker.cpp',
+    'VariableUsageHelpers.cpp',
 ]
 
 GENERATED_FILES += ['ThirdPartyPaths.cpp']
 third_party_paths = GENERATED_FILES['ThirdPartyPaths.cpp']
 third_party_paths.script = "ThirdPartyPaths.py:generate"
 third_party_paths.inputs = [
     '/tools/rewriting/ThirdPartyPaths.txt',
 ]
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestDanglingOnTemporary.cpp
@@ -0,0 +1,41 @@
+#define MOZ_NO_DANGLING_ON_TEMPORARIES                                              \
+  __attribute__((annotate("moz_no_dangling_on_temporaries")))
+
+class AnnotateConflict {
+  MOZ_NO_DANGLING_ON_TEMPORARIES int *get() && { return nullptr; } // expected-error {{methods annotated with MOZ_NO_DANGLING_ON_TEMPORARIES cannot be && ref-qualified}}
+  MOZ_NO_DANGLING_ON_TEMPORARIES int test() { return 0; } // expected-error {{methods annotated with MOZ_NO_DANGLING_ON_TEMPORARIES must return a pointer}}
+};
+
+class NS_ConvertUTF8toUTF16 {
+public:
+  MOZ_NO_DANGLING_ON_TEMPORARIES int *get() { return nullptr; }
+};
+
+NS_ConvertUTF8toUTF16 TemporaryFunction() { return NS_ConvertUTF8toUTF16(); }
+
+void UndefinedFunction(int* test);
+
+void NoEscapeFunction(int *test) {}
+
+int *glob; // expected-note {{through the variable declared here}}
+void EscapeFunction1(int *test) { glob = test; } // expected-note {{the raw pointer escapes the function scope here}}
+
+void EscapeFunction2(int *test, int *&escape) { escape = test; } // expected-note {{the raw pointer escapes the function scope here}} \
+                                                                    expected-note {{through the parameter declared here}}
+
+int *EscapeFunction3(int *test) { return test; } // expected-note {{the raw pointer escapes the function scope here}} \
+                                                    expected-note {{through the return value of the function declared here}}
+
+int main() {
+  int *test = TemporaryFunction().get(); // expected-error {{calling `get` on a temporary, potentially allowing use after free of the raw pointer}}
+  int *test2 = NS_ConvertUTF8toUTF16().get(); // expected-error {{calling `get` on a temporary, potentially allowing use after free of the raw pointer}}
+
+  UndefinedFunction(NS_ConvertUTF8toUTF16().get());
+
+  NoEscapeFunction(TemporaryFunction().get());
+  EscapeFunction1(TemporaryFunction().get()); // expected-error {{calling `get` on a temporary, potentially allowing use after free of the raw pointer}}
+
+  int *escape;
+  EscapeFunction2(TemporaryFunction().get(), escape); // expected-error {{calling `get` on a temporary, potentially allowing use after free of the raw pointer}}
+  int *escape2 = EscapeFunction3(TemporaryFunction().get()); // expected-error {{calling `get` on a temporary, potentially allowing use after free of the raw pointer}}
+}
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -6,16 +6,17 @@
 
 # dummy library name to avoid skipping building the sources here.
 Library('clang-plugin-tests')
 
 SOURCES += [
     'TestAssertWithAssignment.cpp',
     'TestBadImplicitConversionCtor.cpp',
     'TestCustomHeap.cpp',
+    'TestDanglingOnTemporary.cpp',
     'TestExplicitOperatorBool.cpp',
     'TestGlobalClass.cpp',
     'TestHeapClass.cpp',
     'TestInheritTypeAnnotationsFromTemplateArgs.cpp',
     'TestKungFuDeathGrip.cpp',
     'TestMultipleAnnotations.cpp',
     'TestMustOverride.cpp',
     'TestMustReturnFromCaller.cpp',
--- a/mfbt/Attributes.h
+++ b/mfbt/Attributes.h
@@ -521,16 +521,24 @@
  *   to be moved in memory using memmove().
  * MOZ_NEEDS_MEMMOVABLE_TYPE: Applies to template class declarations where the
  *   template arguments are required to be safe to move in memory using
  *   memmove().  Passing MOZ_NON_MEMMOVABLE types to these templates is a
  *   compile time error.
  * MOZ_NEEDS_MEMMOVABLE_MEMBERS: Applies to class declarations where each member
  *   must be safe to move in memory using memmove().  MOZ_NON_MEMMOVABLE types
  *   used in members of these classes are compile time errors.
+ * MOZ_NO_DANGLING_ON_TEMPORARIES: Applies to method declarations which return
+ *   a pointer that is freed when the destructor of the class is called. This
+ *   prevents these methods from being called on temporaries of the class,
+ *   reducing risks of use-after-free.
+ *   This attribute cannot be applied to && methods.
+ *   In some cases, adding a deleted &&-qualified overload is too restrictive as
+ *   this method should still be callable as a non-escaping argument to another
+ *   function. This annotation can be used in those cases.
  * MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS: Applies to template class
  *   declarations where an instance of the template should be considered, for
  *   static analysis purposes, to inherit any type annotations (such as
  *   MOZ_MUST_USE_TYPE and MOZ_STACK_CLASS) from its template arguments.
  * MOZ_INIT_OUTSIDE_CTOR: Applies to class member declarations. Occasionally
  *   there are class members that are not initialized in the constructor,
  *   but logic elsewhere in the class ensures they are initialized prior to use.
  *   Using this attribute on a member disables the check that this member must be
@@ -582,16 +590,17 @@
 #  define MOZ_NON_OWNING_REF __attribute__((annotate("moz_weak_ref")))
 #  define MOZ_UNSAFE_REF(reason) __attribute__((annotate("moz_weak_ref")))
 #  define MOZ_NO_ADDREF_RELEASE_ON_RETURN __attribute__((annotate("moz_no_addref_release_on_return")))
 #  define MOZ_MUST_USE_TYPE __attribute__((annotate("moz_must_use_type")))
 #  define MOZ_NEEDS_NO_VTABLE_TYPE __attribute__((annotate("moz_needs_no_vtable_type")))
 #  define MOZ_NON_MEMMOVABLE __attribute__((annotate("moz_non_memmovable")))
 #  define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type")))
 #  define MOZ_NEEDS_MEMMOVABLE_MEMBERS __attribute__((annotate("moz_needs_memmovable_members")))
+#  define MOZ_NO_DANGLING_ON_TEMPORARIES __attribute__((annotate("moz_no_dangling_on_temporaries")))
 #  define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS \
     __attribute__((annotate("moz_inherit_type_annotations_from_template_args")))
 #  define MOZ_NON_AUTOABLE __attribute__((annotate("moz_non_autoable")))
 #  define MOZ_INIT_OUTSIDE_CTOR \
     __attribute__((annotate("moz_ignore_ctor_initialization")))
 #  define MOZ_IS_CLASS_INIT \
     __attribute__((annotate("moz_is_class_init")))
 #  define MOZ_NON_PARAM \
@@ -627,16 +636,17 @@
 #  define MOZ_NON_OWNING_REF /* nothing */
 #  define MOZ_UNSAFE_REF(reason) /* nothing */
 #  define MOZ_NO_ADDREF_RELEASE_ON_RETURN /* nothing */
 #  define MOZ_MUST_USE_TYPE /* nothing */
 #  define MOZ_NEEDS_NO_VTABLE_TYPE /* nothing */
 #  define MOZ_NON_MEMMOVABLE /* nothing */
 #  define MOZ_NEEDS_MEMMOVABLE_TYPE /* nothing */
 #  define MOZ_NEEDS_MEMMOVABLE_MEMBERS /* nothing */
+#  define MOZ_NO_DANGLING_ON_TEMPORARIES /* nothing */
 #  define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS /* nothing */
 #  define MOZ_INIT_OUTSIDE_CTOR /* nothing */
 #  define MOZ_IS_CLASS_INIT /* nothing */
 #  define MOZ_NON_PARAM /* nothing */
 #  define MOZ_NON_AUTOABLE /* nothing */
 #  define MOZ_REQUIRED_BASE_METHOD /* nothing */
 #  define MOZ_MUST_RETURN_FROM_CALLER /* nothing */
 #  define MOZ_MAY_CALL_AFTER_MUST_RETURN /* nothing */