diff --git a/build/clang-plugin/Checks.inc b/build/clang-plugin/Checks.inc index 2d90cbd92dda..db886eaca1ca 100644 --- a/build/clang-plugin/Checks.inc +++ b/build/clang-plugin/Checks.inc @@ -6,6 +6,7 @@ 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") diff --git a/build/clang-plugin/ChecksIncludes.inc b/build/clang-plugin/ChecksIncludes.inc index 56aa20c95bb6..d902190a3704 100644 --- a/build/clang-plugin/ChecksIncludes.inc +++ b/build/clang-plugin/ChecksIncludes.inc @@ -7,6 +7,7 @@ #include "ArithmeticArgChecker.h" #include "AssertAssignmentChecker.h" +#include "DanglingOnTemporaryChecker.h" #include "ExplicitImplicitChecker.h" #include "ExplicitOperatorBoolChecker.h" #include "KungFuDeathGripChecker.h" diff --git a/build/clang-plugin/CustomMatchers.h b/build/clang-plugin/CustomMatchers.h index 3206da380847..a53985d69d1d 100644 --- a/build/clang-plugin/CustomMatchers.h +++ b/build/clang-plugin/CustomMatchers.h @@ -7,6 +7,7 @@ #include "MemMoveAnnotation.h" #include "Utils.h" +#include "VariableUsageHelpers.h" namespace clang { namespace ast_matchers { @@ -29,6 +30,78 @@ 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, EscapeStmtMatcher, \ + internal::Matcher, EscapeDeclMatcher) { + auto Call = + IgnoreParentTrivials(Node, &Finder->getASTContext()).get(); + 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 +class HasNonTrivialParentMatcher : public internal::WrapperMatcherInterface { + static_assert(internal::IsBaseType::value, + "has parent only accepts base type matcher"); + +public: + explicit HasNonTrivialParentMatcher( + const internal::Matcher &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, + internal::TypeList> + 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) { diff --git a/build/clang-plugin/DanglingOnTemporaryChecker.cpp b/build/clang-plugin/DanglingOnTemporaryChecker.cpp new file mode 100644 index 000000000000..fdfa3870dbe7 --- /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("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 CXXMemberCallExpr *MemberCall = + Result.Nodes.getNodeAs("memberCallExpr"); + + // If we escaped the a parent function call, we get the statement and the + // associated declaration. + const Stmt *EscapeStmt = + Result.Nodes.getNodeAs("escapeStatement"); + const Decl *EscapeDecl = + Result.Nodes.getNodeAs("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(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 (auto FuncDecl = dyn_cast(EscapeDecl)) { + EscapeDeclNote = "through the return value of the function declared here"; + EscapeDeclRange = FuncDecl->getReturnTypeSourceRange(); + } else { + return; + } + + diag(EscapeDecl->getLocation(), EscapeDeclNote, DiagnosticIDs::Note) + << EscapeDeclRange; +} diff --git a/build/clang-plugin/DanglingOnTemporaryChecker.h b/build/clang-plugin/DanglingOnTemporaryChecker.h new file mode 100644 index 000000000000..43f19ebedca8 --- /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 diff --git a/build/clang-plugin/Utils.h b/build/clang-plugin/Utils.h index b34db951480a..39e406eb1b7b 100644 --- a/build/clang-plugin/Utils.h +++ b/build/clang-plugin/Utils.h @@ -307,6 +307,13 @@ inline bool typeIsRefPtr(QualType Q) { return false; } +// This method returns true if the statement is trivial. +inline bool IsTrivial(const Stmt *s) { + return s && (isa(s) || isa(s) || + isa(s) || isa(s) || + isa(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. @@ -329,6 +336,7 @@ inline const Stmt *IgnoreTrivials(const Stmt *s) { } } + assert(!IsTrivial(s)); return s; } @@ -336,6 +344,29 @@ inline const Expr *IgnoreTrivials(const Expr *e) { return cast(IgnoreTrivials(static_cast(e))); } +// This method is like IgnoreTrivials but ignores the nodes upwards instead of +// downwards. +template +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())); + + return CurrentNode; +} + const FieldDecl *getBaseRefCntMember(QualType T); inline const FieldDecl *getBaseRefCntMember(const CXXRecordDecl *D) { diff --git a/build/clang-plugin/VariableUsageHelpers.cpp b/build/clang-plugin/VariableUsageHelpers.cpp new file mode 100644 index 000000000000..11154664db27 --- /dev/null +++ b/build/clang-plugin/VariableUsageHelpers.cpp @@ -0,0 +1,163 @@ +#include "VariableUsageHelpers.h" +#include "Utils.h" + +std::vector +getUsageAsRvalue(const ValueDecl* ValueDeclaration, + const FunctionDecl* FuncDecl) { + std::vector UsageStatements; + + // We check the function declaration has a body. + auto Body = FuncDecl->getBody(); + if (!Body) { + return std::vector(); + } + + // We build a Control Flow Graph (CFG) fron the body of the function + // declaration. + std::unique_ptr 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 CFGStatement = BlockItem.getAs(); + 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(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(IgnoreTrivials(BinOp->getRHS())); + if (!DeclRef) { + continue; + } + + if (DeclRef->getDecl() != ValueDeclaration) { + continue; + } + } else if (auto Return = dyn_cast(CFGStatement->getStmt())) { + // We want our declaration to be used as the expression of the return + // statement. + auto DeclRef = dyn_cast_or_null( + 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> +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(Usage)) { + // We retrieve the declaration the parameter is assigned to. + auto DeclRef = dyn_cast(BinOp->getLHS()); + if (!DeclRef) { + continue; + } + + if (auto ParamDeclaration = dyn_cast(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(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(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}}; +} diff --git a/build/clang-plugin/VariableUsageHelpers.h b/build/clang-plugin/VariableUsageHelpers.h new file mode 100644 index 000000000000..990d995fc7cc --- /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 + +#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 +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> +escapesFunction(const Expr* Arg, const CallExpr* Call); + +#endif diff --git a/build/clang-plugin/moz.build b/build/clang-plugin/moz.build index a4f625a2a7b2..0c31d3d65795 100644 --- a/build/clang-plugin/moz.build +++ b/build/clang-plugin/moz.build @@ -12,6 +12,7 @@ UNIFIED_SOURCES += [ 'ArithmeticArgChecker.cpp', 'AssertAssignmentChecker.cpp', 'CustomTypeAnnotation.cpp', + 'DanglingOnTemporaryChecker.cpp', 'DiagnosticsMatcher.cpp', 'ExplicitImplicitChecker.cpp', 'ExplicitOperatorBoolChecker.cpp', @@ -36,6 +37,7 @@ UNIFIED_SOURCES += [ 'ScopeChecker.cpp', 'SprintfLiteralChecker.cpp', 'TrivialCtorDtorChecker.cpp', + 'VariableUsageHelpers.cpp', ] GENERATED_FILES += ['ThirdPartyPaths.cpp'] diff --git a/build/clang-plugin/tests/TestDanglingOnTemporary.cpp b/build/clang-plugin/tests/TestDanglingOnTemporary.cpp new file mode 100644 index 000000000000..b49c170107d1 --- /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}} +} diff --git a/build/clang-plugin/tests/moz.build b/build/clang-plugin/tests/moz.build index c914d661fd5f..52aa2a73323a 100644 --- a/build/clang-plugin/tests/moz.build +++ b/build/clang-plugin/tests/moz.build @@ -11,6 +11,7 @@ SOURCES += [ 'TestAssertWithAssignment.cpp', 'TestBadImplicitConversionCtor.cpp', 'TestCustomHeap.cpp', + 'TestDanglingOnTemporary.cpp', 'TestExplicitOperatorBool.cpp', 'TestGlobalClass.cpp', 'TestHeapClass.cpp', diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index 44e8f14b6cab..8eed1e4a164b 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -526,6 +526,14 @@ * 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 @@ -587,6 +595,7 @@ # 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"))) @@ -632,6 +641,7 @@ # 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 */