Bug 1380423 - Add static-analysis to enforce strict rules on functions which can run scripts. r=mystor
authorTristan Bourvon <tbourvon@mozilla.com>
Tue, 08 Aug 2017 19:48:53 +0300
changeset 428551 2510955b5c905a325b64384ba9c77bea407a4735
parent 428550 9335b66895a9f6b298fd0beef679f9714b98344d
child 428552 b9e2b684b9883fd5dae81b3592280cd12451521d
push id1567
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 12:36:05 +0000
treeherdermozilla-release@e512c14a0406 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmystor
bugs1380423
milestone57.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 1380423 - Add static-analysis to enforce strict rules on functions which can run scripts. r=mystor MozReview-Commit-ID: GGSyq0z5msB
build/clang-plugin/CanRunScriptChecker.cpp
build/clang-plugin/CanRunScriptChecker.h
build/clang-plugin/Checks.inc
build/clang-plugin/ChecksIncludes.inc
build/clang-plugin/CustomMatchers.h
build/clang-plugin/Utils.h
build/clang-plugin/moz.build
build/clang-plugin/tests/TestCanRunScript.cpp
build/clang-plugin/tests/moz.build
mfbt/Attributes.h
mfbt/RefPtr.h
mfbt/StaticAnalysisFunctions.h
xpcom/base/OwningNonNull.h
xpcom/base/nsCOMPtr.h
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/CanRunScriptChecker.cpp
@@ -0,0 +1,224 @@
+/* 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 "CanRunScriptChecker.h"
+#include "CustomMatchers.h"
+
+void CanRunScriptChecker::registerMatchers(MatchFinder* AstMatcher) {
+  auto InvalidArg =
+      // We want to find any expression,
+      ignoreTrivials(expr(
+          // which has a refcounted pointer type,
+          hasType(pointerType(
+              pointee(hasDeclaration(cxxRecordDecl(isRefCounted()))))),
+          // and which is not this,
+          unless(cxxThisExpr()),
+          // and which is not a method call on a smart ptr,
+          unless(cxxMemberCallExpr(on(hasType(isSmartPtrToRefCounted())))),
+          // and which is not a parameter of the parent function,
+          unless(declRefExpr(to(parmVarDecl()))),
+          // and which is not a MOZ_KnownLive wrapped value.
+          unless(callExpr(callee(
+            functionDecl(hasName("MOZ_KnownLive"))))),
+          expr().bind("invalidArg")));
+
+  auto OptionalInvalidExplicitArg =
+      anyOf(
+          // We want to find any argument which is invalid.
+          hasAnyArgument(InvalidArg),
+
+          // This makes this matcher optional.
+          anything());
+
+  // Please not that the hasCanRunScriptAnnotation() matchers are not present
+  // directly in the cxxMemberCallExpr, callExpr and constructExpr matchers
+  // because we check that the corresponding functions can run script later in
+  // the checker code.
+  AstMatcher->addMatcher(
+      expr(
+          anyOf(
+              // We want to match a method call expression,
+              cxxMemberCallExpr(
+                  // which optionally has an invalid arg,
+                  OptionalInvalidExplicitArg,
+                  // or which optionally has an invalid implicit this argument,
+                  anyOf(
+                      // which derefs into an invalid arg,
+                      on(cxxOperatorCallExpr(
+                          anyOf(
+                              hasAnyArgument(InvalidArg),
+                              anything()))),
+                      // or is an invalid arg.
+                      on(InvalidArg),
+
+                      anything()),
+                  expr().bind("callExpr")),
+              // or a regular call expression,
+              callExpr(
+                  // which optionally has an invalid arg.
+                  OptionalInvalidExplicitArg,
+                  expr().bind("callExpr")),
+              // or a construct expression,
+              cxxConstructExpr(
+                  // which optionally has an invalid arg.
+                  OptionalInvalidExplicitArg,
+                  expr().bind("constructExpr"))),
+
+          anyOf(
+              // We want to match the parent function.
+              forFunction(functionDecl().bind("nonCanRunScriptParentFunction")),
+
+              // ... optionally.
+              anything())),
+      this);
+}
+
+void CanRunScriptChecker::onStartOfTranslationUnit() {
+  IsFuncSetBuilt = false;
+  CanRunScriptFuncs.clear();
+}
+
+namespace {
+  /// This class is a callback used internally to match function declarations
+  /// with the MOZ_CAN_RUN_SCRIPT annotation, adding these functions and all
+  /// the methods they override to the can-run-script function set.
+  class FuncSetCallback : public MatchFinder::MatchCallback {
+  public:
+    FuncSetCallback(std::unordered_set<const FunctionDecl*> &FuncSet)
+      : CanRunScriptFuncs(FuncSet) {}
+
+    void run(const MatchFinder::MatchResult &Result) override;
+
+  private:
+    /// This method recursively adds all the methods overriden by the given
+    /// paremeter.
+    void addAllOverriddenMethodsRecursively(const CXXMethodDecl* Method);
+
+    std::unordered_set<const FunctionDecl*> &CanRunScriptFuncs;
+  };
+
+  void FuncSetCallback::run(const MatchFinder::MatchResult &Result) {
+    const FunctionDecl* Func =
+      Result.Nodes.getNodeAs<FunctionDecl>("canRunScriptFunction");
+
+    CanRunScriptFuncs.insert(Func);
+
+    // If this is a method, we check the methods it overrides.
+    if (auto* Method = dyn_cast<CXXMethodDecl>(Func)) {
+      addAllOverriddenMethodsRecursively(Method);
+    }
+  }
+
+  void FuncSetCallback::addAllOverriddenMethodsRecursively(
+      const CXXMethodDecl* Method) {
+    for (auto OverriddenMethod : Method->overridden_methods()) {
+      CanRunScriptFuncs.insert(OverriddenMethod);
+
+      // If this is not the definition, we also add the definition (if it
+      // exists) to the set.
+      if (!OverriddenMethod->isThisDeclarationADefinition()) {
+        if (auto Def = OverriddenMethod->getDefinition()) {
+          CanRunScriptFuncs.insert(Def);
+        }
+      }
+
+      addAllOverriddenMethodsRecursively(OverriddenMethod);
+    }
+  }
+} // namespace
+
+void CanRunScriptChecker::buildFuncSet(ASTContext *Context) {
+  // We create a match finder.
+  MatchFinder Finder;
+  // We create the callback which will be called when we find a function with
+  // a MOZ_CAN_RUN_SCRIPT annotation.
+  FuncSetCallback Callback(CanRunScriptFuncs);
+  // We add the matcher to the finder, linking it to our callback.
+  Finder.addMatcher(functionDecl(hasCanRunScriptAnnotation())
+                      .bind("canRunScriptFunction"),
+                    &Callback);
+
+  // We start the analysis, given the ASTContext our main checker is in.
+  Finder.matchAST(*Context);
+}
+
+void CanRunScriptChecker::check(
+    const MatchFinder::MatchResult &Result) {
+
+  // If the set of functions which can run script is not yet built, then build
+  // it.
+  if (!IsFuncSetBuilt) {
+    buildFuncSet(Result.Context);
+    IsFuncSetBuilt = true;
+  }
+
+  const char* ErrorInvalidArg =
+      "arguments must all be strong refs or parent parameters when calling a "
+      "function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object "
+      "argument)";
+
+  const char* ErrorNonCanRunScriptParent =
+      "functions marked as MOZ_CAN_RUN_SCRIPT can only be called from "
+      "functions also marked as MOZ_CAN_RUN_SCRIPT";
+  const char* NoteNonCanRunScriptParent =
+      "parent function declared here";
+
+  const Expr* InvalidArg = Result.Nodes.getNodeAs<Expr>("invalidArg");
+
+  const CallExpr* Call = Result.Nodes.getNodeAs<CallExpr>("callExpr");
+  // If we don't find the FunctionDecl linked to this call or if it's not marked
+  // as can-run-script, consider that we didn't find a match.
+  if (Call && (!Call->getDirectCallee() ||
+      !CanRunScriptFuncs.count(Call->getDirectCallee()))) {
+    Call = nullptr;
+  }
+
+  const CXXConstructExpr* Construct =
+      Result.Nodes.getNodeAs<CXXConstructExpr>("constructExpr");
+
+  // If we don't find the CXXConstructorDecl linked to this construct expression
+  // or if it's not marked as can-run-script, consider that we didn't find a
+  // match.
+  if (Construct && (!Construct->getConstructor() ||
+      !CanRunScriptFuncs.count(Construct->getConstructor()))) {
+    Construct = nullptr;
+  }
+
+  const FunctionDecl* ParentFunction =
+      Result.Nodes.getNodeAs<FunctionDecl>("nonCanRunScriptParentFunction");
+  // If the parent function can run script, consider that we didn't find a match
+  // because we only care about parent functions which can't run script.
+  if (ParentFunction && CanRunScriptFuncs.count(ParentFunction)) {
+    ParentFunction = nullptr;
+  }
+
+
+  // Get the call range from either the CallExpr or the ConstructExpr.
+  SourceRange CallRange;
+  if (Call) {
+    CallRange = Call->getSourceRange();
+  } else if (Construct) {
+    CallRange = Construct->getSourceRange();
+  } else {
+    // If we have neither a Call nor a Construct, we have nothing do to here.
+    return;
+  }
+
+  // If we have an invalid argument in the call, we emit the diagnostic to
+  // signal it.
+  if (InvalidArg) {
+    diag(CallRange.getBegin(), ErrorInvalidArg, DiagnosticIDs::Error)
+        << CallRange;
+  }
+
+  // If the parent function is not marked as MOZ_CAN_RUN_SCRIPT, we emit an
+  // error and a not indicating it.
+  if (ParentFunction) {
+    diag(CallRange.getBegin(), ErrorNonCanRunScriptParent, DiagnosticIDs::Error)
+        << CallRange;
+
+    diag(ParentFunction->getLocation(), NoteNonCanRunScriptParent,
+         DiagnosticIDs::Note);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/CanRunScriptChecker.h
@@ -0,0 +1,32 @@
+/* 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 CanRunScriptChecker_h__
+#define CanRunScriptChecker_h__
+
+#include "plugin.h"
+#include <unordered_set>
+
+class CanRunScriptChecker : public BaseCheck {
+public:
+  CanRunScriptChecker(StringRef CheckName,
+                      ContextType *Context = nullptr)
+    : BaseCheck(CheckName, Context) {}
+  void registerMatchers(MatchFinder* AstMatcher) override;
+  void check(const MatchFinder::MatchResult &Result) override;
+
+  // Simply initialize the can-run-script function set at the beginning of each
+  // translation unit.
+  void onStartOfTranslationUnit() override;
+
+private:
+  /// Runs the inner matcher on the AST to find all the can-run-script
+  /// functions using custom rules (not only the annotation).
+  void buildFuncSet(ASTContext *Context);
+
+  bool IsFuncSetBuilt;
+  std::unordered_set<const FunctionDecl*> CanRunScriptFuncs;
+};
+
+#endif
--- 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(CanRunScriptChecker, "can-run-script")
 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")
--- 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 "CanRunScriptChecker.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"
--- a/build/clang-plugin/CustomMatchers.h
+++ b/build/clang-plugin/CustomMatchers.h
@@ -60,16 +60,22 @@ AST_MATCHER(CXXMethodDecl, noDanglingOnT
 }
 
 /// 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 any function declaration that is marked as being
+/// allowed to run script.
+AST_MATCHER(FunctionDecl, hasCanRunScriptAnnotation) {
+  return hasCustomAnnotation(&Node, "moz_can_run_script");
+}
+
 /// This matcher will match all arithmetic binary operators.
 AST_MATCHER(BinaryOperator, binaryArithmeticOperator) {
   BinaryOperatorKind OpCode = Node.getOpcode();
   return OpCode == BO_Mul || OpCode == BO_Div || OpCode == BO_Rem ||
          OpCode == BO_Add || OpCode == BO_Sub || OpCode == BO_Shl ||
          OpCode == BO_Shr || OpCode == BO_And || OpCode == BO_Xor ||
          OpCode == BO_Or || OpCode == BO_MulAssign || OpCode == BO_DivAssign ||
          OpCode == BO_RemAssign || OpCode == BO_AddAssign ||
@@ -132,21 +138,25 @@ AST_MATCHER(MemberExpr, isAddRefOrReleas
   CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Member);
   if (Method) {
     const auto &Name = getNameChecked(Method);
     return Name == "AddRef" || Name == "Release";
   }
   return false;
 }
 
-/// This matcher will select classes which are refcounted.
+/// This matcher will select classes which are refcounted AND have an mRefCnt
+/// member.
 AST_MATCHER(CXXRecordDecl, hasRefCntMember) {
   return isClassRefCounted(&Node) && getClassRefCntMember(&Node);
 }
 
+/// This matcher will select classes which are refcounted.
+AST_MATCHER(CXXRecordDecl, isRefCounted) { return isClassRefCounted(&Node); }
+
 AST_MATCHER(QualType, hasVTable) { return typeHasVTable(Node); }
 
 AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) {
   return hasCustomAnnotation(&Node, "moz_needs_no_vtable_type");
 }
 
 /// This matcher will select classes which are non-memmovable
 AST_MATCHER(QualType, isNonMemMovable) {
@@ -234,16 +244,27 @@ AST_MATCHER(CallExpr, isSnprintfLikeFunc
   return !isIgnoredPathForSprintfLiteral(
       &Node, Finder->getASTContext().getSourceManager());
 }
 
 AST_MATCHER(CXXRecordDecl, isLambdaDecl) { return Node.isLambda(); }
 
 AST_MATCHER(QualType, isRefPtr) { return typeIsRefPtr(Node); }
 
+AST_MATCHER(QualType, isSmartPtrToRefCounted) {
+  auto *D = getNonTemplateSpecializedCXXRecordDecl(Node);
+  if (!D) {
+    return false;
+  }
+
+  D = D->getCanonicalDecl();
+
+  return D && hasCustomAnnotation(D, "moz_is_smartptr_to_refcounted");
+}
+
 AST_MATCHER(CXXRecordDecl, hasBaseClasses) {
   const CXXRecordDecl *Decl = Node.getCanonicalDecl();
 
   // Must have definition and should inherit other classes
   return Decl && Decl->hasDefinition() && Decl->getNumBases();
 }
 
 AST_MATCHER(CXXMethodDecl, isRequiredBaseMethod) {
@@ -255,12 +276,56 @@ AST_MATCHER(CXXMethodDecl, isNonVirtual)
   const CXXMethodDecl *Decl = Node.getCanonicalDecl();
   return Decl && !Decl->isVirtual();
 }
 
 AST_MATCHER(FunctionDecl, isMozMustReturnFromCaller) {
   const FunctionDecl *Decl = Node.getCanonicalDecl();
   return Decl && hasCustomAnnotation(Decl, "moz_must_return_from_caller");
 }
+
+#if CLANG_VERSION_FULL < 309
+/// DISCLAIMER: This is a copy/paste from the Clang source code starting from
+/// Clang 3.9, so that this matcher is supported in lower versions.
+///
+/// \brief Matches declaration of the function the statement belongs to
+///
+/// Given:
+/// \code
+/// F& operator=(const F& o) {
+///   std::copy_if(o.begin(), o.end(), begin(), [](V v) { return v > 0; });
+///   return *this;
+/// }
+/// \endcode
+/// returnStmt(forFunction(hasName("operator=")))
+///   matches 'return *this'
+///   but does match 'return > 0'
+AST_MATCHER_P(Stmt, forFunction, internal::Matcher<FunctionDecl>,
+              InnerMatcher) {
+  const auto &Parents = Finder->getASTContext().getParents(Node);
+
+  llvm::SmallVector<ast_type_traits::DynTypedNode, 8> Stack(Parents.begin(),
+                                                            Parents.end());
+  while(!Stack.empty()) {
+    const auto &CurNode = Stack.back();
+    Stack.pop_back();
+    if(const auto *FuncDeclNode = CurNode.get<FunctionDecl>()) {
+      if(InnerMatcher.matches(*FuncDeclNode, Finder, Builder)) {
+        return true;
+      }
+    } else if(const auto *LambdaExprNode = CurNode.get<LambdaExpr>()) {
+      if(InnerMatcher.matches(*LambdaExprNode->getCallOperator(),
+                              Finder, Builder)) {
+        return true;
+      }
+    } else {
+      for(const auto &Parent: Finder->getASTContext().getParents(CurNode))
+        Stack.push_back(Parent);
+    }
+  }
+  return false;
+}
+#endif
+
 }
 }
 
 #endif
--- a/build/clang-plugin/Utils.h
+++ b/build/clang-plugin/Utils.h
@@ -423,16 +423,39 @@ inline bool inThirdPartyPath(SourceLocat
 inline bool inThirdPartyPath(const Decl *D, ASTContext *context) {
   D = D->getCanonicalDecl();
   SourceLocation Loc = D->getLocation();
   const SourceManager &SM = context->getSourceManager();
 
   return inThirdPartyPath(Loc, SM);
 }
 
+inline CXXRecordDecl* getNonTemplateSpecializedCXXRecordDecl(QualType Q) {
+  auto *D = Q->getAsCXXRecordDecl();
+
+  if (!D) {
+    auto TemplateQ = Q->getAs<TemplateSpecializationType>();
+    if (!TemplateQ) {
+      return nullptr;
+    }
+
+    auto TemplateDecl = TemplateQ->getTemplateName().getAsTemplateDecl();
+    if (!TemplateDecl) {
+      return nullptr;
+    }
+
+    D = dyn_cast_or_null<CXXRecordDecl>(TemplateDecl->getTemplatedDecl());
+    if (!D) {
+      return nullptr;
+    }
+  }
+
+  return D;
+}
+
 inline bool inThirdPartyPath(const Decl *D) {
   return inThirdPartyPath(D, &D->getASTContext());
 }
 
 inline bool inThirdPartyPath(const Stmt *S, ASTContext *context) {
   SourceLocation Loc = S->getLocStart();
   const SourceManager &SM = context->getSourceManager();
 
--- a/build/clang-plugin/moz.build
+++ b/build/clang-plugin/moz.build
@@ -6,16 +6,17 @@
 
 SharedLibrary('clang-plugin')
 
 SOURCES += ['!ThirdPartyPaths.cpp']
 
 UNIFIED_SOURCES += [
     'ArithmeticArgChecker.cpp',
     'AssertAssignmentChecker.cpp',
+    'CanRunScriptChecker.cpp',
     'CustomTypeAnnotation.cpp',
     'DanglingOnTemporaryChecker.cpp',
     'DiagnosticsMatcher.cpp',
     'ExplicitImplicitChecker.cpp',
     'ExplicitOperatorBoolChecker.cpp',
     'KungFuDeathGripChecker.cpp',
     'MozCheckAction.cpp',
     'MustOverrideChecker.cpp',
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestCanRunScript.cpp
@@ -0,0 +1,107 @@
+#include <mozilla/RefPtr.h>
+
+#define MOZ_CAN_RUN_SCRIPT __attribute__((annotate("moz_can_run_script")))
+
+MOZ_CAN_RUN_SCRIPT void test() {
+
+}
+
+void test_parent() { // expected-note {{parent function declared here}}
+  test(); // expected-error {{functions marked as MOZ_CAN_RUN_SCRIPT can only be called from functions also marked as MOZ_CAN_RUN_SCRIPT}}
+}
+
+MOZ_CAN_RUN_SCRIPT void test_parent2() {
+  test();
+}
+
+struct RefCountedBase;
+MOZ_CAN_RUN_SCRIPT void test2(RefCountedBase* param) {
+
+}
+
+struct RefCountedBase {
+  void AddRef();
+  void Release();
+
+  MOZ_CAN_RUN_SCRIPT void method_test() {
+    test();
+  }
+
+  MOZ_CAN_RUN_SCRIPT void method_test2() {
+    test2(this);
+  }
+
+  virtual void method_test3() {
+    test();
+  }
+};
+
+void test2_parent() { // expected-note {{parent function declared here}}
+  test2(new RefCountedBase); // expected-error {{arguments must all be strong refs or parent parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument)}} \
+                             // expected-error {{functions marked as MOZ_CAN_RUN_SCRIPT can only be called from functions also marked as MOZ_CAN_RUN_SCRIPT}}
+}
+
+MOZ_CAN_RUN_SCRIPT void test2_parent2() {
+  test2(new RefCountedBase); // expected-error {{arguments must all be strong refs or parent parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument)}}
+}
+
+MOZ_CAN_RUN_SCRIPT void test2_parent3(RefCountedBase* param) {
+  test2(param);
+}
+
+MOZ_CAN_RUN_SCRIPT void test2_parent4() {
+  RefPtr<RefCountedBase> refptr = new RefCountedBase;
+  test2(refptr);
+}
+
+MOZ_CAN_RUN_SCRIPT void test2_parent5() {
+  test2(MOZ_KnownLive(new RefCountedBase));
+}
+
+MOZ_CAN_RUN_SCRIPT void test2_parent6() {
+  RefPtr<RefCountedBase> refptr = new RefCountedBase;
+  refptr->method_test();
+  refptr->method_test2();
+}
+
+MOZ_CAN_RUN_SCRIPT void test2_parent7() {
+  RefCountedBase* t = new RefCountedBase;
+  t->method_test(); // expected-error {{arguments must all be strong refs or parent parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument)}}
+  t->method_test2(); // expected-error {{arguments must all be strong refs or parent parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument)}}
+}
+
+MOZ_CAN_RUN_SCRIPT void test3(int* param) {}
+
+MOZ_CAN_RUN_SCRIPT void test3_parent() {
+  test3(new int);
+}
+
+struct RefCountedChild : public RefCountedBase {
+  virtual void method_test3() override;
+};
+
+void RefCountedChild::method_test3() {
+  test();
+}
+
+struct RefCountedSubChild : public RefCountedChild {
+  MOZ_CAN_RUN_SCRIPT void method_test3() override;
+};
+
+void RefCountedSubChild::method_test3() {
+  test();
+}
+
+MOZ_CAN_RUN_SCRIPT void test4() {
+  RefPtr<RefCountedBase> refptr1 = new RefCountedChild;
+  refptr1->method_test3();
+
+  RefPtr<RefCountedBase> refptr2 = new RefCountedSubChild;
+  refptr2->method_test3();
+
+  RefPtr<RefCountedChild> refptr3 = new RefCountedSubChild;
+  refptr3->method_test3();
+
+  RefPtr<RefCountedSubChild> refptr4 = new RefCountedSubChild;
+  refptr4->method_test3();
+}
\ No newline at end of file
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # dummy library name to avoid skipping building the sources here.
 Library('clang-plugin-tests')
 
 SOURCES += [
     'TestAssertWithAssignment.cpp',
     'TestBadImplicitConversionCtor.cpp',
+    'TestCanRunScript.cpp',
     'TestCustomHeap.cpp',
     'TestDanglingOnTemporary.cpp',
     'TestExplicitOperatorBool.cpp',
     'TestGlobalClass.cpp',
     'TestHeapClass.cpp',
     'TestInheritTypeAnnotationsFromTemplateArgs.cpp',
     'TestKungFuDeathGrip.cpp',
     'TestMultipleAnnotations.cpp',
--- a/mfbt/Attributes.h
+++ b/mfbt/Attributes.h
@@ -421,16 +421,19 @@
  *
  *   MOZ_LABEL_ATTRIBUTE target:
  *     goto target;
  *   MOZ_CASE_ATTRIBUTE case 5:
  *   MOZ_DEFAULT_ATTRIBUTE default:
  *
  * The static analyses that are performed by the plugin are as follows:
  *
+ * MOZ_CAN_RUN_SCRIPT: Applies to functions which can run script. Callers of
+ *   this function must also be marked as MOZ_CAN_RUN_SCRIPT, and all refcounted
+ *   arguments must be strongly held in the caller.
  * MOZ_MUST_OVERRIDE: Applies to all C++ member functions. All immediate
  *   subclasses must provide an exact override of this method; if a subclass
  *   does not override this method, the compiler will emit an error. This
  *   attribute is not limited to virtual methods, so if it is applied to a
  *   nonvirtual method and the subclass does not provide an equivalent
  *   definition, the compiler will emit an error.
  * MOZ_STACK_CLASS: Applies to all classes. Any class with this annotation is
  *   expected to live on the stack, so it is a compile-time error to use it, or
@@ -473,16 +476,24 @@
  *   non-trivial constructor or destructor for any reason.
  * MOZ_HEAP_ALLOCATOR: Applies to any function. This indicates that the return
  *   value is allocated on the heap, and will as a result check such allocations
  *   during MOZ_STACK_CLASS and MOZ_NONHEAP_CLASS annotation checking.
  * MOZ_IMPLICIT: Applies to constructors. Implicit conversion constructors
  *   are disallowed by default unless they are marked as MOZ_IMPLICIT. This
  *   attribute must be used for constructors which intend to provide implicit
  *   conversions.
+ * MOZ_IS_REFPTR: Applies to class declarations of ref pointer to mark them as
+ *   such for use with static-analysis.
+ *   A ref pointer is an object wrapping a pointer and automatically taking care
+ *   of its refcounting upon construction/destruction/transfer of ownership.
+ *   This annotation implies MOZ_IS_SMARTPTR_TO_REFCOUNTED.
+ * MOZ_IS_SMARTPTR_TO_REFCOUNTED: Applies to class declarations of smart
+ *   pointers to ref counted classes to mark them as such for use with
+ *   static-analysis.
  * MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT: Applies to functions. Makes it a compile
  *   time error to pass arithmetic expressions on variables to the function.
  * MOZ_OWNING_REF: Applies to declarations of pointers to reference counted
  *   types.  This attribute tells the compiler that the raw pointer is a strong
  *   reference, where ownership through methods such as AddRef and Release is
  *   managed manually.  This can make the compiler ignore these pointers when
  *   validating the usage of pointers otherwise.
  *
@@ -571,30 +582,34 @@
  *   Only calls to Constructors, references to local and member variables,
  *   and calls to functions or methods marked as MOZ_MAY_CALL_AFTER_MUST_RETURN
  *   may be made after the MUST_RETURN_FROM_CALLER call.
  * MOZ_MAY_CALL_AFTER_MUST_RETURN: Applies to function or method declarations.
  *   Calls to these methods may be made in functions after calls a
  *   MOZ_MUST_RETURN_FROM_CALLER function or method.
  */
 #ifdef MOZ_CLANG_PLUGIN
+#  define MOZ_CAN_RUN_SCRIPT __attribute__((annotate("moz_can_run_script")))
 #  define MOZ_MUST_OVERRIDE __attribute__((annotate("moz_must_override")))
 #  define MOZ_STACK_CLASS __attribute__((annotate("moz_stack_class")))
 #  define MOZ_NONHEAP_CLASS __attribute__((annotate("moz_nonheap_class")))
 #  define MOZ_HEAP_CLASS __attribute__((annotate("moz_heap_class")))
 #  define MOZ_NON_TEMPORARY_CLASS __attribute__((annotate("moz_non_temporary_class")))
 #  define MOZ_TRIVIAL_CTOR_DTOR __attribute__((annotate("moz_trivial_ctor_dtor")))
 #  ifdef DEBUG
      /* in debug builds, these classes do have non-trivial constructors. */
 #    define MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS __attribute__((annotate("moz_global_class")))
 #  else
 #    define MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS __attribute__((annotate("moz_global_class"))) \
             MOZ_TRIVIAL_CTOR_DTOR
 #  endif
 #  define MOZ_IMPLICIT __attribute__((annotate("moz_implicit")))
+#  define MOZ_IS_SMARTPTR_TO_REFCOUNTED __attribute__((annotate("moz_is_smartptr_to_refcounted")))
+#  define MOZ_IS_REFPTR __attribute__((annotate("moz_is_refptr"))) \
+                        MOZ_IS_SMARTPTR_TO_REFCOUNTED
 #  define MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT __attribute__((annotate("moz_no_arith_expr_in_arg")))
 #  define MOZ_OWNING_REF __attribute__((annotate("moz_strong_ref")))
 #  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")))
@@ -622,24 +637,27 @@
  * anyways, so the warning is safe to ignore.
  */
 #  define MOZ_HEAP_ALLOCATOR \
     _Pragma("clang diagnostic push") \
     _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \
     __attribute__((annotate("moz_heap_allocator"))) \
     _Pragma("clang diagnostic pop")
 #else
+#  define MOZ_CAN_RUN_SCRIPT /* nothing */
 #  define MOZ_MUST_OVERRIDE /* nothing */
 #  define MOZ_STACK_CLASS /* nothing */
 #  define MOZ_NONHEAP_CLASS /* nothing */
 #  define MOZ_HEAP_CLASS /* nothing */
 #  define MOZ_NON_TEMPORARY_CLASS /* nothing */
 #  define MOZ_TRIVIAL_CTOR_DTOR /* nothing */
 #  define MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS /* nothing */
 #  define MOZ_IMPLICIT /* nothing */
+#  define MOZ_IS_SMARTPTR_TO_REFCOUNTED /* nothing */
+#  define MOZ_IS_REFPTR /* nothing */
 #  define MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT /* nothing */
 #  define MOZ_HEAP_ALLOCATOR /* nothing */
 #  define MOZ_OWNING_REF /* nothing */
 #  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 */
--- a/mfbt/RefPtr.h
+++ b/mfbt/RefPtr.h
@@ -39,17 +39,17 @@ struct RefPtrTraits
   static void Release(U* aPtr) {
     aPtr->Release();
   }
 };
 
 } // namespace mozilla
 
 template <class T>
-class RefPtr
+class MOZ_IS_REFPTR RefPtr
 {
 private:
   void
   assign_with_AddRef(T* aRawPtr)
   {
     if (aRawPtr) {
       ConstRemovingRefPtrTraits<T>::AddRef(aRawPtr);
     }
--- a/mfbt/StaticAnalysisFunctions.h
+++ b/mfbt/StaticAnalysisFunctions.h
@@ -16,20 +16,30 @@
  * Functions that are used as markers in Gecko code for static analysis. Their
  * purpose is to have different AST nodes generated during compile time and to
  * match them based on different checkers implemented in build/clang-plugin
  */
 
 #ifdef MOZ_CLANG_PLUGIN
 
 #ifdef __cplusplus
+/**
+ * MOZ_KnownLive - used to opt an argument out of the CanRunScript checker so
+ * that we don't check it if is a strong ref.
+ *
+ * Example:
+ * canRunScript(MOZ_KnownLive(rawPointer));
+ */
+template <typename T>
+static MOZ_ALWAYS_INLINE T* MOZ_KnownLive(T* ptr) { return ptr; }
+
 extern "C" {
 #endif
 
-/*
+/**
  * MOZ_AssertAssignmentTest - used in MOZ_ASSERT in order to test the possible
  * presence of assignment instead of logical comparisons.
  *
  * Example:
  * MOZ_ASSERT(retVal = true);
  */
 static MOZ_ALWAYS_INLINE bool MOZ_AssertAssignmentTest(bool exprResult) {
   return exprResult;
--- a/xpcom/base/OwningNonNull.h
+++ b/xpcom/base/OwningNonNull.h
@@ -10,17 +10,17 @@
 #define mozilla_OwningNonNull_h
 
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionNoteChild.h"
 
 namespace mozilla {
 
 template<class T>
-class OwningNonNull
+class MOZ_IS_SMARTPTR_TO_REFCOUNTED OwningNonNull
 {
 public:
   OwningNonNull() {}
 
   MOZ_IMPLICIT OwningNonNull(T& aValue)
   {
     init(&aValue);
   }
--- a/xpcom/base/nsCOMPtr.h
+++ b/xpcom/base/nsCOMPtr.h
@@ -340,17 +340,17 @@ protected:
 
 // Helper for assert_validity method
 template<class T>
 char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2];
 template<class T>
 char TestForIID(...);
 
 template<class T>
-class nsCOMPtr final
+class MOZ_IS_REFPTR nsCOMPtr final
 #ifdef NSCAP_FEATURE_USE_BASE
   : private nsCOMPtr_base
 #endif
 {
 
 #ifdef NSCAP_FEATURE_USE_BASE
   #define NSCAP_CTOR_BASE(x) nsCOMPtr_base(x)
 #else