diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /build/clang-plugin | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'build/clang-plugin')
177 files changed, 16318 insertions, 0 deletions
diff --git a/build/clang-plugin/.clang-format b/build/clang-plugin/.clang-format new file mode 100644 index 0000000000..9b3aa8b721 --- /dev/null +++ b/build/clang-plugin/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/build/clang-plugin/ArithmeticArgChecker.cpp b/build/clang-plugin/ArithmeticArgChecker.cpp new file mode 100644 index 0000000000..0042961b32 --- /dev/null +++ b/build/clang-plugin/ArithmeticArgChecker.cpp @@ -0,0 +1,60 @@ +/* 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 "ArithmeticArgChecker.h" +#include "CustomMatchers.h" + +void ArithmeticArgChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + callExpr(allOf(hasDeclaration(noArithmeticExprInArgs()), + anyOf(hasDescendant( + binaryOperator( + allOf(binaryArithmeticOperator(), + hasLHS(hasDescendant(declRefExpr())), + hasRHS(hasDescendant(declRefExpr())))) + .bind("node")), + hasDescendant( + unaryOperator( + allOf(unaryArithmeticOperator(), + hasUnaryOperand(allOf( + hasType(builtinType()), + anyOf(hasDescendant(declRefExpr()), + declRefExpr()))))) + .bind("node"))))) + .bind("call"), + this); + AstMatcher->addMatcher( + cxxConstructExpr( + allOf(hasDeclaration(noArithmeticExprInArgs()), + anyOf(hasDescendant( + binaryOperator( + allOf(binaryArithmeticOperator(), + hasLHS(hasDescendant(declRefExpr())), + hasRHS(hasDescendant(declRefExpr())))) + .bind("node")), + hasDescendant( + unaryOperator( + allOf(unaryArithmeticOperator(), + hasUnaryOperand(allOf( + hasType(builtinType()), + anyOf(hasDescendant(declRefExpr()), + declRefExpr()))))) + .bind("node"))))) + .bind("call"), + this); +} + +void ArithmeticArgChecker::check(const MatchFinder::MatchResult &Result) { + const char *Error = + "cannot pass an arithmetic expression of built-in types to %0"; + const Expr *Expression = Result.Nodes.getNodeAs<Expr>("node"); + if (const CallExpr *Call = Result.Nodes.getNodeAs<CallExpr>("call")) { + diag(Expression->getBeginLoc(), Error, DiagnosticIDs::Error) + << Call->getDirectCallee(); + } else if (const CXXConstructExpr *Ctr = + Result.Nodes.getNodeAs<CXXConstructExpr>("call")) { + diag(Expression->getBeginLoc(), Error, DiagnosticIDs::Error) + << Ctr->getConstructor(); + } +} diff --git a/build/clang-plugin/ArithmeticArgChecker.h b/build/clang-plugin/ArithmeticArgChecker.h new file mode 100644 index 0000000000..62165b716b --- /dev/null +++ b/build/clang-plugin/ArithmeticArgChecker.h @@ -0,0 +1,18 @@ +/* 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 ArithmeticArgChecker_h__ +#define ArithmeticArgChecker_h__ + +#include "plugin.h" + +class ArithmeticArgChecker : public BaseCheck { +public: + ArithmeticArgChecker(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/AssertAssignmentChecker.cpp b/build/clang-plugin/AssertAssignmentChecker.cpp new file mode 100644 index 0000000000..467de28d63 --- /dev/null +++ b/build/clang-plugin/AssertAssignmentChecker.cpp @@ -0,0 +1,20 @@ +/* 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 "AssertAssignmentChecker.h" +#include "CustomMatchers.h" + +void AssertAssignmentChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + callExpr(isAssertAssignmentTestFunc()).bind("funcCall"), this); +} + +void AssertAssignmentChecker::check(const MatchFinder::MatchResult &Result) { + const CallExpr *FuncCall = Result.Nodes.getNodeAs<CallExpr>("funcCall"); + + if (FuncCall && hasSideEffectAssignment(FuncCall)) { + diag(FuncCall->getBeginLoc(), "Forbidden assignment in assert expression", + DiagnosticIDs::Error); + } +} diff --git a/build/clang-plugin/AssertAssignmentChecker.h b/build/clang-plugin/AssertAssignmentChecker.h new file mode 100644 index 0000000000..5e47b62183 --- /dev/null +++ b/build/clang-plugin/AssertAssignmentChecker.h @@ -0,0 +1,18 @@ +/* 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 AssertAssignmentChecker_h__ +#define AssertAssignmentChecker_h__ + +#include "plugin.h" + +class AssertAssignmentChecker : public BaseCheck { +public: + AssertAssignmentChecker(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/BaseCheck.h b/build/clang-plugin/BaseCheck.h new file mode 100644 index 0000000000..867b82d2ad --- /dev/null +++ b/build/clang-plugin/BaseCheck.h @@ -0,0 +1,34 @@ +/* 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 BaseCheck_h__ +#define BaseCheck_h__ + +class MozContext {}; +typedef MozContext ContextType; + +class BaseCheck : public MatchFinder::MatchCallback { +public: + BaseCheck(StringRef CheckName, ContextType *Context) {} + virtual void registerMatchers(MatchFinder *Finder) {} + virtual void registerPPCallbacks(CompilerInstance &CI) {} + virtual void check(const MatchFinder::MatchResult &Result) {} + DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, + DiagnosticIDs::Level Level = DiagnosticIDs::Warning) { + DiagnosticsEngine &Diag = Context->getDiagnostics(); + unsigned ID = Diag.getDiagnosticIDs()->getCustomDiagID(Level, Description); + return Diag.Report(Loc, ID); + } + +private: + void run(const MatchFinder::MatchResult &Result) override { + Context = Result.Context; + check(Result); + } + +private: + ASTContext *Context; +}; + +#endif diff --git a/build/clang-plugin/CanRunScriptChecker.cpp b/build/clang-plugin/CanRunScriptChecker.cpp new file mode 100644 index 0000000000..613fe81e19 --- /dev/null +++ b/build/clang-plugin/CanRunScriptChecker.cpp @@ -0,0 +1,450 @@ +/* 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/. */ + +/** + * This checker implements the "can run script" analysis. The idea is to detect + * functions that can run script that are being passed reference-counted + * arguments (including "this") whose refcount might go to zero as a result of + * the script running. We want to prevent that. + * + * The approach is to attempt to enforce the following invariants on the call + * graph: + * + * 1) Any caller of a MOZ_CAN_RUN_SCRIPT function is itself MOZ_CAN_RUN_SCRIPT. + * 2) If a virtual MOZ_CAN_RUN_SCRIPT method overrides a base class method, + * that base class method is also MOZ_CAN_RUN_SCRIPT. + * + * Invariant 2 ensures that we don't accidentally call a MOZ_CAN_RUN_SCRIPT + * function via a base-class virtual call. Invariant 1 ensures that + * the property of being able to run script propagates up the callstack. There + * is an opt-out for invariant 1: A function (declaration _or_ implementation) + * can be decorated with MOZ_CAN_RUN_SCRIPT_BOUNDARY to indicate that we do not + * require it or any of its callers to be MOZ_CAN_RUN_SCRIPT even if it calls + * MOZ_CAN_RUN_SCRIPT functions. + * + * There are two known holes in invariant 1, apart from the + * MOZ_CAN_RUN_SCRIPT_BOUNDARY opt-out: + * + * - Functions called via function pointers can be MOZ_CAN_RUN_SCRIPT even if + * their caller is not, because we have no way to determine from the function + * pointer what function is being called. + * - MOZ_CAN_RUN_SCRIPT destructors can happen in functions that are not + * MOZ_CAN_RUN_SCRIPT. + * https://bugzilla.mozilla.org/show_bug.cgi?id=1535523 tracks this. + * + * Given those invariants we then require that when calling a MOZ_CAN_RUN_SCRIPT + * function all refcounted arguments (including "this") satisfy one of these + * conditions: + * a) The argument is held via a strong pointer on the stack. + * b) The argument is a const strong pointer member of "this". We know "this" + * is being kept alive, and a const strong pointer member can't drop its ref + * until "this" dies. + * c) The argument is an argument of the caller (and hence held by a strong + * pointer somewhere higher up the callstack). + * d) The argument is explicitly annotated with MOZ_KnownLive, which indicates + * that something is guaranteed to keep it alive (e.g. it's rooted via a JS + * reflector). + * e) The argument is constexpr and therefore cannot disappear. + */ + +#include "CanRunScriptChecker.h" +#include "CustomMatchers.h" +#include "clang/Lex/Lexer.h" + +void CanRunScriptChecker::registerMatchers(MatchFinder *AstMatcher) { + auto Refcounted = qualType(hasDeclaration(cxxRecordDecl(isRefCounted()))); + auto StackSmartPtr = ignoreTrivials(declRefExpr(to(varDecl( + hasAutomaticStorageDuration(), hasType(isSmartPtrToRefCounted()))))); + auto ConstMemberOfThisSmartPtr = + memberExpr(hasType(isSmartPtrToRefCounted()), hasType(isConstQualified()), + hasObjectExpression(cxxThisExpr())); + // A smartptr can be known-live for three reasons: + // 1) It's declared on the stack. + // 2) It's a const member of "this". We know "this" is alive (recursively) + // and const members can't change their value hence can't drop their + // reference until "this" gets destroyed. + // 3) It's an immediate temporary being constructed at the point where the + // call is happening. + auto KnownLiveSmartPtr = anyOf( + StackSmartPtr, ConstMemberOfThisSmartPtr, + ignoreTrivials(cxxConstructExpr(hasType(isSmartPtrToRefCounted())))); + + auto MozKnownLiveCall = + ignoreTrivials(callExpr(callee(functionDecl(hasName("MOZ_KnownLive"))))); + + // Params of the calling function are presumed live, because it itself should + // be MOZ_CAN_RUN_SCRIPT. Note that this is subject to + // https://bugzilla.mozilla.org/show_bug.cgi?id=1537656 at the moment. + auto KnownLiveParam = anyOf( + // "this" is OK + cxxThisExpr(), + // A parameter of the calling function is OK. + declRefExpr(to(parmVarDecl()))); + + auto KnownLiveMemberOfParam = + memberExpr(hasKnownLiveAnnotation(), + hasObjectExpression(anyOf( + ignoreTrivials(KnownLiveParam), + declRefExpr(to(varDecl(hasAutomaticStorageDuration())))))); + + // A matcher that matches various things that are known to be live directly, + // without making any assumptions about operators. + auto KnownLiveBaseExceptRef = anyOf( + // Things that are known to be a stack or immutable refptr. + KnownLiveSmartPtr, + // MOZ_KnownLive() calls. + MozKnownLiveCall, + // Params of the caller function. + KnownLiveParam, + // Members of the params that are marked as MOZ_KNOWN_LIVE + KnownLiveMemberOfParam, + // Constexpr things. + declRefExpr(to(varDecl(isConstexpr())))); + + // A reference of smart ptr which is initialized with known live thing is OK. + // FIXME: This does not allow nested references. + auto RefToKnownLivePtr = ignoreTrivials(declRefExpr(to(varDecl( + hasAutomaticStorageDuration(), hasType(referenceType()), + hasInitializer(anyOf( + KnownLiveSmartPtr, KnownLiveParam, KnownLiveMemberOfParam, + conditionalOperator( + hasFalseExpression(ignoreTrivials(anyOf( + KnownLiveSmartPtr, KnownLiveParam, KnownLiveMemberOfParam, + declRefExpr(to(varDecl(isConstexpr()))), + // E.g., for RefPtr<T>::operator*() + cxxOperatorCallExpr( + hasOverloadedOperatorName("*"), + hasAnyArgument( + anyOf(KnownLiveBaseExceptRef, + ignoreTrivials(KnownLiveMemberOfParam))), + argumentCountIs(1)), + // E.g., for *T + unaryOperator(unaryDereferenceOperator(), + hasUnaryOperand( + ignoreTrivials(KnownLiveBaseExceptRef)))))), + hasTrueExpression(ignoreTrivials(anyOf( + KnownLiveSmartPtr, KnownLiveParam, KnownLiveMemberOfParam, + declRefExpr(to(varDecl(isConstexpr()))), + // E.g., for RefPtr<T>::operator*() + cxxOperatorCallExpr( + hasOverloadedOperatorName("*"), + hasAnyArgument( + anyOf(KnownLiveBaseExceptRef, + ignoreTrivials(KnownLiveMemberOfParam))), + argumentCountIs(1)), + // E.g., for *T + unaryOperator(unaryDereferenceOperator(), + hasUnaryOperand(ignoreTrivials( + KnownLiveBaseExceptRef))))))))))))); + + // A matcher that matches various things that are known to be live directly, + // without making any assumptions about operators. + auto KnownLiveBase = + anyOf(KnownLiveBaseExceptRef, + // Smart pointer refs initialized with known live smart ptrs. + RefToKnownLivePtr); + + // A matcher that matches various known-live things that don't involve + // non-unary operators. + auto KnownLiveSimple = anyOf( + // Things that are just known live. + KnownLiveBase, + // Method calls on a live things that are smart ptrs. Note that we don't + // want to allow general method calls on live things, because those can + // return non-live objects (e.g. consider "live_pointer->foo()" as an + // example). For purposes of this analysis we are assuming the method + // calls on smart ptrs all just return the pointer inside, + cxxMemberCallExpr( + on(anyOf(allOf(hasType(isSmartPtrToRefCounted()), KnownLiveBase), + // Allow it if calling a member method which is marked as + // MOZ_KNOWN_LIVE + KnownLiveMemberOfParam))), + // operator* or operator-> on a thing that is already known to be live. + cxxOperatorCallExpr( + hasAnyOverloadedOperatorName("*", "->"), + hasAnyArgument( + anyOf(KnownLiveBase, ignoreTrivials(KnownLiveMemberOfParam))), + argumentCountIs(1)), + // A dereference on a thing that is known to be live. This is _not_ + // caught by the "operator* or operator->" clause above, because + // cxxOperatorCallExpr() only catches cases when a class defines + // operator*. The default (built-in) operator* matches unaryOperator() + // instead.), + unaryOperator( + unaryDereferenceOperator(), + hasUnaryOperand( + // If we're doing *someArg, the argument of the dereference is an + // ImplicitCastExpr LValueToRValue which has the DeclRefExpr as an + // argument. We could try to match that explicitly with a custom + // matcher (none of the built-in matchers seem to match on the + // thing being cast for an implicitCastExpr), but it's simpler to + // just use ignoreTrivials to strip off the cast. + ignoreTrivials(KnownLiveBase))), + // Taking a pointer to a live reference. We explicitly want to exclude + // things that are not of type reference-to-refcounted or type refcounted, + // because if someone takes a pointer to a pointer to refcounted or a + // pointer to a smart ptr and passes those in to a callee that definitely + // does not guarantee liveness; in fact the callee could modify those + // things! In practice they would be the wrong type anyway, though, so + // it's hard to add a test for this. + unaryOperator(hasOperatorName("&"), + hasUnaryOperand(allOf(anyOf(hasType(references(Refcounted)), + hasType(Refcounted)), + ignoreTrivials(KnownLiveBase))))); + + auto KnownLive = anyOf( + // Anything above, of course. + KnownLiveSimple, + // Conditional operators where both arms are live. + conditionalOperator(hasFalseExpression(ignoreTrivials(KnownLiveSimple)), + hasTrueExpression(ignoreTrivials(KnownLiveSimple))) + // We're not handling cases like a dereference of a conditional operator, + // mostly because handling a dereference in general is so ugly. I + // _really_ wish I could just write a recursive matcher here easily. + ); + + auto InvalidArg = ignoreTrivialsConditional( + // We want to consider things if there is anything refcounted involved, + // including in any of the trivials that we otherwise strip off. + anyOf(hasType(Refcounted), hasType(pointsTo(Refcounted)), + hasType(references(Refcounted)), hasType(isSmartPtrToRefCounted())), + // We want to find any expression, + expr( + // which is not known live, + unless(KnownLive), + // and which is not a default arg with value nullptr, since those are + // always safe, + unless(cxxDefaultArgExpr(isNullDefaultArg())), + // and which is not a literal nullptr, + unless(cxxNullPtrLiteralExpr()), expr().bind("invalidArg"))); + + // A matcher which will mark the first invalid argument it finds invalid, but + // will always match, even if it finds no invalid arguments, so it doesn't + // preclude other matchers from running and maybe finding invalid args. + auto OptionalInvalidExplicitArg = anyOf( + // We want to find any argument which is invalid. + hasAnyArgument(InvalidArg), + + // This makes this matcher optional. + anything()); + + // Please note 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 this argument, + anyOf(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 to the +/// can-run-script function set and making sure the functions they override (if +/// any) also have the annotation. +class FuncSetCallback : public MatchFinder::MatchCallback { +public: + FuncSetCallback(CanRunScriptChecker &Checker, + std::unordered_set<const FunctionDecl *> &FuncSet) + : CanRunScriptFuncs(FuncSet), Checker(Checker) {} + + void run(const MatchFinder::MatchResult &Result) override; + +private: + /// This method checks the methods overriden by the given parameter. + void checkOverriddenMethods(const CXXMethodDecl *Method); + + std::unordered_set<const FunctionDecl *> &CanRunScriptFuncs; + CanRunScriptChecker &Checker; +}; + +void FuncSetCallback::run(const MatchFinder::MatchResult &Result) { + const FunctionDecl *Func; + if (auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) { + Func = Lambda->getCallOperator(); + if (!Func || !hasCustomAttribute<moz_can_run_script>(Func)) + return; + } else { + Func = Result.Nodes.getNodeAs<FunctionDecl>("canRunScriptFunction"); + + const char *ErrorAttrInDefinition = + "MOZ_CAN_RUN_SCRIPT must be put in front " + "of the declaration, not the definition"; + const char *NoteAttrInDefinition = "The first declaration exists here"; + if (!Func->isFirstDecl() && + !hasCustomAttribute<moz_can_run_script_for_definition>(Func)) { + const FunctionDecl *FirstDecl = Func->getFirstDecl(); + if (!hasCustomAttribute<moz_can_run_script>(FirstDecl)) { + Checker.diag(Func->getLocation(), ErrorAttrInDefinition, + DiagnosticIDs::Error); + Checker.diag(FirstDecl->getLocation(), NoteAttrInDefinition, + DiagnosticIDs::Note); + } + } + } + + CanRunScriptFuncs.insert(Func); + + // If this is a method, we check the methods it overrides. + if (auto *Method = dyn_cast<CXXMethodDecl>(Func)) { + checkOverriddenMethods(Method); + } +} + +void FuncSetCallback::checkOverriddenMethods(const CXXMethodDecl *Method) { + for (auto OverriddenMethod : Method->overridden_methods()) { + if (!hasCustomAttribute<moz_can_run_script>(OverriddenMethod)) { + const char *ErrorNonCanRunScriptOverridden = + "functions marked as MOZ_CAN_RUN_SCRIPT cannot override functions " + "that are not marked MOZ_CAN_RUN_SCRIPT"; + const char *NoteNonCanRunScriptOverridden = + "overridden function declared here"; + + Checker.diag(Method->getLocation(), ErrorNonCanRunScriptOverridden, + DiagnosticIDs::Error); + Checker.diag(OverriddenMethod->getLocation(), + NoteNonCanRunScriptOverridden, DiagnosticIDs::Note); + } + } +} +} // 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(*this, CanRunScriptFuncs); + // We add the matcher to the finder, linking it to our callback. + Finder.addMatcher( + functionDecl(hasCanRunScriptAnnotation()).bind("canRunScriptFunction"), + &Callback); + Finder.addMatcher(lambdaExpr().bind("lambda"), &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 caller's parameters when calling a " + "function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object " + "argument). '%0' is neither."; + + 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 = "caller function declared here"; + + const Expr *InvalidArg; + if (const CXXDefaultArgExpr *defaultArg = + Result.Nodes.getNodeAs<CXXDefaultArgExpr>("invalidArg")) { + InvalidArg = defaultArg->getExpr(); + } else { + 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. + // + // In addition, If the parent function is annotated as a + // CAN_RUN_SCRIPT_BOUNDARY, we don't want to complain about it calling a + // CAN_RUN_SCRIPT function. This is a mechanism to opt out of the infectious + // nature of CAN_RUN_SCRIPT which is necessary in some tricky code like + // Bindings. + if (ParentFunction && + (CanRunScriptFuncs.count(ParentFunction) || + hasCustomAttribute<moz_can_run_script_boundary>(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) { + const StringRef invalidArgText = Lexer::getSourceText( + CharSourceRange::getTokenRange(InvalidArg->getSourceRange()), + Result.Context->getSourceManager(), Result.Context->getLangOpts()); + diag(InvalidArg->getExprLoc(), ErrorInvalidArg, DiagnosticIDs::Error) + << InvalidArg->getSourceRange() << invalidArgText; + } + + // If the parent function is not marked as MOZ_CAN_RUN_SCRIPT, we emit an + // error and a not indicating it. + if (ParentFunction) { + assert(!hasCustomAttribute<moz_can_run_script>(ParentFunction) && + "Matcher missed something"); + + diag(CallRange.getBegin(), ErrorNonCanRunScriptParent, DiagnosticIDs::Error) + << CallRange; + + diag(ParentFunction->getCanonicalDecl()->getLocation(), + NoteNonCanRunScriptParent, DiagnosticIDs::Note); + } +} diff --git a/build/clang-plugin/CanRunScriptChecker.h b/build/clang-plugin/CanRunScriptChecker.h new file mode 100644 index 0000000000..4516609999 --- /dev/null +++ b/build/clang-plugin/CanRunScriptChecker.h @@ -0,0 +1,31 @@ +/* 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 diff --git a/build/clang-plugin/Checks.inc b/build/clang-plugin/Checks.inc new file mode 100644 index 0000000000..97c77547ac --- /dev/null +++ b/build/clang-plugin/Checks.inc @@ -0,0 +1,44 @@ +/* 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(JSHandleRootedTypedefChecker, "js-handle-rooted-typedef") +CHECK(KungFuDeathGripChecker, "kungfu-death-grip") +CHECK(KnownLiveChecker, "known-live") +#ifdef TARGET_IS_WINDOWS +CHECK(LoadLibraryUsageChecker, "load-library-usage") +CHECK(FopenUsageChecker, "fopen-usage") +#endif +CHECK(MustOverrideChecker, "must-override") +CHECK(MustReturnFromCallerChecker, "must-return-from-caller") +CHECK(NaNExprChecker, "nan-expr") +CHECK(NoPrincipalGetURI, "no-principal-geturi") +CHECK(NeedsNoVTableTypeChecker, "needs-no-vtable-type") +CHECK(NoAddRefReleaseOnReturnChecker, "no-addref-release-on-return") +CHECK(NoAutoTypeChecker, "no-auto-type") +CHECK(NoDuplicateRefCntMemberChecker, "no-duplicate-refcnt-member") +CHECK(NoExplicitMoveConstructorChecker, "no-explicit-move-constructor") +CHECK(NoNewThreadsChecker, "no-new-threads") +CHECK(NonMemMovableMemberChecker, "non-memmovable-member") +CHECK(NonMemMovableTemplateArgChecker, "non-memmovable-template-arg") +CHECK(NoUsingNamespaceMozillaJavaChecker, "no-using-namespace-mozilla-java") +CHECK(NonParamInsideFunctionDeclChecker, "non-memmovable-template-arg") +CHECK(NonTrivialTypeInFfiChecker, "non-trivial-type-in-ffi-boundary") +CHECK(OverrideBaseCallChecker, "override-base-call") +CHECK(OverrideBaseCallUsageChecker, "override-base-call-usage") +CHECK(ParamTraitsEnumChecker, "paramtraits-enum") +CHECK(RefCountedCopyConstructorChecker, "refcounted-copy-constructor") +CHECK(RefCountedInsideLambdaChecker, "refcounted-inside-lambda") +CHECK(RefCountedThisInsideConstructorChecker, "refcount-within-constructor") +CHECK(ScopeChecker, "scope") +CHECK(SprintfLiteralChecker, "sprintf-literal") +CHECK(TrivialCtorDtorChecker, "trivial-constructor-destructor") +CHECK(TrivialDtorChecker, "trivial-destructor") diff --git a/build/clang-plugin/ChecksIncludes.inc b/build/clang-plugin/ChecksIncludes.inc new file mode 100644 index 0000000000..d029d6ea30 --- /dev/null +++ b/build/clang-plugin/ChecksIncludes.inc @@ -0,0 +1,45 @@ +/* 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 #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" +#ifdef TARGET_IS_WINDOWS +#include "LoadLibraryUsageChecker.h" +#include "FopenUsageChecker.h" +#endif +#include "JSHandleRootedTypedefChecker.h" +#include "KungFuDeathGripChecker.h" +#include "KnownLiveChecker.h" +#include "MustOverrideChecker.h" +#include "MustReturnFromCallerChecker.h" +#include "NaNExprChecker.h" +#include "NoPrincipalGetURI.h" +#include "NeedsNoVTableTypeChecker.h" +#include "NoAddRefReleaseOnReturnChecker.h" +#include "NoAutoTypeChecker.h" +#include "NoDuplicateRefCntMemberChecker.h" +#include "NoExplicitMoveConstructorChecker.h" +#include "NoNewThreadsChecker.h" +#include "NonMemMovableMemberChecker.h" +#include "NonMemMovableTemplateArgChecker.h" +#include "NonParamInsideFunctionDeclChecker.h" +#include "NonTrivialTypeInFfiChecker.h" +#include "NoUsingNamespaceMozillaJavaChecker.h" +#include "OverrideBaseCallChecker.h" +#include "OverrideBaseCallUsageChecker.h" +#include "ParamTraitsEnumChecker.h" +#include "RefCountedCopyConstructorChecker.h" +#include "RefCountedInsideLambdaChecker.h" +#include "RefCountedThisInsideConstructorChecker.h" +#include "ScopeChecker.h" +#include "SprintfLiteralChecker.h" +#include "TrivialCtorDtorChecker.h" +#include "TrivialDtorChecker.h" diff --git a/build/clang-plugin/CustomAttributes.cpp b/build/clang-plugin/CustomAttributes.cpp new file mode 100644 index 0000000000..d143f5856d --- /dev/null +++ b/build/clang-plugin/CustomAttributes.cpp @@ -0,0 +1,126 @@ +/* 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 "CustomAttributes.h" +#include "plugin.h" +#include "clang/Frontend/FrontendPluginRegistry.h" +#include <algorithm> + +/* Having annotations in the AST unexpectedly impacts codegen. + * Ideally, we'd avoid having annotations at all, by using an API such as + * the one from https://reviews.llvm.org/D31338, and storing the attributes + * data separately from the AST on our own. Unfortunately, there is no such + * API currently in clang, so we must do without. + * We can do something similar, though, where we go through the AST before + * running the checks, create a mapping of AST nodes to attributes, and + * remove the attributes/annotations from the AST nodes. + * Not all declarations can be reached from the decl() AST matcher, though, + * so we do our best effort (getting the other declarations we look at in + * checks). We emit a warning when checks look at a note that still has + * annotations attached (aka, hasn't been seen during our first pass), + * so that those don't go unnoticed. (-Werror should then take care of + * making that an error) + */ + +using namespace clang; +using namespace llvm; + +static DenseMap<const Decl *, CustomAttributesSet> AttributesCache; + +static CustomAttributesSet CacheAttributes(const Decl *D) { + CustomAttributesSet attrs = {}; + for (auto Attr : D->specific_attrs<AnnotateAttr>()) { + auto annotation = Attr->getAnnotation(); +#define ATTR(a) \ + if (annotation == #a) { \ + attrs.has_##a = true; \ + } else +#include "CustomAttributes.inc" +#include "external/CustomAttributes.inc" +#undef ATTR + {} + } + const_cast<Decl *>(D)->dropAttr<AnnotateAttr>(); + AttributesCache.insert(std::make_pair(D, attrs)); + return attrs; +} + +#ifndef CLANG_TIDY +static void Report(const Decl *D, const char *message) { + ASTContext &Context = D->getASTContext(); + DiagnosticsEngine &Diag = Context.getDiagnostics(); + unsigned ID = + Diag.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Warning, message); + Diag.Report(D->getBeginLoc(), ID); +} + +class CustomAttributesMatcher + : public ast_matchers::MatchFinder::MatchCallback { +public: + void run(const ast_matchers::MatchFinder::MatchResult &Result) final { + if (auto D = Result.Nodes.getNodeAs<Decl>("decl")) { + CacheAttributes(D); + } else if (auto L = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) { + CacheAttributes(L->getCallOperator()); + CacheAttributes(L->getLambdaClass()); + } + } +}; + +class CustomAttributesAction : public PluginASTAction { +public: + ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, + StringRef FileName) override { + auto &Context = CI.getASTContext(); + auto AstMatcher = new (Context.Allocate<MatchFinder>()) MatchFinder(); + auto Matcher = new (Context.Allocate<CustomAttributesMatcher>()) + CustomAttributesMatcher(); + AstMatcher->addMatcher(decl().bind("decl"), Matcher); + AstMatcher->addMatcher(lambdaExpr().bind("lambda"), Matcher); + return AstMatcher->newASTConsumer(); + } + + bool ParseArgs(const CompilerInstance &CI, + const std::vector<std::string> &Args) override { + return true; + } + + ActionType getActionType() override { return AddBeforeMainAction; } +}; + +static FrontendPluginRegistry::Add<CustomAttributesAction> + X("moz-custom-attributes", "prepare custom attributes for moz-check"); +#endif + +CustomAttributesSet GetAttributes(const Decl *D) { + CustomAttributesSet attrs = {}; + if (D->hasAttr<AnnotateAttr>()) { +// If we are not in clang-tidy env push warnings, most likely we are in the +// build environment and this should have been done in AstMatcher - +// CustomAttributesMatcher +#ifndef CLANG_TIDY + Report(D, "Declaration has unhandled annotations."); +#endif + attrs = CacheAttributes(D); + } else { + auto attributes = AttributesCache.find(D); + if (attributes != AttributesCache.end()) { + attrs = attributes->second; + } + } + return attrs; +} + +bool hasCustomAttribute(const clang::Decl *D, CustomAttributes A) { + CustomAttributesSet attrs = GetAttributes(D); + switch (A) { +#define ATTR(a) \ + case a: \ + return attrs.has_##a; +#include "CustomAttributes.inc" +#include "external/CustomAttributes.inc" +#undef ATTR + } + return false; +} diff --git a/build/clang-plugin/CustomAttributes.h b/build/clang-plugin/CustomAttributes.h new file mode 100644 index 0000000000..04c95b7184 --- /dev/null +++ b/build/clang-plugin/CustomAttributes.h @@ -0,0 +1,41 @@ +/* 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 CustomAttributes_h__ +#define CustomAttributes_h__ + +#include "clang/AST/DeclBase.h" +#include "llvm/ADT/StringRef.h" + +enum CustomAttributes { +#define ATTR(a) a, +#include "CustomAttributes.inc" +#include "external/CustomAttributes.inc" +#undef ATTR +}; + +struct CustomAttributesSet { +#define ATTR(a) bool has_##a : 1; +#include "CustomAttributes.inc" +#include "external/CustomAttributes.inc" +#undef ATTR +}; + +template <CustomAttributes A> bool hasCustomAttribute(const clang::Decl *D) { + return false; +} + +extern CustomAttributesSet GetAttributes(const clang::Decl *D); + +#define ATTR(name) \ + template <> inline bool hasCustomAttribute<name>(const clang::Decl *D) { \ + return GetAttributes(D).has_##name; \ + } +#include "CustomAttributes.inc" +#include "external/CustomAttributes.inc" +#undef ATTR + +extern bool hasCustomAttribute(const clang::Decl *D, CustomAttributes A); + +#endif /* CustomAttributes_h__ */ diff --git a/build/clang-plugin/CustomAttributes.inc b/build/clang-plugin/CustomAttributes.inc new file mode 100644 index 0000000000..883676e9c5 --- /dev/null +++ b/build/clang-plugin/CustomAttributes.inc @@ -0,0 +1,31 @@ +ATTR(moz_allow_temporary) +ATTR(moz_can_run_script) +ATTR(moz_can_run_script_for_definition) +ATTR(moz_can_run_script_boundary) +ATTR(moz_global_class) +ATTR(moz_heap_allocator) +ATTR(moz_heap_class) +ATTR(moz_implicit) +ATTR(moz_inherit_type_annotations_from_template_args) +ATTR(moz_is_smartptr_to_refcounted) +ATTR(moz_known_live) +ATTR(moz_may_call_after_must_return) +ATTR(moz_must_override) +ATTR(moz_must_return_from_caller_if_this_is_arg) +ATTR(moz_needs_memmovable_members) +ATTR(moz_needs_memmovable_type) +ATTR(moz_needs_no_vtable_type) +ATTR(moz_no_addref_release_on_return) +ATTR(moz_no_arith_expr_in_arg) +ATTR(moz_no_dangling_on_temporaries) +ATTR(moz_non_autoable) +ATTR(moz_non_memmovable) +ATTR(moz_non_param) +ATTR(moz_non_temporary_class) +ATTR(moz_nonheap_class) +ATTR(moz_required_base_method) +ATTR(moz_stack_class) +ATTR(moz_static_local_class) +ATTR(moz_temporary_class) +ATTR(moz_trivial_ctor_dtor) +ATTR(moz_trivial_dtor) diff --git a/build/clang-plugin/CustomMatchers.h b/build/clang-plugin/CustomMatchers.h new file mode 100644 index 0000000000..97caf4d59c --- /dev/null +++ b/build/clang-plugin/CustomMatchers.h @@ -0,0 +1,494 @@ +/* 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 CustomMatchers_h__ +#define CustomMatchers_h__ + +#include "MemMoveAnnotation.h" +#include "Utils.h" + +#if CLANG_VERSION_FULL >= 1300 +// Starting with clang-13 Expr::isRValue has been renamed to Expr::isPRValue +#define isRValue isPRValue +#endif + +namespace clang { +namespace ast_matchers { + +/// This matcher will match any function declaration that is declared as a heap +/// allocator. +AST_MATCHER(FunctionDecl, heapAllocator) { + return hasCustomAttribute<moz_heap_allocator>(&Node); +} + +/// This matcher will match any declaration that is marked as not accepting +/// arithmetic expressions in its arguments. +AST_MATCHER(Decl, noArithmeticExprInArgs) { + return hasCustomAttribute<moz_no_arith_expr_in_arg>(&Node); +} + +/// This matcher will match any C++ class that is marked as having a trivial +/// constructor and destructor. +AST_MATCHER(CXXRecordDecl, hasTrivialCtorDtor) { + return hasCustomAttribute<moz_trivial_ctor_dtor>(&Node); +} + +/// This matcher will match any C++ class that is marked as having a trivial +/// destructor. +AST_MATCHER(CXXRecordDecl, hasTrivialDtor) { + return hasCustomAttribute<moz_trivial_dtor>(&Node); +} + +AST_MATCHER(CXXConstructExpr, allowsTemporary) { + return hasCustomAttribute<moz_allow_temporary>(Node.getConstructor()); +} + +/// 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; +} + +AST_POLYMORPHIC_MATCHER(isFirstParty, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) { + return !inThirdPartyPath(&Node, &Finder->getASTContext()) && + !ASTIsInSystemHeader(Finder->getASTContext(), Node); +} + +AST_MATCHER(DeclaratorDecl, isNotSpiderMonkey) { + // Detect SpiderMonkey path. Not as strict as isFirstParty, but this is + // expected to disappear soon by getting a common style guide between DOM and + // SpiderMonkey. + std::string Path = Node.getBeginLoc().printToString( + Finder->getASTContext().getSourceManager()); + return Path.find("js") == std::string::npos && + Path.find("xpc") == std::string::npos && + Path.find("XPC") == std::string::npos; +} + +/// This matcher will match temporary expressions. +/// We need this matcher for compatibility with clang 3.* (clang 4 and above +/// insert a MaterializeTemporaryExpr everywhere). +AST_MATCHER(Expr, isTemporary) { + return Node.isRValue() || Node.isXValue() || + isa<MaterializeTemporaryExpr>(&Node); +} + +/// 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 hasCustomAttribute<moz_no_dangling_on_temporaries>(&Node); +} + +/// 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 hasCustomAttribute<moz_no_addref_release_on_return>(&Node); +} + +/// This matcher will match any function declaration that is marked as being +/// allowed to run script. +AST_MATCHER(FunctionDecl, hasCanRunScriptAnnotation) { + return hasCustomAttribute<moz_can_run_script>(&Node); +} + +/// 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 || + OpCode == BO_SubAssign || OpCode == BO_ShlAssign || + OpCode == BO_ShrAssign || OpCode == BO_AndAssign || + OpCode == BO_XorAssign || OpCode == BO_OrAssign; +} + +/// This matcher will match all arithmetic unary operators. +AST_MATCHER(UnaryOperator, unaryArithmeticOperator) { + UnaryOperatorKind OpCode = Node.getOpcode(); + return OpCode == UO_PostInc || OpCode == UO_PostDec || OpCode == UO_PreInc || + OpCode == UO_PreDec || OpCode == UO_Plus || OpCode == UO_Minus || + OpCode == UO_Not; +} + +/// This matcher will match the unary dereference operator +AST_MATCHER(UnaryOperator, unaryDereferenceOperator) { + UnaryOperatorKind OpCode = Node.getOpcode(); + return OpCode == UO_Deref; +} + +/// This matcher will match == and != binary operators. +AST_MATCHER(BinaryOperator, binaryEqualityOperator) { + BinaryOperatorKind OpCode = Node.getOpcode(); + return OpCode == BO_EQ || OpCode == BO_NE; +} + +/// This matcher will match comma operator. +AST_MATCHER(BinaryOperator, binaryCommaOperator) { + BinaryOperatorKind OpCode = Node.getOpcode(); + return OpCode == BO_Comma; +} + +/// This matcher will match floating point types. +AST_MATCHER(QualType, isFloat) { return Node->isRealFloatingType(); } + +/// This matcher will match locations in system headers. This is adopted from +/// isExpansionInSystemHeader in newer clangs, but modified in order to work +/// with old clangs that we use on infra. +AST_POLYMORPHIC_MATCHER(isInSystemHeader, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) { + return ASTIsInSystemHeader(Finder->getASTContext(), Node); +} + +/// This matcher will match a file "gtest-port.h". The file contains +/// known fopen usages that are OK. +AST_MATCHER(CallExpr, isInWhitelistForFopenUsage) { + static const char Whitelist[] = "gtest-port.h"; + SourceLocation Loc = Node.getBeginLoc(); + StringRef FileName = + getFilename(Finder->getASTContext().getSourceManager(), Loc); + + return llvm::sys::path::rbegin(FileName)->equals(Whitelist); +} + +/// This matcher will match a list of files. These files contain +/// known NaN-testing expressions which we would like to whitelist. +AST_MATCHER(BinaryOperator, isInWhitelistForNaNExpr) { + const char *whitelist[] = {"SkScalar.h", "json_writer.cpp", "State.cpp"}; + + SourceLocation Loc = Node.getOperatorLoc(); + StringRef FileName = + getFilename(Finder->getASTContext().getSourceManager(), Loc); + for (auto itr = std::begin(whitelist); itr != std::end(whitelist); itr++) { + if (llvm::sys::path::rbegin(FileName)->equals(*itr)) { + return true; + } + } + + return false; +} + +AST_MATCHER(CallExpr, isInWhiteListForPrincipalGetUri) { + const auto Whitelist = {"nsIPrincipal.h", "BasePrincipal.cpp", + "ContentPrincipal.cpp"}; + SourceLocation Loc = Node.getBeginLoc(); + StringRef Filename = + getFilename(Finder->getASTContext().getSourceManager(), Loc); + + for (auto Exclusion : Whitelist) { + if (Filename.find(Exclusion) != std::string::npos) { + return true; + } + } + return false; +} + +/// This matcher will match a list of files which contain NS_NewNamedThread +/// code or names of existing threads that we would like to ignore. +AST_MATCHER(CallExpr, isInAllowlistForThreads) { + + // Get the source location of the call. + SourceLocation Loc = Node.getRParenLoc(); + StringRef FileName = + getFilename(Finder->getASTContext().getSourceManager(), Loc); + + const auto rbegin = [](StringRef s) { return llvm::sys::path::rbegin(s); }; + const auto rend = [](StringRef s) { return llvm::sys::path::rend(s); }; + + // Files in the allowlist are (definitionally) explicitly permitted to create + // new threads. + for (auto thread_file : allow_thread_files) { + // All the provided path-elements must match. + const bool match = [&] { + auto it1 = rbegin(FileName), it2 = rbegin(thread_file), + end1 = rend(FileName), end2 = rend(thread_file); + for (; it2 != end2; ++it1, ++it2) { + if (it1 == end1 || !it1->equals(*it2)) { + return false; + } + } + return true; + }(); + if (match) { + return true; + } + } + + // Check the first arg (the name of the thread). + const StringLiteral *nameArg = + dyn_cast<StringLiteral>(Node.getArg(0)->IgnoreImplicit()); + if (nameArg) { + const StringRef name = nameArg->getString(); + for (auto thread_name : allow_thread_names) { + if (name.equals(thread_name)) { + return true; + } + } + } + + return false; +} + +/// This matcher will match all accesses to AddRef or Release methods. +AST_MATCHER(MemberExpr, isAddRefOrRelease) { + ValueDecl *Member = Node.getMemberDecl(); + 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 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 hasCustomAttribute<moz_needs_no_vtable_type>(&Node); +} + +/// This matcher will select classes which are non-memmovable +AST_MATCHER(QualType, isNonMemMovable) { + return NonMemMovable.hasEffectiveAnnotation(Node); +} + +/// This matcher will select classes which require a memmovable template arg +AST_MATCHER(CXXRecordDecl, needsMemMovableTemplateArg) { + return hasCustomAttribute<moz_needs_memmovable_type>(&Node); +} + +/// This matcher will select classes which require all members to be memmovable +AST_MATCHER(CXXRecordDecl, needsMemMovableMembers) { + return hasCustomAttribute<moz_needs_memmovable_members>(&Node); +} + +AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) { + const CXXConstructorDecl *Declaration = Node.getCanonicalDecl(); + return + // Skip constructors in system headers + !ASTIsInSystemHeader(Declaration->getASTContext(), *Declaration) && + // Skip ignored namespaces and paths + !isInIgnoredNamespaceForImplicitCtor(Declaration) && + !inThirdPartyPath(Declaration) && + // We only want Converting constructors + Declaration->isConvertingConstructor(false) && + // We don't want copy of move constructors, as those are allowed to be + // implicit + !Declaration->isCopyOrMoveConstructor() && + // We don't want inheriting constructors, since using declarations can't + // have attributes + !Declaration->isInheritingConstructor() && + // We don't want deleted constructors. + !Declaration->isDeleted(); +} + +AST_MATCHER_P(Expr, ignoreTrivials, internal::Matcher<Expr>, InnerMatcher) { + return InnerMatcher.matches(*IgnoreTrivials(&Node), Finder, Builder); +} + +// Takes two matchers: the first one is a condition; the second is a matcher to +// be applied once we are done unwrapping trivials. While the condition does +// not match and we're looking at a trivial, will keep unwrapping the trivial +// and trying again. Once the condition matches, we will go ahead and unwrap all +// trivials and apply the inner matcher to the result. +// +// The expected use here is if we want to condition a match on some typecheck +// but apply the match to only non-trivials, because there are trivials (e.g. +// casts) that can change types. +AST_MATCHER_P2(Expr, ignoreTrivialsConditional, internal::Matcher<Expr>, + Condition, internal::Matcher<Expr>, InnerMatcher) { + const Expr *node = &Node; + while (true) { + if (Condition.matches(*node, Finder, Builder)) { + return InnerMatcher.matches(*IgnoreTrivials(node), Finder, Builder); + } + const Expr *newNode = MaybeSkipOneTrivial(node); + if (newNode == node) { + return false; + } + node = newNode; + } +} + +// We can't call this "isImplicit" since it clashes with an existing matcher in +// clang. +AST_MATCHER(CXXConstructorDecl, isMarkedImplicit) { + return hasCustomAttribute<moz_implicit>(&Node); +} + +AST_MATCHER(CXXRecordDecl, isConcreteClass) { return !Node.isAbstract(); } + +AST_MATCHER(QualType, autoNonAutoableType) { + if (const AutoType *T = Node->getContainedAutoType()) { + if (const CXXRecordDecl *Rec = T->getAsCXXRecordDecl()) { + return hasCustomAttribute<moz_non_autoable>(Rec); + } + } + return false; +} + +AST_MATCHER(CXXConstructorDecl, isExplicitMoveConstructor) { + return Node.isExplicit() && Node.isMoveConstructor(); +} + +AST_MATCHER(CXXConstructorDecl, isCompilerProvidedCopyConstructor) { + return !Node.isUserProvided() && Node.isCopyConstructor(); +} + +AST_MATCHER(CallExpr, isAssertAssignmentTestFunc) { + static const std::string AssertName = "MOZ_AssertAssignmentTest"; + const FunctionDecl *Method = Node.getDirectCallee(); + + return Method && Method->getDeclName().isIdentifier() && + Method->getName() == AssertName; +} + +AST_MATCHER(CallExpr, isSnprintfLikeFunc) { + static const std::string Snprintf = "snprintf"; + static const std::string Vsnprintf = "vsnprintf"; + const FunctionDecl *Func = Node.getDirectCallee(); + + if (!Func || isa<CXXMethodDecl>(Func)) { + return false; + } + + StringRef Name = getNameChecked(Func); + if (Name != Snprintf && Name != Vsnprintf) { + return false; + } + + return !inThirdPartyPath(Node.getBeginLoc(), + Finder->getASTContext().getSourceManager()) && + !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 && hasCustomAttribute<moz_is_smartptr_to_refcounted>(D); +} + +AST_MATCHER(ClassTemplateSpecializationDecl, isSmartPtrToRefCountedDecl) { + auto *D = dyn_cast_or_null<CXXRecordDecl>( + Node.getSpecializedTemplate()->getTemplatedDecl()); + if (!D) { + return false; + } + + D = D->getCanonicalDecl(); + + return D && hasCustomAttribute<moz_is_smartptr_to_refcounted>(D); +} + +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) { + const CXXMethodDecl *Decl = Node.getCanonicalDecl(); + return Decl && hasCustomAttribute<moz_required_base_method>(Decl); +} + +AST_MATCHER(CXXMethodDecl, isNonVirtual) { + const CXXMethodDecl *Decl = Node.getCanonicalDecl(); + return Decl && !Decl->isVirtual(); +} + +AST_MATCHER(FunctionDecl, isMozMustReturnFromCaller) { + const FunctionDecl *Decl = Node.getCanonicalDecl(); + return Decl && + hasCustomAttribute<moz_must_return_from_caller_if_this_is_arg>(Decl); +} + +/// This matcher will select default args which have nullptr as the value. +AST_MATCHER(CXXDefaultArgExpr, isNullDefaultArg) { + const Expr *Expr = Node.getExpr(); + return Expr && Expr->isNullPointerConstant(Finder->getASTContext(), + Expr::NPC_NeverValueDependent); +} + +AST_MATCHER(UsingDirectiveDecl, isUsingNamespaceMozillaJava) { + const NamespaceDecl *Namespace = Node.getNominatedNamespace(); + const std::string &FQName = Namespace->getQualifiedNameAsString(); + + static const char NAMESPACE[] = "mozilla::java"; + static const char PREFIX[] = "mozilla::java::"; + + // We match both the `mozilla::java` namespace itself as well as any other + // namespaces contained within the `mozilla::java` namespace. + return !FQName.compare(NAMESPACE) || + !FQName.compare(0, sizeof(PREFIX) - 1, PREFIX); +} + +AST_MATCHER(MemberExpr, hasKnownLiveAnnotation) { + ValueDecl *Member = Node.getMemberDecl(); + FieldDecl *Field = dyn_cast<FieldDecl>(Member); + return Field && hasCustomAttribute<moz_known_live>(Field); +} + +#define GENERATE_JSTYPEDEF_PAIR(templateName) \ + {templateName "Function", templateName "<JSFunction*>"}, \ + {templateName "Id", templateName "<JS::PropertyKey>"}, \ + {templateName "Object", templateName "<JSObject*>"}, \ + {templateName "Script", templateName "<JSScript*>"}, \ + {templateName "String", templateName "<JSString*>"}, \ + {templateName "Symbol", templateName "<JS::Symbol*>"}, \ + {templateName "BigInt", templateName "<JS::BigInt*>"}, \ + {templateName "Value", templateName "<JS::Value>"}, \ + {templateName "ValueVector", templateName "Vector<JS::Value>"}, \ + {templateName "ObjectVector", templateName "Vector<JSObject*>"}, { \ + templateName "IdVector", templateName "Vector<JS::PropertyKey>" \ + } + +static const char *const JSHandleRootedTypedefMap[][2] = { + GENERATE_JSTYPEDEF_PAIR("JS::Handle"), + GENERATE_JSTYPEDEF_PAIR("JS::MutableHandle"), + GENERATE_JSTYPEDEF_PAIR("JS::Rooted"), + // Technically there is no PersistentRootedValueVector, and that's okay + GENERATE_JSTYPEDEF_PAIR("JS::PersistentRooted"), +}; + +AST_MATCHER(DeclaratorDecl, isUsingJSHandleRootedTypedef) { + QualType Type = Node.getType(); + std::string TypeName = Type.getAsString(); + for (auto &pair : JSHandleRootedTypedefMap) { + if (!TypeName.compare(pair[0])) { + return true; + } + } + return false; +} + +} // namespace ast_matchers +} // namespace clang + +#undef isRValue +#endif diff --git a/build/clang-plugin/CustomTypeAnnotation.cpp b/build/clang-plugin/CustomTypeAnnotation.cpp new file mode 100644 index 0000000000..6e0f44ce05 --- /dev/null +++ b/build/clang-plugin/CustomTypeAnnotation.cpp @@ -0,0 +1,182 @@ +/* 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 "CustomTypeAnnotation.h" +#include "Utils.h" + +CustomTypeAnnotation StackClass = + CustomTypeAnnotation(moz_stack_class, "stack"); +CustomTypeAnnotation GlobalClass = + CustomTypeAnnotation(moz_global_class, "global"); +CustomTypeAnnotation NonHeapClass = + CustomTypeAnnotation(moz_nonheap_class, "non-heap"); +CustomTypeAnnotation HeapClass = CustomTypeAnnotation(moz_heap_class, "heap"); +CustomTypeAnnotation NonTemporaryClass = + CustomTypeAnnotation(moz_non_temporary_class, "non-temporary"); +CustomTypeAnnotation TemporaryClass = + CustomTypeAnnotation(moz_temporary_class, "temporary"); +CustomTypeAnnotation StaticLocalClass = + CustomTypeAnnotation(moz_static_local_class, "static-local"); + +void CustomTypeAnnotation::dumpAnnotationReason(BaseCheck &Check, QualType T, + SourceLocation Loc) { + const char *Inherits = + "%1 is a %0 type because it inherits from a %0 type %2"; + const char *Member = "%1 is a %0 type because member %2 is a %0 type %3"; + const char *Array = "%1 is a %0 type because it is an array of %0 type %2"; + const char *Templ = + "%1 is a %0 type because it has a template argument %0 type %2"; + const char *Implicit = "%1 is a %0 type because %2"; + + AnnotationReason Reason = directAnnotationReason(T); + for (;;) { + switch (Reason.Kind) { + case RK_ArrayElement: + Check.diag(Loc, Array, DiagnosticIDs::Note) << Pretty << T << Reason.Type; + break; + case RK_BaseClass: { + const CXXRecordDecl *Declaration = T->getAsCXXRecordDecl(); + assert(Declaration && "This type should be a C++ class"); + + Check.diag(Declaration->getLocation(), Inherits, DiagnosticIDs::Note) + << Pretty << T << Reason.Type; + break; + } + case RK_Field: + Check.diag(Reason.Field->getLocation(), Member, DiagnosticIDs::Note) + << Pretty << T << Reason.Field << Reason.Type; + break; + case RK_TemplateInherited: { + const CXXRecordDecl *Declaration = T->getAsCXXRecordDecl(); + assert(Declaration && "This type should be a C++ class"); + + Check.diag(Declaration->getLocation(), Templ, DiagnosticIDs::Note) + << Pretty << T << Reason.Type; + break; + } + case RK_Implicit: { + const TagDecl *Declaration = T->getAsTagDecl(); + assert(Declaration && "This type should be a TagDecl"); + + Check.diag(Declaration->getLocation(), Implicit, DiagnosticIDs::Note) + << Pretty << T << Reason.ImplicitReason; + return; + } + default: + // FIXME (bug 1203263): note the original annotation. + return; + } + + T = Reason.Type; + Reason = directAnnotationReason(T); + } +} + +CustomTypeAnnotation::AnnotationReason +CustomTypeAnnotation::directAnnotationReason(QualType T) { + VisitFlags ToVisit = VISIT_FIELDS | VISIT_BASES; + + if (const TagDecl *D = T->getAsTagDecl()) { + // Recurse into template arguments if the annotation + // MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS is present + if (hasCustomAttribute<moz_inherit_type_annotations_from_template_args>( + D)) { + ToVisit |= VISIT_TMPL_ARGS; + } + + if (hasCustomAttribute(D, Attribute)) { + AnnotationReason Reason = {T, RK_Direct, nullptr, ""}; + return Reason; + } + + std::string ImplAnnotReason = getImplicitReason(D, ToVisit); + if (!ImplAnnotReason.empty()) { + AnnotationReason Reason = {T, RK_Implicit, nullptr, ImplAnnotReason}; + return Reason; + } + } + + // Check if we have a cached answer + void *Key = T.getAsOpaquePtr(); + ReasonCache::iterator Cached = Cache.find(T.getAsOpaquePtr()); + if (Cached != Cache.end()) { + return Cached->second; + } + + // Check if we have a type which we can recurse into + if (const clang::ArrayType *Array = T->getAsArrayTypeUnsafe()) { + if (hasEffectiveAnnotation(Array->getElementType())) { + AnnotationReason Reason{Array->getElementType(), RK_ArrayElement, nullptr, + ""}; + Cache[Key] = Reason; + return Reason; + } + } + + // Recurse into Base classes + if (const CXXRecordDecl *Declaration = T->getAsCXXRecordDecl()) { + if (Declaration->hasDefinition()) { + Declaration = Declaration->getDefinition(); + + if (ToVisit & VISIT_BASES) { + for (const CXXBaseSpecifier &Base : Declaration->bases()) { + if (hasEffectiveAnnotation(Base.getType())) { + AnnotationReason Reason{Base.getType(), RK_BaseClass, nullptr, ""}; + Cache[Key] = Reason; + return Reason; + } + } + } + + if (ToVisit & VISIT_FIELDS) { + for (const FieldDecl *Field : Declaration->fields()) { + if (hasEffectiveAnnotation(Field->getType())) { + AnnotationReason Reason{Field->getType(), RK_Field, Field, ""}; + Cache[Key] = Reason; + return Reason; + } + } + } + + if (ToVisit & VISIT_TMPL_ARGS) { + const ClassTemplateSpecializationDecl *Spec = + dyn_cast<ClassTemplateSpecializationDecl>(Declaration); + if (Spec) { + const TemplateArgumentList &Args = Spec->getTemplateArgs(); + + AnnotationReason Reason = tmplArgAnnotationReason(Args.asArray()); + if (Reason.Kind != RK_None) { + Cache[Key] = Reason; + return Reason; + } + } + } + } + } + + AnnotationReason Reason{QualType(), RK_None, nullptr, ""}; + Cache[Key] = Reason; + return Reason; +} + +CustomTypeAnnotation::AnnotationReason +CustomTypeAnnotation::tmplArgAnnotationReason(ArrayRef<TemplateArgument> Args) { + for (const TemplateArgument &Arg : Args) { + if (Arg.getKind() == TemplateArgument::Type) { + QualType Type = Arg.getAsType(); + if (hasEffectiveAnnotation(Type)) { + AnnotationReason Reason = {Type, RK_TemplateInherited, nullptr, ""}; + return Reason; + } + } else if (Arg.getKind() == TemplateArgument::Pack) { + AnnotationReason Reason = tmplArgAnnotationReason(Arg.getPackAsArray()); + if (Reason.Kind != RK_None) { + return Reason; + } + } + } + + AnnotationReason Reason = {QualType(), RK_None, nullptr, ""}; + return Reason; +} diff --git a/build/clang-plugin/CustomTypeAnnotation.h b/build/clang-plugin/CustomTypeAnnotation.h new file mode 100644 index 0000000000..453976915a --- /dev/null +++ b/build/clang-plugin/CustomTypeAnnotation.h @@ -0,0 +1,92 @@ +/* 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 CustomTypeAnnotation_h__ +#define CustomTypeAnnotation_h__ + +#include "CustomAttributes.h" +#include "plugin.h" +#include "llvm/ADT/BitmaskEnum.h" + +class CustomTypeAnnotation { + enum ReasonKind { + RK_None, + RK_Direct, + RK_ArrayElement, + RK_BaseClass, + RK_Field, + RK_TemplateInherited, + RK_Implicit, + }; + struct AnnotationReason { + QualType Type; + ReasonKind Kind; + const FieldDecl *Field; + std::string ImplicitReason; + + bool valid() const { return Kind != RK_None; } + }; + typedef DenseMap<void *, AnnotationReason> ReasonCache; + + CustomAttributes Attribute; + const char *Pretty; + ReasonCache Cache; + +public: + CustomTypeAnnotation(CustomAttributes Attribute, const char *Pretty) + : Attribute(Attribute), Pretty(Pretty){}; + + virtual ~CustomTypeAnnotation() {} + + // Checks if this custom annotation "effectively affects" the given type. + bool hasEffectiveAnnotation(QualType T) { + return directAnnotationReason(T).valid(); + } + void dumpAnnotationReason(BaseCheck &Check, QualType T, SourceLocation Loc); + + void reportErrorIfPresent(BaseCheck &Check, QualType T, SourceLocation Loc, + const char *Error, const char *Note) { + if (hasEffectiveAnnotation(T)) { + Check.diag(Loc, Error, DiagnosticIDs::Error) << T; + Check.diag(Loc, Note, DiagnosticIDs::Note); + dumpAnnotationReason(Check, T, Loc); + } + } + +private: + AnnotationReason directAnnotationReason(QualType T); + AnnotationReason tmplArgAnnotationReason(ArrayRef<TemplateArgument> Args); + +protected: + // Flags specifying which properties of the underlying type we want to visit. + enum VisitFlags { + VISIT_NONE = 0, + VISIT_FIELDS = 1, + VISIT_TMPL_ARGS = 2, + VISIT_BASES = 4, + LLVM_MARK_AS_BITMASK_ENUM(VISIT_BASES) + }; + + // Allow subclasses to apply annotations for reasons other than a direct + // annotation. A non-empty string return value means that the object D is + // annotated, and should contain the reason why. + // + // The subclass may also modify `VisitFlags` to change what properties of the + // type will be inspected to skip inspecting fields, force template arguments + // to be inspected, etc. + virtual std::string getImplicitReason(const TagDecl *D, + VisitFlags &Flags) const { + return ""; + } +}; + +extern CustomTypeAnnotation StackClass; +extern CustomTypeAnnotation GlobalClass; +extern CustomTypeAnnotation NonHeapClass; +extern CustomTypeAnnotation HeapClass; +extern CustomTypeAnnotation NonTemporaryClass; +extern CustomTypeAnnotation TemporaryClass; +extern CustomTypeAnnotation StaticLocalClass; + +#endif 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(); + } +} diff --git a/build/clang-plugin/DanglingOnTemporaryChecker.h b/build/clang-plugin/DanglingOnTemporaryChecker.h new file mode 100644 index 0000000000..43f19ebedc --- /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/DiagnosticsMatcher.cpp b/build/clang-plugin/DiagnosticsMatcher.cpp new file mode 100644 index 0000000000..96d2f60440 --- /dev/null +++ b/build/clang-plugin/DiagnosticsMatcher.cpp @@ -0,0 +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/. */ + +#include "DiagnosticsMatcher.h" + +DiagnosticsMatcher::DiagnosticsMatcher(CompilerInstance &CI) { +#define CHECK(cls, name) \ + cls##_.registerMatchers(&AstMatcher); \ + cls##_.registerPPCallbacks(CI); +#include "Checks.inc" +#include "external/ExternalChecks.inc" +#ifdef MOZ_CLANG_PLUGIN_ALPHA +#include "alpha/AlphaChecks.inc" +#endif +#undef CHECK +} diff --git a/build/clang-plugin/DiagnosticsMatcher.h b/build/clang-plugin/DiagnosticsMatcher.h new file mode 100644 index 0000000000..2738541f62 --- /dev/null +++ b/build/clang-plugin/DiagnosticsMatcher.h @@ -0,0 +1,31 @@ +/* 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 DiagnosticsMatcher_h__ +#define DiagnosticsMatcher_h__ + +#include "ChecksIncludes.inc" +#include "external/ExternalIncludes.inc" +#ifdef MOZ_CLANG_PLUGIN_ALPHA +#include "alpha/AlphaIncludes.inc" +#endif + +class DiagnosticsMatcher { +public: + DiagnosticsMatcher(CompilerInstance &CI); + + ASTConsumerPtr makeASTConsumer() { return AstMatcher.newASTConsumer(); } + +private: +#define CHECK(cls, name) cls cls##_{name}; +#include "Checks.inc" +#include "external/ExternalChecks.inc" +#ifdef MOZ_CLANG_PLUGIN_ALPHA +#include "alpha/AlphaChecks.inc" +#endif +#undef CHECK + MatchFinder AstMatcher; +}; + +#endif diff --git a/build/clang-plugin/ExplicitImplicitChecker.cpp b/build/clang-plugin/ExplicitImplicitChecker.cpp new file mode 100644 index 0000000000..e0620f502f --- /dev/null +++ b/build/clang-plugin/ExplicitImplicitChecker.cpp @@ -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 "ExplicitImplicitChecker.h" +#include "CustomMatchers.h" + +void ExplicitImplicitChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + cxxConstructorDecl( + isInterestingImplicitCtor(), + ofClass(allOf(isConcreteClass(), decl().bind("class"))), + unless(isMarkedImplicit())) + .bind("ctor"), + this); +} + +void ExplicitImplicitChecker::check(const MatchFinder::MatchResult &Result) { + // We've already checked everything in the matcher, so we just have to report + // the error. + + const CXXConstructorDecl *Ctor = + Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); + const CXXRecordDecl *Declaration = + Result.Nodes.getNodeAs<CXXRecordDecl>("class"); + + FixItHint FixItHint = + FixItHint::CreateInsertion(Ctor->getLocation(), "explicit "); + diag(Ctor->getLocation(), "bad implicit conversion constructor for %0", + DiagnosticIDs::Error) + << Declaration->getDeclName(); + diag(Ctor->getLocation(), + "consider adding the explicit keyword to the constructor", + DiagnosticIDs::Note) + << FixItHint; +} diff --git a/build/clang-plugin/ExplicitImplicitChecker.h b/build/clang-plugin/ExplicitImplicitChecker.h new file mode 100644 index 0000000000..f1591c999e --- /dev/null +++ b/build/clang-plugin/ExplicitImplicitChecker.h @@ -0,0 +1,18 @@ +/* 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 ExplicitImplicitChecker_h__ +#define ExplicitImplicitChecker_h__ + +#include "plugin.h" + +class ExplicitImplicitChecker : public BaseCheck { +public: + ExplicitImplicitChecker(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/ExplicitOperatorBoolChecker.cpp b/build/clang-plugin/ExplicitOperatorBoolChecker.cpp new file mode 100644 index 0000000000..ed97335a00 --- /dev/null +++ b/build/clang-plugin/ExplicitOperatorBoolChecker.cpp @@ -0,0 +1,31 @@ +/* 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 "ExplicitOperatorBoolChecker.h" +#include "CustomMatchers.h" + +void ExplicitOperatorBoolChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + cxxMethodDecl(allOf(isFirstParty(), hasName("operator bool"))) + .bind("node"), + this); +} + +void ExplicitOperatorBoolChecker::check( + const MatchFinder::MatchResult &Result) { + const CXXConversionDecl *Method = + Result.Nodes.getNodeAs<CXXConversionDecl>("node"); + const CXXRecordDecl *Clazz = Method->getParent(); + + if (!Method->isExplicit() && !hasCustomAttribute<moz_implicit>(Method) && + !ASTIsInSystemHeader(Method->getASTContext(), *Method) && + isInterestingDeclForImplicitConversion(Method)) { + diag(Method->getBeginLoc(), "bad implicit conversion operator for %0", + DiagnosticIDs::Error) + << Clazz; + diag(Method->getBeginLoc(), "consider adding the explicit keyword to %0", + DiagnosticIDs::Note) + << "'operator bool'"; + } +} diff --git a/build/clang-plugin/ExplicitOperatorBoolChecker.h b/build/clang-plugin/ExplicitOperatorBoolChecker.h new file mode 100644 index 0000000000..90909e6296 --- /dev/null +++ b/build/clang-plugin/ExplicitOperatorBoolChecker.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 ExplicitOperatorBoolChecker_h__ +#define ExplicitOperatorBoolChecker_h__ + +#include "plugin.h" + +class ExplicitOperatorBoolChecker : public BaseCheck { +public: + ExplicitOperatorBoolChecker(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/FopenUsageChecker.cpp b/build/clang-plugin/FopenUsageChecker.cpp new file mode 100644 index 0000000000..905fc91f47 --- /dev/null +++ b/build/clang-plugin/FopenUsageChecker.cpp @@ -0,0 +1,73 @@ +/* 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 "FopenUsageChecker.h" +#include "CustomMatchers.h" + +void FopenUsageChecker::registerMatchers(MatchFinder *AstMatcher) { + + auto hasConstCharPtrParam = [](const unsigned int Position) { + return hasParameter( + Position, hasType(hasCanonicalType(pointsTo(asString("const char"))))); + }; + + auto hasParamOfType = [](const unsigned int Position, const char *Name) { + return hasParameter(Position, hasType(asString(Name))); + }; + + auto hasIntegerParam = [](const unsigned int Position) { + return hasParameter(Position, hasType(isInteger())); + }; + + AstMatcher->addMatcher( + callExpr( + allOf( + isFirstParty(), + callee(functionDecl(allOf( + isInSystemHeader(), + anyOf( + allOf(anyOf(allOf(hasName("fopen"), + hasConstCharPtrParam(0)), + allOf(hasName("fopen_s"), + hasParameter( + 0, hasType(pointsTo(pointsTo( + asString("FILE"))))), + hasConstCharPtrParam(2))), + hasConstCharPtrParam(1)), + allOf(anyOf(hasName("open"), + allOf(hasName("_open"), hasIntegerParam(2)), + allOf(hasName("_sopen"), hasIntegerParam(3))), + hasConstCharPtrParam(0), hasIntegerParam(1)), + allOf(hasName("_sopen_s"), + hasParameter(0, hasType(pointsTo(isInteger()))), + hasConstCharPtrParam(1), hasIntegerParam(2), + hasIntegerParam(3), hasIntegerParam(4)), + allOf(hasName("OpenFile"), hasConstCharPtrParam(0), + hasParamOfType(1, "LPOFSTRUCT"), + hasIntegerParam(2)), + allOf(hasName("CreateFileA"), hasConstCharPtrParam(0), + hasIntegerParam(1), hasIntegerParam(2), + hasParamOfType(3, "LPSECURITY_ATTRIBUTES"), + hasIntegerParam(4), hasIntegerParam(5), + hasParamOfType(6, "HANDLE")))))), + unless(isInWhitelistForFopenUsage()))) + .bind("funcCall"), + this); +} + +void FopenUsageChecker::check(const MatchFinder::MatchResult &Result) { + const CallExpr *FuncCall = Result.Nodes.getNodeAs<CallExpr>("funcCall"); + static const char *ExtraInfo = + "On Windows executed functions: fopen, fopen_s, open, _open, _sopen, " + "_sopen_s, OpenFile, CreateFileA should never be used due to lossy " + "conversion from UTF8 to ANSI."; + + if (FuncCall) { + diag(FuncCall->getBeginLoc(), + "Usage of ASCII file functions (here %0) is forbidden on Windows.", + DiagnosticIDs::Warning) + << FuncCall->getDirectCallee()->getName(); + diag(FuncCall->getBeginLoc(), ExtraInfo, DiagnosticIDs::Note); + } +} diff --git a/build/clang-plugin/FopenUsageChecker.h b/build/clang-plugin/FopenUsageChecker.h new file mode 100644 index 0000000000..9dc71831ab --- /dev/null +++ b/build/clang-plugin/FopenUsageChecker.h @@ -0,0 +1,18 @@ +/* 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 FopenUsageChecker_h__ +#define FopenUsageChecker_h__ + +#include "plugin.h" + +class FopenUsageChecker : public BaseCheck { +public: + FopenUsageChecker(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/JSHandleRootedTypedefChecker.cpp b/build/clang-plugin/JSHandleRootedTypedefChecker.cpp new file mode 100644 index 0000000000..3e3adf7906 --- /dev/null +++ b/build/clang-plugin/JSHandleRootedTypedefChecker.cpp @@ -0,0 +1,38 @@ +/* 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 "JSHandleRootedTypedefChecker.h" +#include "CustomMatchers.h" + +void JSHandleRootedTypedefChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + declaratorDecl(isUsingJSHandleRootedTypedef(), isNotSpiderMonkey()) + .bind("declaratorDecl"), + this); +} + +std::string getReplacement(std::string TypeName) { + for (auto &pair : JSHandleRootedTypedefMap) { + if (!TypeName.compare(pair[0])) { + return pair[1]; + } + } + llvm_unreachable("Unexpected type name"); +} + +void JSHandleRootedTypedefChecker::check( + const MatchFinder::MatchResult &Result) { + const char *Error = "The fully qualified types are preferred over the " + "shorthand typedefs for JS::Handle/JS::Rooted types " + "outside SpiderMonkey."; + + const DeclaratorDecl *Declarator = + Result.Nodes.getNodeAs<DeclaratorDecl>("declaratorDecl"); + + std::string Replacement = getReplacement(Declarator->getType().getAsString()); + diag(Declarator->getBeginLoc(), Error, DiagnosticIDs::Error) + << FixItHint::CreateReplacement( + Declarator->getTypeSourceInfo()->getTypeLoc().getSourceRange(), + Replacement); +} diff --git a/build/clang-plugin/JSHandleRootedTypedefChecker.h b/build/clang-plugin/JSHandleRootedTypedefChecker.h new file mode 100644 index 0000000000..32fc8fff9b --- /dev/null +++ b/build/clang-plugin/JSHandleRootedTypedefChecker.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 BUILD_CLANG_PLUGIN_JSHANDLEROOTEDTYPEDEFCHECKER_H_ +#define BUILD_CLANG_PLUGIN_JSHANDLEROOTEDTYPEDEFCHECKER_H_ + +#include "plugin.h" + +class JSHandleRootedTypedefChecker : public BaseCheck { +public: + JSHandleRootedTypedefChecker(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/KnownLiveChecker.cpp b/build/clang-plugin/KnownLiveChecker.cpp new file mode 100644 index 0000000000..be0c3739ec --- /dev/null +++ b/build/clang-plugin/KnownLiveChecker.cpp @@ -0,0 +1,35 @@ +/* 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 "KnownLiveChecker.h" +#include "CustomMatchers.h" + +void KnownLiveChecker::registerMatchers(MatchFinder *AstMatcher) { + // Note that this cannot catch mutations after pass-by-reference, and thus no + // error for cycle collection macros. + + auto KnownLiveLHS = hasLHS(memberExpr(hasKnownLiveAnnotation()).bind("lhs")); + auto ForGeneralFunctions = forFunction( + functionDecl(unless(anyOf(cxxConstructorDecl(), cxxDestructorDecl()))) + .bind("func")); + + auto Matcher = + allOf(isAssignmentOperator(), KnownLiveLHS, ForGeneralFunctions); + + AstMatcher->addMatcher(binaryOperator(Matcher), this); + AstMatcher->addMatcher(cxxOperatorCallExpr(Matcher), this); +} + +void KnownLiveChecker::check(const MatchFinder::MatchResult &Result) { + const char *Error = "MOZ_KNOWN_LIVE members can only be modified by " + "constructors and destructors"; + + if (const MemberExpr *Expr = Result.Nodes.getNodeAs<MemberExpr>("lhs")) { + diag(Expr->getBeginLoc(), Error, DiagnosticIDs::Error); + } + if (const CXXOperatorCallExpr *Expr = + Result.Nodes.getNodeAs<CXXOperatorCallExpr>("lhs")) { + diag(Expr->getBeginLoc(), Error, DiagnosticIDs::Error); + } +} diff --git a/build/clang-plugin/KnownLiveChecker.h b/build/clang-plugin/KnownLiveChecker.h new file mode 100644 index 0000000000..bd92d0b326 --- /dev/null +++ b/build/clang-plugin/KnownLiveChecker.h @@ -0,0 +1,18 @@ +/* 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 BUILD_CLANG_PLUGIN_KNOWNLIVECHECKER_H_ +#define BUILD_CLANG_PLUGIN_KNOWNLIVECHECKER_H_ + +#include "plugin.h" + +class KnownLiveChecker : public BaseCheck { +public: + KnownLiveChecker(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/KungFuDeathGripChecker.cpp b/build/clang-plugin/KungFuDeathGripChecker.cpp new file mode 100644 index 0000000000..03bb20514f --- /dev/null +++ b/build/clang-plugin/KungFuDeathGripChecker.cpp @@ -0,0 +1,114 @@ +/* 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 "KungFuDeathGripChecker.h" +#include "CustomMatchers.h" + +void KungFuDeathGripChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(varDecl(allOf(hasType(isRefPtr()), hasLocalStorage(), + hasInitializer(anything()))) + .bind("decl"), + this); +} + +void KungFuDeathGripChecker::check(const MatchFinder::MatchResult &Result) { + const char *Error = "Unused \"kungFuDeathGrip\" %0 objects constructed from " + "%1 are prohibited"; + const char *Note = "Please switch all accesses to this %0 to go through " + "'%1', or explicitly pass '%1' to `mozilla::Unused`"; + + const VarDecl *D = Result.Nodes.getNodeAs<VarDecl>("decl"); + if (D->isReferenced()) { + return; + } + + // Not interested in parameters. + if (isa<ImplicitParamDecl>(D) || isa<ParmVarDecl>(D)) { + return; + } + + const Expr *E = IgnoreTrivials(D->getInit()); + const CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(E); + if (CE && CE->getNumArgs() == 0) { + // We don't report an error when we construct and don't use a nsCOMPtr / + // nsRefPtr with no arguments. We don't report it because the error is not + // related to the current check. In the future it may be reported through a + // more generic mechanism. + return; + } + + // We don't want to look at the single argument conversion constructors + // which are inbetween the declaration and the actual object which we are + // assigning into the nsCOMPtr/RefPtr. To do this, we repeatedly + // IgnoreTrivials, then look at the expression. If it is one of these + // conversion constructors, we ignore it and continue to dig. + while ((CE = dyn_cast<CXXConstructExpr>(E)) && CE->getNumArgs() == 1) { + E = IgnoreTrivials(CE->getArg(0)); + } + + // If the argument expression is an xvalue, we are not taking a copy of + // anything. + if (E->isXValue()) { + return; + } + + // It is possible that the QualType doesn't point to a type yet so we are + // not interested. + if (E->getType().isNull()) { + return; + } + + // We allow taking a kungFuDeathGrip of `this` because it cannot change + // beneath us, so calling directly through `this` is OK. This is the same + // for local variable declarations. + // + // We also don't complain about unused RefPtrs which are constructed from + // the return value of a new expression, as these are required in order to + // immediately destroy the value created (which was presumably created for + // its side effects), and are not used as a death grip. + if (isa<CXXThisExpr>(E) || isa<DeclRefExpr>(E) || isa<CXXNewExpr>(E)) { + return; + } + + // These types are assigned into nsCOMPtr and RefPtr for their side effects, + // and not as a kungFuDeathGrip. We don't want to consider RefPtr and nsCOMPtr + // types which are initialized with these types as errors. + const TagDecl *TD = E->getType()->getAsTagDecl(); + if (TD && TD->getIdentifier()) { + static const char *IgnoreTypes[] = { + "already_AddRefed", + "nsGetServiceByCID", + "nsGetServiceByCIDWithError", + "nsGetServiceByContractID", + "nsGetServiceByContractIDWithError", + "nsCreateInstanceByCID", + "nsCreateInstanceByContractID", + "nsCreateInstanceFromFactory", + }; + + for (uint32_t i = 0; i < sizeof(IgnoreTypes) / sizeof(IgnoreTypes[0]); + ++i) { + if (TD->getName() == IgnoreTypes[i]) { + return; + } + } + } + + // Report the error + const char *ErrThing; + const char *NoteThing; + if (isa<MemberExpr>(E)) { + ErrThing = "members"; + NoteThing = "member"; + } else { + ErrThing = "temporary values"; + NoteThing = "value"; + } + + // We cannot provide the note if we don't have an initializer + diag(D->getBeginLoc(), Error, DiagnosticIDs::Error) + << D->getType() << ErrThing; + diag(E->getBeginLoc(), Note, DiagnosticIDs::Note) + << NoteThing << getNameChecked(D); +} diff --git a/build/clang-plugin/KungFuDeathGripChecker.h b/build/clang-plugin/KungFuDeathGripChecker.h new file mode 100644 index 0000000000..6bb2c76835 --- /dev/null +++ b/build/clang-plugin/KungFuDeathGripChecker.h @@ -0,0 +1,18 @@ +/* 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 KungFuDeathGripChecker_h__ +#define KungFuDeathGripChecker_h__ + +#include "plugin.h" + +class KungFuDeathGripChecker : public BaseCheck { +public: + KungFuDeathGripChecker(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/LoadLibraryUsageChecker.cpp b/build/clang-plugin/LoadLibraryUsageChecker.cpp new file mode 100644 index 0000000000..1c7d336ea9 --- /dev/null +++ b/build/clang-plugin/LoadLibraryUsageChecker.cpp @@ -0,0 +1,34 @@ +/* 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 "LoadLibraryUsageChecker.h" +#include "CustomMatchers.h" + +// On MacOS the filesystem is UTF-8, on linux the canonical filename is 8-bit +// string. On Windows data loss conversion will occur. This checker restricts +// the use of ASCII file functions for loading libraries. + +void LoadLibraryUsageChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + callExpr( + allOf(isFirstParty(), + callee(functionDecl(anyOf( + allOf(isInSystemHeader(), anyOf(hasName("LoadLibraryA"), + hasName("LoadLibraryExA"))), + hasName("PR_LoadLibrary")))), + unless(hasArgument(0, stringLiteral())))) + .bind("funcCall"), + this); +} + +void LoadLibraryUsageChecker::check(const MatchFinder::MatchResult &Result) { + const CallExpr *FuncCall = Result.Nodes.getNodeAs<CallExpr>("funcCall"); + + if (FuncCall) { + diag(FuncCall->getBeginLoc(), + "Usage of ASCII file functions (such as %0) is forbidden.", + DiagnosticIDs::Error) + << FuncCall->getDirectCallee()->getName(); + } +} diff --git a/build/clang-plugin/LoadLibraryUsageChecker.h b/build/clang-plugin/LoadLibraryUsageChecker.h new file mode 100644 index 0000000000..f7b60234be --- /dev/null +++ b/build/clang-plugin/LoadLibraryUsageChecker.h @@ -0,0 +1,18 @@ +/* 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 LoadLibraryUsageChecker_h__ +#define LoadLibraryUsageChecker_h__ + +#include "plugin.h" + +class LoadLibraryUsageChecker : public BaseCheck { +public: + LoadLibraryUsageChecker(StringRef CheckName, ContextType *Context = nullptr) + : BaseCheck(CheckName, Context) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void check(const MatchFinder::MatchResult &Result) override; +}; + +#endif // !defined(LoadLibraryUsageChecker_h__) diff --git a/build/clang-plugin/Makefile.in b/build/clang-plugin/Makefile.in new file mode 100644 index 0000000000..b4c9d40de0 --- /dev/null +++ b/build/clang-plugin/Makefile.in @@ -0,0 +1,14 @@ +# 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 $(topsrcdir)/config/config.mk + +HOST_LDFLAGS += $(LLVM_LDFLAGS) $(CLANG_LDFLAGS) + +ifeq ($(HOST_OS_ARCH),WINNT) +# clang-plugin.dll needs to be deterministic for sccache hashes +HOST_LDFLAGS += -brepro +else +HOST_LDFLAGS += -shared +endif diff --git a/build/clang-plugin/MemMoveAnnotation.h b/build/clang-plugin/MemMoveAnnotation.h new file mode 100644 index 0000000000..0a1a96e8ff --- /dev/null +++ b/build/clang-plugin/MemMoveAnnotation.h @@ -0,0 +1,62 @@ +/* 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 MemMoveAnnotation_h__ +#define MemMoveAnnotation_h__ + +#include "CustomMatchers.h" +#include "CustomTypeAnnotation.h" +#include "Utils.h" + +#include <unordered_set> + +class MemMoveAnnotation final : public CustomTypeAnnotation { +public: + MemMoveAnnotation() + : CustomTypeAnnotation(moz_non_memmovable, "non-memmove()able") {} + + virtual ~MemMoveAnnotation() {} + +protected: + std::string getImplicitReason(const TagDecl *D, + VisitFlags &ToVisit) const override { + // Annotate everything in ::std, with a few exceptions; see bug + // 1201314 for discussion. + if (getDeclarationNamespace(D) != "std") { + return ""; + } + + StringRef Name = getNameChecked(D); + + // If the type has a trivial move constructor and destructor, it is safe to + // memmove, and we don't need to visit any fields. + auto RD = dyn_cast<CXXRecordDecl>(D); + if (RD && RD->isCompleteDefinition() && + (RD->hasTrivialMoveConstructor() || + (!RD->hasMoveConstructor() && RD->hasTrivialCopyConstructor())) && + RD->hasTrivialDestructor()) { + ToVisit = VISIT_NONE; + return ""; + } + + // This doesn't check that it's really ::std::pair and not + // ::std::something_else::pair, but should be good enough. + if (isNameExcepted(Name.data())) { + // If we're an excepted name, stop traversing within the type further, + // and only check template arguments for foreign types. + ToVisit = VISIT_TMPL_ARGS; + return ""; + } + return "it is an stl-provided type not guaranteed to be memmove-able"; + } + +private: + bool isNameExcepted(StringRef Name) const { + return Name == "pair" || Name == "atomic" || Name == "tuple"; + } +}; + +extern MemMoveAnnotation NonMemMovable; + +#endif diff --git a/build/clang-plugin/MozCheckAction.cpp b/build/clang-plugin/MozCheckAction.cpp new file mode 100644 index 0000000000..6ec8c92bd8 --- /dev/null +++ b/build/clang-plugin/MozCheckAction.cpp @@ -0,0 +1,27 @@ +/* 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 "DiagnosticsMatcher.h" +#include "plugin.h" +#include "clang/Frontend/FrontendPluginRegistry.h" + +class MozCheckAction : public PluginASTAction { +public: + ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, + StringRef FileName) override { + void *Buffer = CI.getASTContext().Allocate<DiagnosticsMatcher>(); + auto Matcher = new (Buffer) DiagnosticsMatcher(CI); + return Matcher->makeASTConsumer(); + } + + bool ParseArgs(const CompilerInstance &CI, + const std::vector<std::string> &Args) override { + return true; + } +}; + +static FrontendPluginRegistry::Add<MozCheckAction> X("moz-check", + "check moz action"); + +DenseMap<StringRef, bool> InThirdPartyPathCache; diff --git a/build/clang-plugin/MozillaTidyModule.cpp b/build/clang-plugin/MozillaTidyModule.cpp new file mode 100644 index 0000000000..b1870e7454 --- /dev/null +++ b/build/clang-plugin/MozillaTidyModule.cpp @@ -0,0 +1,45 @@ +/* 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/. */ + +#ifdef CLANG_TIDY + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "ChecksIncludes.inc" +#include "external/ExternalIncludes.inc" +#ifdef MOZ_CLANG_PLUGIN_ALPHA +#include "alpha/AlphaIncludes.inc" +#endif + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +class MozillaModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { +#define CHECK(cls, name) CheckFactories.registerCheck<cls>("mozilla-" name); +#include "Checks.inc" +#include "external/ExternalChecks.inc" +#ifdef MOZ_CLANG_PLUGIN_ALPHA +#include "alpha/AlphaChecks.inc" +#endif +#undef CHECK + } +}; + +// Register the MozillaTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add<MozillaModule> + X("mozilla-module", "Adds Mozilla lint checks."); + +} // namespace tidy +} // namespace clang + +// This anchor is used to force the linker to link in the generated object file +// and thus register the MozillaModule. +volatile int MozillaModuleAnchorSource = 0; + +#endif diff --git a/build/clang-plugin/MustOverrideChecker.cpp b/build/clang-plugin/MustOverrideChecker.cpp new file mode 100644 index 0000000000..b19ae94aac --- /dev/null +++ b/build/clang-plugin/MustOverrideChecker.cpp @@ -0,0 +1,60 @@ +/* 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 "MustOverrideChecker.h" +#include "CustomMatchers.h" + +void MustOverrideChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(cxxRecordDecl(isDefinition()).bind("class"), this); +} + +void MustOverrideChecker::registerPPCallbacks(CompilerInstance &CI) { + this->CI = &CI; +} + +void MustOverrideChecker::check(const MatchFinder::MatchResult &Result) { + auto D = Result.Nodes.getNodeAs<CXXRecordDecl>("class"); + + // Look through all of our immediate bases to find methods that need to be + // overridden + typedef std::vector<CXXMethodDecl *> OverridesVector; + OverridesVector MustOverrides; + for (const auto &Base : D->bases()) { + // The base is either a class (CXXRecordDecl) or it's a templated class... + CXXRecordDecl *Parent = Base.getType() + .getDesugaredType(D->getASTContext()) + ->getAsCXXRecordDecl(); + // The parent might not be resolved to a type yet. In this case, we can't + // do any checking here. For complete correctness, we should visit + // template instantiations, but this case is likely to be rare, so we will + // ignore it until it becomes important. + if (!Parent) { + continue; + } + Parent = Parent->getDefinition(); + for (const auto &M : Parent->methods()) { + if (hasCustomAttribute<moz_must_override>(M)) + MustOverrides.push_back(M); + } + } + + for (auto &O : MustOverrides) { + bool Overridden = false; + for (const auto &M : D->methods()) { + // The way that Clang checks if a method M overrides its parent method + // is if the method has the same name but would not overload. + if (getNameChecked(M) == getNameChecked(O) && + !CI->getSema().IsOverload(M, O, false)) { + Overridden = true; + break; + } + } + if (!Overridden) { + diag(D->getLocation(), "%0 must override %1", DiagnosticIDs::Error) + << D->getDeclName() << O->getDeclName(); + diag(O->getLocation(), "function to override is here", + DiagnosticIDs::Note); + } + } +} diff --git a/build/clang-plugin/MustOverrideChecker.h b/build/clang-plugin/MustOverrideChecker.h new file mode 100644 index 0000000000..ed1835eb57 --- /dev/null +++ b/build/clang-plugin/MustOverrideChecker.h @@ -0,0 +1,22 @@ +/* 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 MustOverrideChecker_h__ +#define MustOverrideChecker_h__ + +#include "plugin.h" + +class MustOverrideChecker : public BaseCheck { +public: + MustOverrideChecker(StringRef CheckName, ContextType *Context = nullptr) + : BaseCheck(CheckName, Context), CI(nullptr) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void registerPPCallbacks(CompilerInstance &CI) override; + void check(const MatchFinder::MatchResult &Result) override; + +private: + const CompilerInstance *CI; +}; + +#endif diff --git a/build/clang-plugin/MustReturnFromCallerChecker.cpp b/build/clang-plugin/MustReturnFromCallerChecker.cpp new file mode 100644 index 0000000000..df28ddb4d3 --- /dev/null +++ b/build/clang-plugin/MustReturnFromCallerChecker.cpp @@ -0,0 +1,136 @@ +/* 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 "MustReturnFromCallerChecker.h" +#include "CustomMatchers.h" + +void MustReturnFromCallerChecker::registerMatchers(MatchFinder *AstMatcher) { + // Look for a call to a MOZ_MUST_RETURN_FROM_CALLER member + AstMatcher->addMatcher( + cxxMemberCallExpr( + on(declRefExpr(to(parmVarDecl()))), + callee(functionDecl(isMozMustReturnFromCaller())), + anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")), + hasAncestor(functionDecl().bind("containing-func")))) + .bind("call"), + this); +} + +void MustReturnFromCallerChecker::check( + const MatchFinder::MatchResult &Result) { + const auto *ContainingLambda = + Result.Nodes.getNodeAs<LambdaExpr>("containing-lambda"); + const auto *ContainingFunc = + Result.Nodes.getNodeAs<FunctionDecl>("containing-func"); + const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call"); + + Stmt *Body = nullptr; + if (ContainingLambda) { + Body = ContainingLambda->getBody(); + } else if (ContainingFunc) { + Body = ContainingFunc->getBody(); + } else { + return; + } + assert(Body && "Should have a body by this point"); + + // Generate the CFG for the enclosing function or decl. + CFG::BuildOptions Options; + std::unique_ptr<CFG> TheCFG = + CFG::buildCFG(nullptr, Body, Result.Context, Options); + if (!TheCFG) { + return; + } + + // Determine which block in the CFG we want to look at the successors of. + StmtToBlockMap BlockMap(TheCFG.get(), Result.Context); + size_t CallIndex; + const auto *Block = BlockMap.blockContainingStmt(Call, &CallIndex); + if (!Block) { + // This statement is not within the CFG! + return; + } + + if (!immediatelyReturns(Block, Result.Context, CallIndex + 1)) { + diag(Call->getBeginLoc(), + "You must immediately return after calling this function", + DiagnosticIDs::Error); + } +} + +bool MustReturnFromCallerChecker::isIgnorable(const Stmt *S) { + auto AfterTrivials = IgnoreTrivials(S); + + // After a call to MOZ_MUST_RETURN_FROM_CALLER function it's ok to have any of + // these expressions. + if (isa<ReturnStmt>(AfterTrivials) || isa<CXXConstructExpr>(AfterTrivials) || + isa<DeclRefExpr>(AfterTrivials) || isa<MemberExpr>(AfterTrivials) || + isa<IntegerLiteral>(AfterTrivials) || + isa<FloatingLiteral>(AfterTrivials) || + isa<CXXNullPtrLiteralExpr>(AfterTrivials) || + isa<CXXBoolLiteralExpr>(AfterTrivials)) { + return true; + } + + // Solitary `this` should be permited, like in the context `return this;` + if (auto TE = dyn_cast<CXXThisExpr>(AfterTrivials)) { + if (TE->child_begin() == TE->child_end()) { + return true; + } + return false; + } + + // For UnaryOperator make sure we only accept arithmetic operations. + if (auto UO = dyn_cast<UnaryOperator>(AfterTrivials)) { + if (!UO->isArithmeticOp()) { + return false; + } + return isIgnorable(UO->getSubExpr()); + } + + // It's also OK to call any function or method which is annotated with + // MOZ_MAY_CALL_AFTER_MUST_RETURN. We consider all CXXConversionDecls + // to be MOZ_MAY_CALL_AFTER_MUST_RETURN (like operator T*()). + if (auto CE = dyn_cast<CallExpr>(AfterTrivials)) { + auto Callee = CE->getDirectCallee(); + if (Callee && hasCustomAttribute<moz_may_call_after_must_return>(Callee)) { + return true; + } + + if (Callee && isa<CXXConversionDecl>(Callee)) { + return true; + } + } + return false; +} + +bool MustReturnFromCallerChecker::immediatelyReturns( + RecurseGuard<const CFGBlock *> Block, ASTContext *TheContext, + size_t FromIdx) { + if (Block.isRepeat()) { + return false; + } + + for (size_t I = FromIdx; I < Block->size(); ++I) { + auto S = (*Block)[I].getAs<CFGStmt>(); + if (!S) { + continue; + } + + // Some statements should be ignored by default due to their CFG context. + if (isIgnorable(S->getStmt())) { + continue; + } + + // Otherwise, this expression is problematic. + return false; + } + + for (auto Succ = Block->succ_begin(); Succ != Block->succ_end(); ++Succ) { + if (!immediatelyReturns(Block.recurse(*Succ), TheContext, 0)) { + return false; + } + } + return true; +} diff --git a/build/clang-plugin/MustReturnFromCallerChecker.h b/build/clang-plugin/MustReturnFromCallerChecker.h new file mode 100644 index 0000000000..68630bf7a7 --- /dev/null +++ b/build/clang-plugin/MustReturnFromCallerChecker.h @@ -0,0 +1,27 @@ +/* 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 MustReturnFromCallerChecker_h__ +#define MustReturnFromCallerChecker_h__ + +#include "RecurseGuard.h" +#include "StmtToBlockMap.h" +#include "Utils.h" +#include "plugin.h" + +class MustReturnFromCallerChecker : public BaseCheck { +public: + MustReturnFromCallerChecker(StringRef CheckName, + ContextType *Context = nullptr) + : BaseCheck(CheckName, Context) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void check(const MatchFinder::MatchResult &Result) override; + +private: + bool isIgnorable(const Stmt *S); + bool immediatelyReturns(RecurseGuard<const CFGBlock *> Block, + ASTContext *TheContext, size_t FromIdx); +}; + +#endif diff --git a/build/clang-plugin/NaNExprChecker.cpp b/build/clang-plugin/NaNExprChecker.cpp new file mode 100644 index 0000000000..bacebc9155 --- /dev/null +++ b/build/clang-plugin/NaNExprChecker.cpp @@ -0,0 +1,54 @@ +/* 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 "NaNExprChecker.h" +#include "CustomMatchers.h" + +void NaNExprChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + binaryOperator( + allOf(binaryEqualityOperator(), + hasLHS(has(ignoringParenImpCasts( + declRefExpr(hasType(qualType((isFloat())))).bind("lhs")))), + hasRHS(has(ignoringParenImpCasts( + declRefExpr(hasType(qualType((isFloat())))).bind("rhs")))), + unless(anyOf(isInSystemHeader(), isInWhitelistForNaNExpr())))) + .bind("node"), + this); +} + +void NaNExprChecker::check(const MatchFinder::MatchResult &Result) { + if (!Result.Context->getLangOpts().CPlusPlus) { + return; + } + + const BinaryOperator *Expression = + Result.Nodes.getNodeAs<BinaryOperator>("node"); + const DeclRefExpr *LHS = Result.Nodes.getNodeAs<DeclRefExpr>("lhs"); + const DeclRefExpr *RHS = Result.Nodes.getNodeAs<DeclRefExpr>("rhs"); + const ImplicitCastExpr *LHSExpr = + dyn_cast<ImplicitCastExpr>(Expression->getLHS()); + const ImplicitCastExpr *RHSExpr = + dyn_cast<ImplicitCastExpr>(Expression->getRHS()); + // The AST subtree that we are looking for will look like this: + // -BinaryOperator ==/!= + // |-ImplicitCastExpr LValueToRValue + // | |-DeclRefExpr + // |-ImplicitCastExpr LValueToRValue + // |-DeclRefExpr + // The check below ensures that we are dealing with the correct AST subtree + // shape, and + // also that both of the found DeclRefExpr's point to the same declaration. + if (LHS->getFoundDecl() == RHS->getFoundDecl() && LHSExpr && RHSExpr && + std::distance(LHSExpr->child_begin(), LHSExpr->child_end()) == 1 && + std::distance(RHSExpr->child_begin(), RHSExpr->child_end()) == 1 && + *LHSExpr->child_begin() == LHS && *RHSExpr->child_begin() == RHS) { + diag(Expression->getBeginLoc(), + "comparing a floating point value to itself for " + "NaN checking can lead to incorrect results", + DiagnosticIDs::Error); + diag(Expression->getBeginLoc(), "consider using std::isnan instead", + DiagnosticIDs::Note); + } +} diff --git a/build/clang-plugin/NaNExprChecker.h b/build/clang-plugin/NaNExprChecker.h new file mode 100644 index 0000000000..313c3cb4cc --- /dev/null +++ b/build/clang-plugin/NaNExprChecker.h @@ -0,0 +1,18 @@ +/* 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 NaNExprChecker_h__ +#define NaNExprChecker_h__ + +#include "plugin.h" + +class NaNExprChecker : public BaseCheck { +public: + NaNExprChecker(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/NeedsNoVTableTypeChecker.cpp b/build/clang-plugin/NeedsNoVTableTypeChecker.cpp new file mode 100644 index 0000000000..9d5ad039ba --- /dev/null +++ b/build/clang-plugin/NeedsNoVTableTypeChecker.cpp @@ -0,0 +1,39 @@ +/* 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 "NeedsNoVTableTypeChecker.h" +#include "CustomMatchers.h" + +void NeedsNoVTableTypeChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + classTemplateSpecializationDecl( + allOf(hasAnyTemplateArgument(refersToType(hasVTable())), + hasNeedsNoVTableTypeAttr())) + .bind("node"), + this); +} + +void NeedsNoVTableTypeChecker::check(const MatchFinder::MatchResult &Result) { + const ClassTemplateSpecializationDecl *Specialization = + Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("node"); + + // Get the offending template argument + QualType Offender; + const TemplateArgumentList &Args = + Specialization->getTemplateInstantiationArgs(); + for (unsigned i = 0; i < Args.size(); ++i) { + Offender = Args[i].getAsType(); + if (typeHasVTable(Offender)) { + break; + } + } + + diag(Specialization->getBeginLoc(), + "%0 cannot be instantiated because %1 has a VTable", + DiagnosticIDs::Error) + << Specialization << Offender; + diag(Specialization->getPointOfInstantiation(), + "bad instantiation of %0 requested here", DiagnosticIDs::Note) + << Specialization; +} diff --git a/build/clang-plugin/NeedsNoVTableTypeChecker.h b/build/clang-plugin/NeedsNoVTableTypeChecker.h new file mode 100644 index 0000000000..abff4d1554 --- /dev/null +++ b/build/clang-plugin/NeedsNoVTableTypeChecker.h @@ -0,0 +1,18 @@ +/* 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 NeedsNoVTableTypeChecker_h__ +#define NeedsNoVTableTypeChecker_h__ + +#include "plugin.h" + +class NeedsNoVTableTypeChecker : public BaseCheck { +public: + NeedsNoVTableTypeChecker(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/NoAddRefReleaseOnReturnChecker.cpp b/build/clang-plugin/NoAddRefReleaseOnReturnChecker.cpp new file mode 100644 index 0000000000..e1c38fa6b2 --- /dev/null +++ b/build/clang-plugin/NoAddRefReleaseOnReturnChecker.cpp @@ -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/. */ + +#include "NoAddRefReleaseOnReturnChecker.h" +#include "CustomMatchers.h" + +void NoAddRefReleaseOnReturnChecker::registerMatchers(MatchFinder *AstMatcher) { + // Look for all of the calls to AddRef() or Release() + AstMatcher->addMatcher( + memberExpr(isAddRefOrRelease(), hasParent(callExpr())).bind("member"), + this); +} + +void NoAddRefReleaseOnReturnChecker::check( + const MatchFinder::MatchResult &Result) { + const MemberExpr *Member = Result.Nodes.getNodeAs<MemberExpr>("member"); + const Expr *Base = IgnoreTrivials(Member->getBase()); + + // Check if the call to AddRef() or Release() was made on the result of a call + // to a MOZ_NO_ADDREF_RELEASE_ON_RETURN function or method. + if (auto *Call = dyn_cast<CallExpr>(Base)) { + if (auto *Callee = Call->getDirectCallee()) { + if (hasCustomAttribute<moz_no_addref_release_on_return>(Callee)) { + diag(Call->getBeginLoc(), + "%1 must not be called on the return value of '%0' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN", + DiagnosticIDs::Error) + << Callee->getQualifiedNameAsString() << dyn_cast<CXXMethodDecl>(Member->getMemberDecl()); + } + } + } +} diff --git a/build/clang-plugin/NoAddRefReleaseOnReturnChecker.h b/build/clang-plugin/NoAddRefReleaseOnReturnChecker.h new file mode 100644 index 0000000000..525f769eff --- /dev/null +++ b/build/clang-plugin/NoAddRefReleaseOnReturnChecker.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 NoAddRefReleaseOnReturnChecker_h__ +#define NoAddRefReleaseOnReturnChecker_h__ + +#include "plugin.h" + +class NoAddRefReleaseOnReturnChecker : public BaseCheck { +public: + NoAddRefReleaseOnReturnChecker(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/NoAutoTypeChecker.cpp b/build/clang-plugin/NoAutoTypeChecker.cpp new file mode 100644 index 0000000000..937c7c5742 --- /dev/null +++ b/build/clang-plugin/NoAutoTypeChecker.cpp @@ -0,0 +1,21 @@ +/* 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 "NoAutoTypeChecker.h" +#include "CustomMatchers.h" + +void NoAutoTypeChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(varDecl(hasType(autoNonAutoableType())).bind("node"), + this); +} + +void NoAutoTypeChecker::check(const MatchFinder::MatchResult &Result) { + const VarDecl *D = Result.Nodes.getNodeAs<VarDecl>("node"); + + diag(D->getLocation(), "Cannot use auto to declare a variable of type %0", + DiagnosticIDs::Error) + << D->getType(); + diag(D->getLocation(), "Please write out this type explicitly", + DiagnosticIDs::Note); +} diff --git a/build/clang-plugin/NoAutoTypeChecker.h b/build/clang-plugin/NoAutoTypeChecker.h new file mode 100644 index 0000000000..0801503846 --- /dev/null +++ b/build/clang-plugin/NoAutoTypeChecker.h @@ -0,0 +1,18 @@ +/* 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 NoAutoTypeChecker_h__ +#define NoAutoTypeChecker_h__ + +#include "plugin.h" + +class NoAutoTypeChecker : public BaseCheck { +public: + NoAutoTypeChecker(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/NoDuplicateRefCntMemberChecker.cpp b/build/clang-plugin/NoDuplicateRefCntMemberChecker.cpp new file mode 100644 index 0000000000..eb78a3bd49 --- /dev/null +++ b/build/clang-plugin/NoDuplicateRefCntMemberChecker.cpp @@ -0,0 +1,65 @@ +/* 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 "NoDuplicateRefCntMemberChecker.h" +#include "CustomMatchers.h" + +void NoDuplicateRefCntMemberChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(cxxRecordDecl().bind("decl"), this); +} + +void NoDuplicateRefCntMemberChecker::check( + const MatchFinder::MatchResult &Result) { + const CXXRecordDecl *D = Result.Nodes.getNodeAs<CXXRecordDecl>("decl"); + const FieldDecl *RefCntMember = getClassRefCntMember(D); + const FieldDecl *FoundRefCntBase = nullptr; + + if (!D->hasDefinition()) + return; + D = D->getDefinition(); + + // If we don't have an mRefCnt member, and we have less than 2 superclasses, + // we don't have to run this loop, as neither case will ever apply. + if (!RefCntMember && D->getNumBases() < 2) { + return; + } + + // Check every superclass for whether it has a base with a refcnt member, and + // warn for those which do + for (auto &Base : D->bases()) { + // Determine if this base class has an mRefCnt member + const FieldDecl *BaseRefCntMember = getBaseRefCntMember(Base.getType()); + + if (BaseRefCntMember) { + if (RefCntMember) { + // We have an mRefCnt, and superclass has an mRefCnt + const char *Error = "Refcounted record %0 has multiple mRefCnt members"; + const char *Note1 = "Superclass %0 also has an mRefCnt member"; + const char *Note2 = + "Consider using the _INHERITED macros for AddRef and Release here"; + + diag(D->getBeginLoc(), Error, DiagnosticIDs::Error) << D; + diag(BaseRefCntMember->getBeginLoc(), Note1, DiagnosticIDs::Note) + << BaseRefCntMember->getParent(); + diag(RefCntMember->getBeginLoc(), Note2, DiagnosticIDs::Note); + } + + if (FoundRefCntBase) { + const char *Error = "Refcounted record %0 has multiple superclasses " + "with mRefCnt members"; + const char *Note = "Superclass %0 has an mRefCnt member"; + + // superclass has mRefCnt, and another superclass also has an mRefCnt + diag(D->getBeginLoc(), Error, DiagnosticIDs::Error) << D; + diag(BaseRefCntMember->getBeginLoc(), Note, DiagnosticIDs::Note) + << BaseRefCntMember->getParent(); + diag(FoundRefCntBase->getBeginLoc(), Note, DiagnosticIDs::Note) + << FoundRefCntBase->getParent(); + } + + // Record that we've found a base with a mRefCnt member + FoundRefCntBase = BaseRefCntMember; + } + } +} diff --git a/build/clang-plugin/NoDuplicateRefCntMemberChecker.h b/build/clang-plugin/NoDuplicateRefCntMemberChecker.h new file mode 100644 index 0000000000..e038ca873b --- /dev/null +++ b/build/clang-plugin/NoDuplicateRefCntMemberChecker.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 NoDuplicateRefCntMemberChecker_h__ +#define NoDuplicateRefCntMemberChecker_h__ + +#include "plugin.h" + +class NoDuplicateRefCntMemberChecker : public BaseCheck { +public: + NoDuplicateRefCntMemberChecker(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/NoExplicitMoveConstructorChecker.cpp b/build/clang-plugin/NoExplicitMoveConstructorChecker.cpp new file mode 100644 index 0000000000..69e324dee3 --- /dev/null +++ b/build/clang-plugin/NoExplicitMoveConstructorChecker.cpp @@ -0,0 +1,25 @@ +/* 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 "NoExplicitMoveConstructorChecker.h" +#include "CustomMatchers.h" + +void NoExplicitMoveConstructorChecker::registerMatchers( + MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + cxxConstructorDecl(isExplicitMoveConstructor(), isFirstParty()) + .bind("node"), + this); +} + +void NoExplicitMoveConstructorChecker::check( + const MatchFinder::MatchResult &Result) { + // Everything we needed to know was checked in the matcher - we just report + // the error here + const CXXConstructorDecl *D = + Result.Nodes.getNodeAs<CXXConstructorDecl>("node"); + + diag(D->getLocation(), "Move constructors may not be marked explicit", + DiagnosticIDs::Error); +} diff --git a/build/clang-plugin/NoExplicitMoveConstructorChecker.h b/build/clang-plugin/NoExplicitMoveConstructorChecker.h new file mode 100644 index 0000000000..adc474c144 --- /dev/null +++ b/build/clang-plugin/NoExplicitMoveConstructorChecker.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 NoExplicitMoveConstructorChecker_h__ +#define NoExplicitMoveConstructorChecker_h__ + +#include "plugin.h" + +class NoExplicitMoveConstructorChecker : public BaseCheck { +public: + NoExplicitMoveConstructorChecker(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/NoNewThreadsChecker.cpp b/build/clang-plugin/NoNewThreadsChecker.cpp new file mode 100644 index 0000000000..90c190f8d1 --- /dev/null +++ b/build/clang-plugin/NoNewThreadsChecker.cpp @@ -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 "NoNewThreadsChecker.h" +#include "CustomMatchers.h" + +void NoNewThreadsChecker::registerMatchers(MatchFinder *AstMatcher) { + // The checker looks for: + // -Instances of NS_NewNamedThread that aren't in allowed files + // -Instances of NS_NewNamedThread that use names that aren't recognized + AstMatcher->addMatcher( + callExpr(allOf(isFirstParty(), + callee(functionDecl(hasName("NS_NewNamedThread"))), + unless(isInAllowlistForThreads()))) + .bind("funcCall"), + this); +} + +void NoNewThreadsChecker::check(const MatchFinder::MatchResult &Result) { + const CallExpr *FuncCall = Result.Nodes.getNodeAs<CallExpr>("funcCall"); + + if (FuncCall) { + diag(FuncCall->getBeginLoc(), + "Thread name not recognized. Please use the background thread pool.", + DiagnosticIDs::Error) + << FuncCall->getDirectCallee()->getName(); + diag( + FuncCall->getBeginLoc(), + "NS_NewNamedThread has been deprecated in favor of background " + "task dispatch via NS_DispatchBackgroundTask and " + "NS_CreateBackgroundTaskQueue. If you must create a new ad-hoc thread, " + "have your thread name added to ThreadAllows.txt.", + DiagnosticIDs::Note); + } +} diff --git a/build/clang-plugin/NoNewThreadsChecker.h b/build/clang-plugin/NoNewThreadsChecker.h new file mode 100644 index 0000000000..e8dc13fece --- /dev/null +++ b/build/clang-plugin/NoNewThreadsChecker.h @@ -0,0 +1,18 @@ +/* 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 NoNewThreadsChecker_h__ +#define NoNewThreadsChecker_h__ + +#include "plugin.h" + +class NoNewThreadsChecker : public BaseCheck { +public: + NoNewThreadsChecker(StringRef CheckName, ContextType *Context = nullptr) + : BaseCheck(CheckName, Context) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void check(const MatchFinder::MatchResult &Result) override; +}; + +#endif // !defined(NoNewThreadsChecker_h__) diff --git a/build/clang-plugin/NoPrincipalGetURI.cpp b/build/clang-plugin/NoPrincipalGetURI.cpp new file mode 100644 index 0000000000..60e22abeb5 --- /dev/null +++ b/build/clang-plugin/NoPrincipalGetURI.cpp @@ -0,0 +1,27 @@ +/* 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 "NoPrincipalGetURI.h" +#include "CustomMatchers.h" + +void NoPrincipalGetURI::registerMatchers(MatchFinder *AstMatcher) { + + AstMatcher->addMatcher( + cxxMemberCallExpr( + allOf(callee(cxxMethodDecl(hasName("GetURI"))), + anyOf(on(hasType(hasCanonicalType(asString("class nsIPrincipal *")))), + on(hasType(hasCanonicalType(asString("class nsIPrincipal"))))), + unless(isInWhiteListForPrincipalGetUri())), + argumentCountIs(1)) + .bind("id"), + this); +} + +void NoPrincipalGetURI::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXMemberCallExpr>("id"); + diag(MatchedDecl->getExprLoc(), + "Principal->GetURI is deprecated and will be removed soon. Please " + "consider using the new helper functions of nsIPrincipal", + DiagnosticIDs::Error); +} diff --git a/build/clang-plugin/NoPrincipalGetURI.h b/build/clang-plugin/NoPrincipalGetURI.h new file mode 100644 index 0000000000..2b39b74bed --- /dev/null +++ b/build/clang-plugin/NoPrincipalGetURI.h @@ -0,0 +1,18 @@ +/* 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 NoPrincipalGetURI_h__ +#define NoPrincipalGetURI_h__ + +#include "plugin.h" + +class NoPrincipalGetURI : public BaseCheck { +public: + NoPrincipalGetURI(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/NoUsingNamespaceMozillaJavaChecker.cpp b/build/clang-plugin/NoUsingNamespaceMozillaJavaChecker.cpp new file mode 100644 index 0000000000..20449dba92 --- /dev/null +++ b/build/clang-plugin/NoUsingNamespaceMozillaJavaChecker.cpp @@ -0,0 +1,24 @@ +/* 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 "NoUsingNamespaceMozillaJavaChecker.h" +#include "CustomMatchers.h" + +void NoUsingNamespaceMozillaJavaChecker::registerMatchers( + MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + usingDirectiveDecl(isUsingNamespaceMozillaJava()).bind("directive"), + this); +} + +void NoUsingNamespaceMozillaJavaChecker::check( + const MatchFinder::MatchResult &Result) { + const UsingDirectiveDecl *Directive = + Result.Nodes.getNodeAs<UsingDirectiveDecl>("directive"); + const NamespaceDecl *Namespace = Directive->getNominatedNamespace(); + + diag(Directive->getUsingLoc(), "using namespace %0 is forbidden", + DiagnosticIDs::Error) + << Namespace->getQualifiedNameAsString(); +} diff --git a/build/clang-plugin/NoUsingNamespaceMozillaJavaChecker.h b/build/clang-plugin/NoUsingNamespaceMozillaJavaChecker.h new file mode 100644 index 0000000000..7b68ba9333 --- /dev/null +++ b/build/clang-plugin/NoUsingNamespaceMozillaJavaChecker.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 NoUsingNamespaceMozillaJavaChecker_h__ +#define NoUsingNamespaceMozillaJavaChecker_h__ + +#include "plugin.h" + +class NoUsingNamespaceMozillaJavaChecker : public BaseCheck { +public: + NoUsingNamespaceMozillaJavaChecker(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/NonMemMovableMemberChecker.cpp b/build/clang-plugin/NonMemMovableMemberChecker.cpp new file mode 100644 index 0000000000..232c634534 --- /dev/null +++ b/build/clang-plugin/NonMemMovableMemberChecker.cpp @@ -0,0 +1,34 @@ +/* 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 "NonMemMovableMemberChecker.h" +#include "CustomMatchers.h" + +MemMoveAnnotation NonMemMovable = MemMoveAnnotation(); + +void NonMemMovableMemberChecker::registerMatchers(MatchFinder *AstMatcher) { + // Handle non-mem-movable members + AstMatcher->addMatcher(cxxRecordDecl(needsMemMovableMembers()).bind("decl"), + this); +} + +void NonMemMovableMemberChecker::check(const MatchFinder::MatchResult &Result) { + const char *Error = + "class %0 cannot have non-memmovable member %1 of type %2"; + + // Get the specialization + const CXXRecordDecl *Declaration = + Result.Nodes.getNodeAs<CXXRecordDecl>("decl"); + + // Report an error for every member which is non-memmovable + for (const FieldDecl *Field : Declaration->fields()) { + QualType Type = Field->getType(); + if (NonMemMovable.hasEffectiveAnnotation(Type)) { + diag(Field->getLocation(), Error, DiagnosticIDs::Error) + << Declaration << Field << Type; + NonMemMovable.dumpAnnotationReason(*this, Type, + Declaration->getLocation()); + } + } +} diff --git a/build/clang-plugin/NonMemMovableMemberChecker.h b/build/clang-plugin/NonMemMovableMemberChecker.h new file mode 100644 index 0000000000..0fc9b1f87f --- /dev/null +++ b/build/clang-plugin/NonMemMovableMemberChecker.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 NonMemMovableMemberChecker_h__ +#define NonMemMovableMemberChecker_h__ + +#include "plugin.h" + +class NonMemMovableMemberChecker : public BaseCheck { +public: + NonMemMovableMemberChecker(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/NonMemMovableTemplateArgChecker.cpp b/build/clang-plugin/NonMemMovableTemplateArgChecker.cpp new file mode 100644 index 0000000000..65afe21006 --- /dev/null +++ b/build/clang-plugin/NonMemMovableTemplateArgChecker.cpp @@ -0,0 +1,51 @@ +/* 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 "NonMemMovableTemplateArgChecker.h" +#include "CustomMatchers.h" + +void NonMemMovableTemplateArgChecker::registerMatchers( + MatchFinder *AstMatcher) { + // Handle non-mem-movable template specializations + AstMatcher->addMatcher( + classTemplateSpecializationDecl( + allOf(needsMemMovableTemplateArg(), + hasAnyTemplateArgument(refersToType(isNonMemMovable())))) + .bind("specialization"), + this); +} + +void NonMemMovableTemplateArgChecker::check( + const MatchFinder::MatchResult &Result) { + const char *Error = + "Cannot instantiate %0 with non-memmovable template argument %1"; + const char *Note = "instantiation of %0 requested here"; + + // Get the specialization + const ClassTemplateSpecializationDecl *Specialization = + Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("specialization"); + SourceLocation RequestLoc = Specialization->getPointOfInstantiation(); + + // Report an error for every template argument which is non-memmovable + const TemplateArgumentList &Args = + Specialization->getTemplateInstantiationArgs(); + for (unsigned i = 0; i < Args.size(); ++i) { + QualType ArgType = Args[i].getAsType(); + if (NonMemMovable.hasEffectiveAnnotation(ArgType)) { + diag(Specialization->getLocation(), Error, DiagnosticIDs::Error) + << Specialization << ArgType; + // XXX It would be really nice if we could get the instantiation stack + // information + // from Sema such that we could print a full template instantiation stack, + // however, + // it seems as though that information is thrown out by the time we get + // here so we + // can only report one level of template specialization (which in many + // cases won't + // be useful) + diag(RequestLoc, Note, DiagnosticIDs::Note) << Specialization; + NonMemMovable.dumpAnnotationReason(*this, ArgType, RequestLoc); + } + } +} diff --git a/build/clang-plugin/NonMemMovableTemplateArgChecker.h b/build/clang-plugin/NonMemMovableTemplateArgChecker.h new file mode 100644 index 0000000000..cd94c95930 --- /dev/null +++ b/build/clang-plugin/NonMemMovableTemplateArgChecker.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 NonMemMovableTemplateArgChecker_h__ +#define NonMemMovableTemplateArgChecker_h__ + +#include "plugin.h" + +class NonMemMovableTemplateArgChecker : public BaseCheck { +public: + NonMemMovableTemplateArgChecker(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/NonParamInsideFunctionDeclChecker.cpp b/build/clang-plugin/NonParamInsideFunctionDeclChecker.cpp new file mode 100644 index 0000000000..e74fd2bb08 --- /dev/null +++ b/build/clang-plugin/NonParamInsideFunctionDeclChecker.cpp @@ -0,0 +1,217 @@ +/* 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 "NonParamInsideFunctionDeclChecker.h" +#include "CustomMatchers.h" +#include "clang/Basic/TargetInfo.h" + +class NonParamAnnotation : public CustomTypeAnnotation { +public: + NonParamAnnotation() : CustomTypeAnnotation(moz_non_param, "non-param"){}; + +protected: + // Helper for checking if a Decl has an explicitly specified alignment. + // Returns the alignment, in char units, of the largest alignment attribute, + // if it exceeds pointer alignment, and 0 otherwise. + static unsigned checkExplicitAlignment(const Decl *D) { + ASTContext &Context = D->getASTContext(); +#if CLANG_VERSION_FULL >= 1600 + unsigned PointerAlign = Context.getTargetInfo().getPointerAlign(LangAS::Default); +#else + unsigned PointerAlign = Context.getTargetInfo().getPointerAlign(0); +#endif + + // getMaxAlignment gets the largest alignment, in bits, specified by an + // alignment attribute directly on the declaration. If no alignment + // attribute is specified, it will return `0`. + unsigned MaxAlign = D->getMaxAlignment(); + if (MaxAlign > PointerAlign) { + return Context.toCharUnitsFromBits(MaxAlign).getQuantity(); + } + return 0; + } + + // This is directly derived from the logic in Clang's `canPassInRegisters` + // function, from `SemaDeclCXX`. It is used instead of `canPassInRegisters` to + // behave consistently on 64-bit windows platforms which are overly + // permissive, allowing too many types to be passed in registers. + // + // Types which can be passed in registers will be re-aligned in the called + // function by clang, so aren't impacted by the win32 object passing ABI + // alignment issue. + static bool canPassAsTemporary(const CXXRecordDecl *D) { + // Per C++ [class.temporary]p3: + // + // When an object of class type X is passed to or returned from a function, + // if X has at least one eligible copy or move constructor ([special]), each + // such constructor is trivial, and the destructor of X is either trivial or + // deleted, implementations are permitted to create a temporary object to + // hold the function parameter or result object. + // + // The temporary object is constructed from the function argument or return + // value, respectively, and the function's parameter or return object is + // initialized as if by using the eligible trivial constructor to copy the + // temporary (even if that constructor is inaccessible or would not be + // selected by overload resolution to perform a copy or move of the object). + bool HasNonDeletedCopyOrMove = false; + + if (D->needsImplicitCopyConstructor() && + !D->defaultedCopyConstructorIsDeleted()) { + if (!D->hasTrivialCopyConstructorForCall()) + return false; + HasNonDeletedCopyOrMove = true; + } + + if (D->needsImplicitMoveConstructor() && + !D->defaultedMoveConstructorIsDeleted()) { + if (!D->hasTrivialMoveConstructorForCall()) + return false; + HasNonDeletedCopyOrMove = true; + } + + if (D->needsImplicitDestructor() && !D->defaultedDestructorIsDeleted() && + !D->hasTrivialDestructorForCall()) + return false; + + for (const CXXMethodDecl *MD : D->methods()) { + if (MD->isDeleted()) + continue; + + auto *CD = dyn_cast<CXXConstructorDecl>(MD); + if (CD && CD->isCopyOrMoveConstructor()) + HasNonDeletedCopyOrMove = true; + else if (!isa<CXXDestructorDecl>(MD)) + continue; + + if (!MD->isTrivialForCall()) + return false; + } + + return HasNonDeletedCopyOrMove; + } + + // Adding alignas(_) on a struct implicitly marks it as MOZ_NON_PARAM, due to + // MSVC limitations which prevent passing explcitly aligned types by value as + // parameters. This overload of hasFakeAnnotation injects fake MOZ_NON_PARAM + // annotations onto these types. + std::string getImplicitReason(const TagDecl *D, + VisitFlags &ToVisit) const override { + // Some stdlib types are known to have alignments over the pointer size on + // non-win32 platforms, but should not be linted against. Clear any + // annotations on those types. + if (!D->getASTContext().getTargetInfo().getCXXABI().isMicrosoft() && + getDeclarationNamespace(D) == "std") { + StringRef Name = getNameChecked(D); + if (Name == "function") { + ToVisit = VISIT_NONE; + return ""; + } + } + + // If the type doesn't have a destructor and can be passed with a temporary, + // clang will handle re-aligning it for us automatically, and we don't need + // to worry about the passed alignment. + auto RD = dyn_cast<CXXRecordDecl>(D); + if (RD && RD->isCompleteDefinition() && canPassAsTemporary(RD)) { + return ""; + } + + // Check if the decl itself has an explicit alignment on it. + if (unsigned ExplicitAlign = checkExplicitAlignment(D)) { + return "it has an explicit alignment of '" + + std::to_string(ExplicitAlign) + "'"; + } + + // Check if any of the decl's fields have an explicit alignment on them. + if (auto RD = dyn_cast<RecordDecl>(D)) { + for (auto F : RD->fields()) { + if (unsigned ExplicitAlign = checkExplicitAlignment(F)) { + return ("member '" + F->getName() + + "' has an explicit alignment of '" + + std::to_string(ExplicitAlign) + "'") + .str(); + } + } + } + + // We don't need to check the types of fields, as the CustomTypeAnnotation + // infrastructure will handle that for us. + return ""; + } +}; +NonParamAnnotation NonParam; + +void NonParamInsideFunctionDeclChecker::registerMatchers( + MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + functionDecl( + anyOf(allOf(isDefinition(), + hasAncestor( + classTemplateSpecializationDecl().bind("spec"))), + isDefinition())) + .bind("func"), + this); + AstMatcher->addMatcher(lambdaExpr().bind("lambda"), this); +} + +void NonParamInsideFunctionDeclChecker::check( + const MatchFinder::MatchResult &Result) { + static DenseSet<const FunctionDecl *> CheckedFunctionDecls; + + const FunctionDecl *func = Result.Nodes.getNodeAs<FunctionDecl>("func"); + if (!func) { + const LambdaExpr *lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda"); + if (lambda) { + func = lambda->getCallOperator(); + } + } + + if (!func) { + return; + } + + if (func->isDeleted()) { + return; + } + + // We need to skip decls which have these types as parameters in system + // headers, because presumably those headers act like an assertion that the + // alignment will be preserved in that situation. + if (getDeclarationNamespace(func) == "std") { + return; + } + + if (inThirdPartyPath(func)) { + return; + } + + // Don't report errors on the same declarations more than once. + if (CheckedFunctionDecls.count(func)) { + return; + } + CheckedFunctionDecls.insert(func); + + const ClassTemplateSpecializationDecl *Spec = + Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("spec"); + + for (ParmVarDecl *p : func->parameters()) { + QualType T = p->getType().withoutLocalFastQualifiers(); + if (NonParam.hasEffectiveAnnotation(T)) { + diag(p->getLocation(), "Type %0 must not be used as parameter", + DiagnosticIDs::Error) + << T; + diag(p->getLocation(), + "Please consider passing a const reference instead", + DiagnosticIDs::Note); + + if (Spec) { + diag(Spec->getPointOfInstantiation(), + "The bad argument was passed to %0 here", DiagnosticIDs::Note) + << Spec->getSpecializedTemplate(); + } + + NonParam.dumpAnnotationReason(*this, T, p->getLocation()); + } + } +} diff --git a/build/clang-plugin/NonParamInsideFunctionDeclChecker.h b/build/clang-plugin/NonParamInsideFunctionDeclChecker.h new file mode 100644 index 0000000000..ed11f3fe12 --- /dev/null +++ b/build/clang-plugin/NonParamInsideFunctionDeclChecker.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 NonParamInsideFunctionDeclChecker_h__ +#define NonParamInsideFunctionDeclChecker_h__ + +#include "plugin.h" + +class NonParamInsideFunctionDeclChecker : public BaseCheck { +public: + NonParamInsideFunctionDeclChecker(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/NonTrivialTypeInFfiChecker.cpp b/build/clang-plugin/NonTrivialTypeInFfiChecker.cpp new file mode 100644 index 0000000000..f482fb131f --- /dev/null +++ b/build/clang-plugin/NonTrivialTypeInFfiChecker.cpp @@ -0,0 +1,56 @@ +/* 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 "NonTrivialTypeInFfiChecker.h" +#include "CustomMatchers.h" + +void NonTrivialTypeInFfiChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(functionDecl(isExternC()).bind("func"), this); +} + +void NonTrivialTypeInFfiChecker::check(const MatchFinder::MatchResult &Result) { + static DenseSet<const FunctionDecl *> CheckedFunctionDecls; + + const FunctionDecl *func = Result.Nodes.getNodeAs<FunctionDecl>("func"); + // Don't report errors on the same declarations more than once. + if (!CheckedFunctionDecls.insert(func).second) { + return; + } + + if (inThirdPartyPath(func)) { + return; + } + + auto NoteFor = [](const QualType &T) -> std::string { + std::string s = "Please consider using a pointer or reference"; + if (T->getAs<TemplateSpecializationType>()) { + s += ", or explicitly instantiating the template"; + } + return s + " instead"; + }; + + for (ParmVarDecl *p : func->parameters()) { + QualType T = p->getType().getUnqualifiedType(); + if (!T->isVoidType() && !T->isReferenceType() && + !T.isTriviallyCopyableType(*Result.Context)) { + diag(p->getLocation(), + "Type %0 must not be used as parameter to extern " + "\"C\" function", + DiagnosticIDs::Error) + << T; + diag(p->getLocation(), NoteFor(T), DiagnosticIDs::Note); + } + } + + QualType T = func->getReturnType().getUnqualifiedType(); + if (!T->isVoidType() && !T->isReferenceType() && + !T.isTriviallyCopyableType(*Result.Context)) { + diag(func->getLocation(), + "Type %0 must not be used as return type of " + "extern \"C\" function", + DiagnosticIDs::Error) + << T; + diag(func->getLocation(), NoteFor(T), DiagnosticIDs::Note); + } +} diff --git a/build/clang-plugin/NonTrivialTypeInFfiChecker.h b/build/clang-plugin/NonTrivialTypeInFfiChecker.h new file mode 100644 index 0000000000..106c64c021 --- /dev/null +++ b/build/clang-plugin/NonTrivialTypeInFfiChecker.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 NonTrivialTypeInFfiChecker_h__ +#define NonTrivialTypeInFfiChecker_h__ + +#include "plugin.h" + +class NonTrivialTypeInFfiChecker : public BaseCheck { +public: + NonTrivialTypeInFfiChecker(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/OverrideBaseCallChecker.cpp b/build/clang-plugin/OverrideBaseCallChecker.cpp new file mode 100644 index 0000000000..600d431335 --- /dev/null +++ b/build/clang-plugin/OverrideBaseCallChecker.cpp @@ -0,0 +1,109 @@ +/* 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 "OverrideBaseCallChecker.h" +#include "CustomMatchers.h" + +void OverrideBaseCallChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(cxxRecordDecl(hasBaseClasses()).bind("class"), this); +} + +bool OverrideBaseCallChecker::isRequiredBaseMethod( + const CXXMethodDecl *Method) { + return hasCustomAttribute<moz_required_base_method>(Method); +} + +void OverrideBaseCallChecker::evaluateExpression( + const Stmt *StmtExpr, std::list<const CXXMethodDecl *> &MethodList) { + // Continue while we have methods in our list + if (!MethodList.size()) { + return; + } + + if (auto MemberFuncCall = dyn_cast<CXXMemberCallExpr>(StmtExpr)) { + if (auto Method = + dyn_cast<CXXMethodDecl>(MemberFuncCall->getDirectCallee())) { + findBaseMethodCall(Method, MethodList); + } + } + + for (auto S : StmtExpr->children()) { + if (S) { + evaluateExpression(S, MethodList); + } + } +} + +void OverrideBaseCallChecker::getRequiredBaseMethod( + const CXXMethodDecl *Method, + std::list<const CXXMethodDecl *> &MethodsList) { + + if (isRequiredBaseMethod(Method)) { + MethodsList.push_back(Method); + } else { + // Loop through all it's base methods. + for (auto BaseMethod = Method->begin_overridden_methods(); + BaseMethod != Method->end_overridden_methods(); BaseMethod++) { + getRequiredBaseMethod(*BaseMethod, MethodsList); + } + } +} + +void OverrideBaseCallChecker::findBaseMethodCall( + const CXXMethodDecl *Method, + std::list<const CXXMethodDecl *> &MethodsList) { + + MethodsList.remove(Method); + // Loop also through all it's base methods; + for (auto BaseMethod = Method->begin_overridden_methods(); + BaseMethod != Method->end_overridden_methods(); BaseMethod++) { + findBaseMethodCall(*BaseMethod, MethodsList); + } +} + +void OverrideBaseCallChecker::check(const MatchFinder::MatchResult &Result) { + const char *Error = + "Method %0 must be called in all overrides, but is not called in " + "this override defined for class %1"; + const CXXRecordDecl *Decl = Result.Nodes.getNodeAs<CXXRecordDecl>("class"); + + // Loop through the methods and look for the ones that are overridden. + for (auto Method : Decl->methods()) { + // If this method doesn't override other methods or it doesn't have a body, + // continue to the next declaration. + if (!Method->size_overridden_methods() || !Method->hasBody()) { + continue; + } + + // Preferred the usage of list instead of vector in order to avoid + // calling erase-remove when deleting items + std::list<const CXXMethodDecl *> MethodsList; + // For each overridden method push it to a list if it meets our + // criteria + for (auto BaseMethod = Method->begin_overridden_methods(); + BaseMethod != Method->end_overridden_methods(); BaseMethod++) { + getRequiredBaseMethod(*BaseMethod, MethodsList); + } + + // If no method has been found then no annotation was used + // so checking is not needed + if (!MethodsList.size()) { + continue; + } + + // Loop through the body of our method and search for calls to + // base methods + evaluateExpression(Method->getBody(), MethodsList); + + // If list is not empty pop up errors + for (auto BaseMethod : MethodsList) { + std::string QualName; + raw_string_ostream OS(QualName); + BaseMethod->printQualifiedName(OS); + + diag(Method->getLocation(), Error, DiagnosticIDs::Error) + << OS.str() << Decl->getName(); + } + } +} diff --git a/build/clang-plugin/OverrideBaseCallChecker.h b/build/clang-plugin/OverrideBaseCallChecker.h new file mode 100644 index 0000000000..e919af6749 --- /dev/null +++ b/build/clang-plugin/OverrideBaseCallChecker.h @@ -0,0 +1,27 @@ +/* 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 OverrideBaseCallChecker_h__ +#define OverrideBaseCallChecker_h__ + +#include "plugin.h" + +class OverrideBaseCallChecker : public BaseCheck { +public: + OverrideBaseCallChecker(StringRef CheckName, ContextType *Context = nullptr) + : BaseCheck(CheckName, Context) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void check(const MatchFinder::MatchResult &Result) override; + +private: + void evaluateExpression(const Stmt *StmtExpr, + std::list<const CXXMethodDecl *> &MethodList); + void getRequiredBaseMethod(const CXXMethodDecl *Method, + std::list<const CXXMethodDecl *> &MethodsList); + void findBaseMethodCall(const CXXMethodDecl *Method, + std::list<const CXXMethodDecl *> &MethodsList); + bool isRequiredBaseMethod(const CXXMethodDecl *Method); +}; + +#endif diff --git a/build/clang-plugin/OverrideBaseCallUsageChecker.cpp b/build/clang-plugin/OverrideBaseCallUsageChecker.cpp new file mode 100644 index 0000000000..34b9cd16a9 --- /dev/null +++ b/build/clang-plugin/OverrideBaseCallUsageChecker.cpp @@ -0,0 +1,21 @@ +/* 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 "OverrideBaseCallUsageChecker.h" +#include "CustomMatchers.h" + +void OverrideBaseCallUsageChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + cxxMethodDecl(isNonVirtual(), isRequiredBaseMethod()).bind("method"), + this); +} + +void OverrideBaseCallUsageChecker::check( + const MatchFinder::MatchResult &Result) { + const char *Error = + "MOZ_REQUIRED_BASE_METHOD can be used only on virtual methods"; + const CXXMethodDecl *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method"); + + diag(Method->getLocation(), Error, DiagnosticIDs::Error); +} diff --git a/build/clang-plugin/OverrideBaseCallUsageChecker.h b/build/clang-plugin/OverrideBaseCallUsageChecker.h new file mode 100644 index 0000000000..0e81d72387 --- /dev/null +++ b/build/clang-plugin/OverrideBaseCallUsageChecker.h @@ -0,0 +1,23 @@ +/* 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 OverrideBaseCallUsageChecker_h__ +#define OverrideBaseCallUsageChecker_h__ + +#include "plugin.h" + +/* + * This is a companion checker for OverrideBaseCallChecker that rejects + * the usage of MOZ_REQUIRED_BASE_METHOD on non-virtual base methods. + */ +class OverrideBaseCallUsageChecker : public BaseCheck { +public: + OverrideBaseCallUsageChecker(StringRef CheckName = "override-base-call-usage", + 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/ParamTraitsEnumChecker.cpp b/build/clang-plugin/ParamTraitsEnumChecker.cpp new file mode 100644 index 0000000000..7214e9fe5b --- /dev/null +++ b/build/clang-plugin/ParamTraitsEnumChecker.cpp @@ -0,0 +1,38 @@ +/* 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 "ParamTraitsEnumChecker.h" +#include "CustomMatchers.h" + +void ParamTraitsEnumChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + classTemplateSpecializationDecl(hasName("ParamTraits")).bind("decl"), + this); +} + +void ParamTraitsEnumChecker::check(const MatchFinder::MatchResult &Result) { + const ClassTemplateSpecializationDecl *Decl = + Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("decl"); + + for (auto &Inner : Decl->decls()) { + if (auto *Def = dyn_cast<TypedefDecl>(Inner)) { + QualType UnderlyingType = Def->getUnderlyingType(); + QualType CanonicalType = UnderlyingType.getCanonicalType(); + + const clang::Type *TypePtr = CanonicalType.getTypePtrOrNull(); + if (!TypePtr) { + return; + } + + if (TypePtr->isEnumeralType()) { + diag(Decl->getBeginLoc(), + "Custom ParamTraits implementation for an enum type", + DiagnosticIDs::Error); + diag(Decl->getBeginLoc(), + "Please use a helper class for example ContiguousEnumSerializer", + DiagnosticIDs::Note); + } + } + } +} diff --git a/build/clang-plugin/ParamTraitsEnumChecker.h b/build/clang-plugin/ParamTraitsEnumChecker.h new file mode 100644 index 0000000000..e89a7d2895 --- /dev/null +++ b/build/clang-plugin/ParamTraitsEnumChecker.h @@ -0,0 +1,18 @@ +/* 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 ParamTraitsEnumChecker_h__ +#define ParamTraitsEnumChecker_h__ + +#include "plugin.h" + +class ParamTraitsEnumChecker : public BaseCheck { +public: + ParamTraitsEnumChecker(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/RecurseGuard.h b/build/clang-plugin/RecurseGuard.h new file mode 100644 index 0000000000..5daf55a9e8 --- /dev/null +++ b/build/clang-plugin/RecurseGuard.h @@ -0,0 +1,56 @@ +/* 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 RecurseGuard_h__ +#define RecurseGuard_h__ + +#include "Utils.h" + +// This class acts as a tracker for avoiding infinite recursion when traversing +// chains in CFGs etc. +// +// Constructing a RecurseGuard sets up a shared backing store which tracks the +// currently observed objects. Whenever recursing, use RecurseGuard.recurse(T) +// to construct another RecurseGuard with the same backing store. +// +// The RecurseGuard object will unregister its object when it is destroyed, and +// has a method `isRepeat()` which will return `true` if the item was already +// seen. +template <typename T> class RecurseGuard { +public: + RecurseGuard(T Thing) : Thing(Thing), Set(new DenseSet<T>()), Repeat(false) { + Set->insert(Thing); + } + RecurseGuard(T Thing, std::shared_ptr<DenseSet<T>> &Set) + : Thing(Thing), Set(Set), Repeat(false) { + Repeat = !Set->insert(Thing).second; + } + RecurseGuard(const RecurseGuard &) = delete; + RecurseGuard(RecurseGuard &&Other) + : Thing(Other.Thing), Set(Other.Set), Repeat(Other.Repeat) { + Other.Repeat = true; + } + ~RecurseGuard() { + if (!Repeat) { + Set->erase(Thing); + } + } + + bool isRepeat() { return Repeat; } + + T get() { return Thing; } + + operator T() { return Thing; } + + T operator->() { return Thing; } + + RecurseGuard recurse(T NewThing) { return RecurseGuard(NewThing, Set); } + +private: + T Thing; + std::shared_ptr<DenseSet<T>> Set; + bool Repeat; +}; + +#endif // RecurseGuard_h__ diff --git a/build/clang-plugin/RefCountedCopyConstructorChecker.cpp b/build/clang-plugin/RefCountedCopyConstructorChecker.cpp new file mode 100644 index 0000000000..569d4eecec --- /dev/null +++ b/build/clang-plugin/RefCountedCopyConstructorChecker.cpp @@ -0,0 +1,34 @@ +/* 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 "RefCountedCopyConstructorChecker.h" +#include "CustomMatchers.h" + +void RefCountedCopyConstructorChecker::registerMatchers( + MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(isCompilerProvidedCopyConstructor(), + ofClass(hasRefCntMember())))) + .bind("node"), + this); +} + +void RefCountedCopyConstructorChecker::check( + const MatchFinder::MatchResult &Result) { + const char *Error = + "Invalid use of compiler-provided copy constructor on refcounted type"; + const char *Note = "The default copy constructor also copies the " + "default mRefCnt property, leading to reference " + "count imbalance issues. Please provide your own " + "copy constructor which only copies the fields which " + "need to be copied"; + + // Everything we needed to know was checked in the matcher - we just report + // the error here + const CXXConstructExpr *E = Result.Nodes.getNodeAs<CXXConstructExpr>("node"); + + diag(E->getLocation(), Error, DiagnosticIDs::Error); + diag(E->getLocation(), Note, DiagnosticIDs::Note); +} diff --git a/build/clang-plugin/RefCountedCopyConstructorChecker.h b/build/clang-plugin/RefCountedCopyConstructorChecker.h new file mode 100644 index 0000000000..edae63534a --- /dev/null +++ b/build/clang-plugin/RefCountedCopyConstructorChecker.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 RefCountedCopyConstructorChecker_h__ +#define RefCountedCopyConstructorChecker_h__ + +#include "plugin.h" + +class RefCountedCopyConstructorChecker : public BaseCheck { +public: + RefCountedCopyConstructorChecker(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/RefCountedInsideLambdaChecker.cpp b/build/clang-plugin/RefCountedInsideLambdaChecker.cpp new file mode 100644 index 0000000000..3256aa8f04 --- /dev/null +++ b/build/clang-plugin/RefCountedInsideLambdaChecker.cpp @@ -0,0 +1,164 @@ +/* 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 "RefCountedInsideLambdaChecker.h" +#include "CustomMatchers.h" + +RefCountedMap RefCountedClasses; + +void RefCountedInsideLambdaChecker::registerMatchers(MatchFinder *AstMatcher) { + // We want to reject any code which captures a pointer to an object of a + // refcounted type, and then lets that value escape. As a primitive analysis, + // we reject any occurances of the lambda as a template parameter to a class + // (which could allow it to escape), as well as any presence of such a lambda + // in a return value (either from lambdas, or in c++14, auto functions). + // + // We check these lambdas' capture lists for raw pointers to refcounted types. + AstMatcher->addMatcher(functionDecl(returns(recordType(hasDeclaration( + cxxRecordDecl(isLambdaDecl()).bind("decl"))))), + this); + AstMatcher->addMatcher(lambdaExpr().bind("lambdaExpr"), this); + AstMatcher->addMatcher( + classTemplateSpecializationDecl( + hasAnyTemplateArgument(refersToType(recordType( + hasDeclaration(cxxRecordDecl(isLambdaDecl()).bind("decl")))))), + this); +} + +void RefCountedInsideLambdaChecker::emitDiagnostics(SourceLocation Loc, + StringRef Name, + QualType Type) { + diag(Loc, + "Refcounted variable '%0' of type %1 cannot be captured by a lambda", + DiagnosticIDs::Error) + << Name << Type; + diag(Loc, "Please consider using a smart pointer", DiagnosticIDs::Note); +} + +static bool IsKnownLive(const VarDecl *Var) { + const Stmt *Init = Var->getInit(); + if (!Init) { + return false; + } + if (auto *Call = dyn_cast<CallExpr>(Init)) { + const FunctionDecl *Callee = Call->getDirectCallee(); + return Callee && Callee->getName() == "MOZ_KnownLive"; + } + return false; +} + +void RefCountedInsideLambdaChecker::check( + const MatchFinder::MatchResult &Result) { + static DenseSet<const CXXRecordDecl *> CheckedDecls; + + const CXXRecordDecl *Lambda = Result.Nodes.getNodeAs<CXXRecordDecl>("decl"); + + if (const LambdaExpr *OuterLambda = + Result.Nodes.getNodeAs<LambdaExpr>("lambdaExpr")) { + const CXXMethodDecl *OpCall = OuterLambda->getCallOperator(); + QualType ReturnTy = OpCall->getReturnType(); + if (const CXXRecordDecl *Record = ReturnTy->getAsCXXRecordDecl()) { + Lambda = Record; + } + } + + if (!Lambda || !Lambda->isLambda()) { + return; + } + + // Don't report errors on the same declarations more than once. + if (CheckedDecls.count(Lambda)) { + return; + } + CheckedDecls.insert(Lambda); + + bool StrongRefToThisCaptured = false; + + for (const LambdaCapture &Capture : Lambda->captures()) { + // Check if any of the captures are ByRef. If they are, we have nothing to + // report, as it's OK to capture raw pointers to refcounted objects so long + // as the Lambda doesn't escape the current scope, which is required by + // ByRef captures already. + if (Capture.getCaptureKind() == LCK_ByRef) { + return; + } + + // Check if this capture is byvalue, and captures a strong reference to + // this. + // XXX: Do we want to make sure that this type which we are capturing is a + // "Smart Pointer" somehow? + if (!StrongRefToThisCaptured && Capture.capturesVariable() && + Capture.getCaptureKind() == LCK_ByCopy) { + const VarDecl *Var = dyn_cast<VarDecl>(Capture.getCapturedVar()); + if (Var->hasInit()) { + const Stmt *Init = Var->getInit(); + + // Ignore single argument constructors, and trivial nodes. + while (true) { + auto NewInit = IgnoreTrivials(Init); + if (auto ConstructExpr = dyn_cast<CXXConstructExpr>(NewInit)) { + if (ConstructExpr->getNumArgs() == 1) { + NewInit = ConstructExpr->getArg(0); + } + } + if (Init == NewInit) { + break; + } + Init = NewInit; + } + + if (isa<CXXThisExpr>(Init)) { + StrongRefToThisCaptured = true; + } + } + } + } + + // Now we can go through and produce errors for any captured variables or this + // pointers. + for (const LambdaCapture &Capture : Lambda->captures()) { + if (Capture.capturesVariable()) { + const VarDecl *Var = dyn_cast<VarDecl>(Capture.getCapturedVar()); + QualType Pointee = Var->getType()->getPointeeType(); + if (!Pointee.isNull() && isClassRefCounted(Pointee) && + !IsKnownLive(Var)) { + emitDiagnostics(Capture.getLocation(), Var->getName(), Pointee); + return; + } + } + + // The situation with captures of `this` is more complex. All captures of + // `this` look the same-ish (they are LCK_This). We want to complain about + // captures of `this` where `this` is a refcounted type, and the capture is + // actually used in the body of the lambda (if the capture isn't used, then + // we don't care, because it's only being captured in order to give access + // to private methods). + // + // In addition, we don't complain about this, even if it is used, if it was + // captured implicitly when the LambdaCaptureDefault was LCD_ByRef, as that + // expresses the intent that the lambda won't leave the enclosing scope. + bool ImplicitByRefDefaultedCapture = + Capture.isImplicit() && Lambda->getLambdaCaptureDefault() == LCD_ByRef; + if (Capture.capturesThis() && !ImplicitByRefDefaultedCapture && + !StrongRefToThisCaptured) { + ThisVisitor V(*this); + bool NotAborted = V.TraverseDecl( + const_cast<CXXMethodDecl *>(Lambda->getLambdaCallOperator())); + if (!NotAborted) { + return; + } + } + } +} + +bool RefCountedInsideLambdaChecker::ThisVisitor::VisitCXXThisExpr( + CXXThisExpr *This) { + QualType Pointee = This->getType()->getPointeeType(); + if (!Pointee.isNull() && isClassRefCounted(Pointee)) { + Checker.emitDiagnostics(This->getBeginLoc(), "this", Pointee); + return false; + } + + return true; +} diff --git a/build/clang-plugin/RefCountedInsideLambdaChecker.h b/build/clang-plugin/RefCountedInsideLambdaChecker.h new file mode 100644 index 0000000000..ed9419681a --- /dev/null +++ b/build/clang-plugin/RefCountedInsideLambdaChecker.h @@ -0,0 +1,33 @@ +/* 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 RefCountedInsideLambdaChecker_h__ +#define RefCountedInsideLambdaChecker_h__ + +#include "plugin.h" + +class RefCountedInsideLambdaChecker : public BaseCheck { +public: + RefCountedInsideLambdaChecker(StringRef CheckName, + ContextType *Context = nullptr) + : BaseCheck(CheckName, Context) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void check(const MatchFinder::MatchResult &Result) override; + + void emitDiagnostics(SourceLocation Loc, StringRef Name, QualType Type); + +private: + class ThisVisitor : public RecursiveASTVisitor<ThisVisitor> { + public: + explicit ThisVisitor(RefCountedInsideLambdaChecker &Checker) + : Checker(Checker) {} + + bool VisitCXXThisExpr(CXXThisExpr *This); + + private: + RefCountedInsideLambdaChecker &Checker; + }; +}; + +#endif diff --git a/build/clang-plugin/RefCountedThisInsideConstructorChecker.cpp b/build/clang-plugin/RefCountedThisInsideConstructorChecker.cpp new file mode 100644 index 0000000000..9607f41f10 --- /dev/null +++ b/build/clang-plugin/RefCountedThisInsideConstructorChecker.cpp @@ -0,0 +1,29 @@ +/* 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 "RefCountedThisInsideConstructorChecker.h" +#include "CustomMatchers.h" + +void RefCountedThisInsideConstructorChecker::registerMatchers( + MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + cxxConstructExpr(hasType(isSmartPtrToRefCounted()), + hasArgument(0, cxxThisExpr()), + hasAncestor(cxxConstructorDecl().bind("constructor"))) + .bind("call"), + this); +} + +void RefCountedThisInsideConstructorChecker::check( + const MatchFinder::MatchResult &Result) { + const CXXConstructExpr *Call = + Result.Nodes.getNodeAs<CXXConstructExpr>("call"); + + diag(Call->getBeginLoc(), + "Refcounting `this` inside the constructor is a footgun, `this` may be " + "destructed at the end of the constructor unless there's another strong " + "reference. Consider adding a separate Create function and do the work " + "there.", + DiagnosticIDs::Error); +} diff --git a/build/clang-plugin/RefCountedThisInsideConstructorChecker.h b/build/clang-plugin/RefCountedThisInsideConstructorChecker.h new file mode 100644 index 0000000000..f8ae68e2a5 --- /dev/null +++ b/build/clang-plugin/RefCountedThisInsideConstructorChecker.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 BUILD_CLANG_PLUGIN_PREVENTADDREFWITHINCONSTRUCTORCHECKER_H +#define BUILD_CLANG_PLUGIN_PREVENTADDREFWITHINCONSTRUCTORCHECKER_H + +#include "plugin.h" + +class RefCountedThisInsideConstructorChecker : public BaseCheck { +public: + RefCountedThisInsideConstructorChecker(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/ScopeChecker.cpp b/build/clang-plugin/ScopeChecker.cpp new file mode 100644 index 0000000000..962c252105 --- /dev/null +++ b/build/clang-plugin/ScopeChecker.cpp @@ -0,0 +1,180 @@ +/* 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 "ScopeChecker.h" +#include "CustomMatchers.h" + +void ScopeChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(varDecl().bind("node"), this); + AstMatcher->addMatcher(cxxNewExpr().bind("node"), this); + AstMatcher->addMatcher( + materializeTemporaryExpr( + unless(hasDescendant(cxxConstructExpr(allowsTemporary())))) + .bind("node"), + this); + AstMatcher->addMatcher( + callExpr(callee(functionDecl(heapAllocator()))).bind("node"), this); +} + +// These enum variants determine whether an allocation has occured in the code. +enum AllocationVariety { + AV_None, + AV_Global, + AV_Automatic, + AV_Temporary, + AV_Heap, +}; + +// XXX Currently the Decl* in the AutomaticTemporaryMap is unused, but it +// probably will be used at some point in the future, in order to produce better +// error messages. +typedef DenseMap<const MaterializeTemporaryExpr *, const Decl *> + AutomaticTemporaryMap; +AutomaticTemporaryMap AutomaticTemporaries; + +void ScopeChecker::check(const MatchFinder::MatchResult &Result) { + // There are a variety of different reasons why something could be allocated + AllocationVariety Variety = AV_None; + SourceLocation Loc; + QualType T; + bool IsStaticLocal = false; + + if (const ParmVarDecl *D = Result.Nodes.getNodeAs<ParmVarDecl>("node")) { + if (D->hasUnparsedDefaultArg() || D->hasUninstantiatedDefaultArg()) { + return; + } + if (const Expr *Default = D->getDefaultArg()) { + if (const MaterializeTemporaryExpr *E = + dyn_cast<MaterializeTemporaryExpr>(Default)) { + // We have just found a ParmVarDecl which has, as its default argument, + // a MaterializeTemporaryExpr. We mark that MaterializeTemporaryExpr as + // automatic, by adding it to the AutomaticTemporaryMap. + // Reporting on this type will occur when the MaterializeTemporaryExpr + // is matched against. + AutomaticTemporaries[E] = D; + } + } + return; + } + + // Determine the type of allocation which we detected + if (const VarDecl *D = Result.Nodes.getNodeAs<VarDecl>("node")) { + if (D->hasGlobalStorage()) { + Variety = AV_Global; + } else { + Variety = AV_Automatic; + } + T = D->getType(); + Loc = D->getBeginLoc(); + IsStaticLocal = D->isStaticLocal(); + } else if (const CXXNewExpr *E = Result.Nodes.getNodeAs<CXXNewExpr>("node")) { + // New allocates things on the heap. + // We don't consider placement new to do anything, as it doesn't actually + // allocate the storage, and thus gives us no useful information. + if (!isPlacementNew(E)) { + Variety = AV_Heap; + T = E->getAllocatedType(); + Loc = E->getBeginLoc(); + } + } else if (const MaterializeTemporaryExpr *E = + Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("node")) { + // Temporaries can actually have varying storage durations, due to temporary + // lifetime extension. We consider the allocation variety of this temporary + // to be the same as the allocation variety of its lifetime. + + // XXX We maybe should mark these lifetimes as being due to a temporary + // which has had its lifetime extended, to improve the error messages. + switch (E->getStorageDuration()) { + case SD_FullExpression: { + // Check if this temporary is allocated as a default argument! + // if it is, we want to pretend that it is automatic. + AutomaticTemporaryMap::iterator AutomaticTemporary = + AutomaticTemporaries.find(E); + if (AutomaticTemporary != AutomaticTemporaries.end()) { + Variety = AV_Automatic; + } else { + Variety = AV_Temporary; + } + } break; + case SD_Automatic: + Variety = AV_Automatic; + break; + case SD_Thread: + case SD_Static: + Variety = AV_Global; + break; + case SD_Dynamic: + assert(false && "I don't think that this ever should occur..."); + Variety = AV_Heap; + break; + } + T = E->getType().getUnqualifiedType(); + Loc = E->getBeginLoc(); + } else if (const CallExpr *E = Result.Nodes.getNodeAs<CallExpr>("node")) { + T = E->getType()->getPointeeType(); + if (!T.isNull()) { + // This will always allocate on the heap, as the heapAllocator() check + // was made in the matcher + Variety = AV_Heap; + Loc = E->getBeginLoc(); + } + } + + // Error messages for incorrect allocations. + const char *Stack = "variable of type %0 only valid on the stack"; + const char *Global = "variable of type %0 only valid as global"; + const char *Heap = "variable of type %0 only valid on the heap"; + const char *NonHeap = "variable of type %0 is not valid on the heap"; + const char *NonTemporary = "variable of type %0 is not valid in a temporary"; + const char *Temporary = "variable of type %0 is only valid as a temporary"; + const char *StaticLocal = "variable of type %0 is only valid as a static " + "local"; + + const char *StackNote = + "value incorrectly allocated in an automatic variable"; + const char *GlobalNote = "value incorrectly allocated in a global variable"; + const char *HeapNote = "value incorrectly allocated on the heap"; + const char *TemporaryNote = "value incorrectly allocated in a temporary"; + + // Report errors depending on the annotations on the input types. + switch (Variety) { + case AV_None: + return; + + case AV_Global: + StackClass.reportErrorIfPresent(*this, T, Loc, Stack, GlobalNote); + HeapClass.reportErrorIfPresent(*this, T, Loc, Heap, GlobalNote); + TemporaryClass.reportErrorIfPresent(*this, T, Loc, Temporary, GlobalNote); + if (!IsStaticLocal) { + StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal, + GlobalNote); + } + break; + + case AV_Automatic: + GlobalClass.reportErrorIfPresent(*this, T, Loc, Global, StackNote); + HeapClass.reportErrorIfPresent(*this, T, Loc, Heap, StackNote); + TemporaryClass.reportErrorIfPresent(*this, T, Loc, Temporary, StackNote); + StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal, + StackNote); + break; + + case AV_Temporary: + GlobalClass.reportErrorIfPresent(*this, T, Loc, Global, TemporaryNote); + HeapClass.reportErrorIfPresent(*this, T, Loc, Heap, TemporaryNote); + NonTemporaryClass.reportErrorIfPresent(*this, T, Loc, NonTemporary, + TemporaryNote); + StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal, + TemporaryNote); + break; + + case AV_Heap: + GlobalClass.reportErrorIfPresent(*this, T, Loc, Global, HeapNote); + StackClass.reportErrorIfPresent(*this, T, Loc, Stack, HeapNote); + NonHeapClass.reportErrorIfPresent(*this, T, Loc, NonHeap, HeapNote); + TemporaryClass.reportErrorIfPresent(*this, T, Loc, Temporary, HeapNote); + StaticLocalClass.reportErrorIfPresent(*this, T, Loc, StaticLocal, HeapNote); + break; + } +} diff --git a/build/clang-plugin/ScopeChecker.h b/build/clang-plugin/ScopeChecker.h new file mode 100644 index 0000000000..edab241f1c --- /dev/null +++ b/build/clang-plugin/ScopeChecker.h @@ -0,0 +1,18 @@ +/* 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 ScopeChecker_h__ +#define ScopeChecker_h__ + +#include "plugin.h" + +class ScopeChecker : public BaseCheck { +public: + ScopeChecker(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/SprintfLiteralChecker.cpp b/build/clang-plugin/SprintfLiteralChecker.cpp new file mode 100644 index 0000000000..94e8e2fd1b --- /dev/null +++ b/build/clang-plugin/SprintfLiteralChecker.cpp @@ -0,0 +1,84 @@ +/* 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 "SprintfLiteralChecker.h" +#include "CustomMatchers.h" + +void SprintfLiteralChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + callExpr( + isSnprintfLikeFunc(), + allOf(hasArgument( + 0, ignoringParenImpCasts(declRefExpr().bind("buffer"))), + anyOf(hasArgument(1, sizeOfExpr(has(ignoringParenImpCasts( + declRefExpr().bind("size"))))), + hasArgument(1, integerLiteral().bind("immediate")), + hasArgument(1, declRefExpr(to(varDecl( + hasType(isConstQualified()), + hasInitializer(integerLiteral().bind( + "constant"))))))))) + .bind("funcCall"), + this); +} + +void SprintfLiteralChecker::check(const MatchFinder::MatchResult &Result) { + if (!Result.Context->getLangOpts().CPlusPlus) { + // SprintfLiteral is not usable in C, so there is no point in issuing these + // warnings. + return; + } + + const char *Error = + "Use %1 instead of %0 when writing into a character array."; + const char *Note = + "This will prevent passing in the wrong size to %0 accidentally."; + + const CallExpr *D = Result.Nodes.getNodeAs<CallExpr>("funcCall"); + + StringRef Name = D->getDirectCallee()->getName(); + const char *Replacement; + if (Name == "snprintf") { + Replacement = "SprintfLiteral"; + } else { + assert(Name == "vsnprintf"); + Replacement = "VsprintfLiteral"; + } + + const DeclRefExpr *Buffer = Result.Nodes.getNodeAs<DeclRefExpr>("buffer"); + const DeclRefExpr *Size = Result.Nodes.getNodeAs<DeclRefExpr>("size"); + if (Size) { + // Match calls like snprintf(x, sizeof(x), ...). + if (Buffer->getFoundDecl() != Size->getFoundDecl()) { + return; + } + + diag(D->getBeginLoc(), Error, DiagnosticIDs::Error) << Name << Replacement; + diag(D->getBeginLoc(), Note, DiagnosticIDs::Note) << Name; + return; + } + + const QualType QType = Buffer->getType(); + const ConstantArrayType *Type = + dyn_cast<ConstantArrayType>(QType.getTypePtrOrNull()); + if (Type) { + // Match calls like snprintf(x, 100, ...), where x is int[100]; + const IntegerLiteral *Literal = + Result.Nodes.getNodeAs<IntegerLiteral>("immediate"); + if (!Literal) { + // Match calls like: const int y = 100; snprintf(x, y, ...); + Literal = Result.Nodes.getNodeAs<IntegerLiteral>("constant"); + } + + // We're going to assume here that the bitwidth of both of these values fits + // within 64 bits. and zero-extend both values to 64-bits before comparing + // them. + uint64_t Size = Type->getSize().getZExtValue(); + uint64_t Lit = Literal->getValue().getZExtValue(); + if (Size <= Lit) { + diag(D->getBeginLoc(), Error, DiagnosticIDs::Error) + << Name << Replacement; + diag(D->getBeginLoc(), Note, DiagnosticIDs::Note) << Name; + } + } +} diff --git a/build/clang-plugin/SprintfLiteralChecker.h b/build/clang-plugin/SprintfLiteralChecker.h new file mode 100644 index 0000000000..bf407987b3 --- /dev/null +++ b/build/clang-plugin/SprintfLiteralChecker.h @@ -0,0 +1,18 @@ +/* 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 SprintfLiteralChecker_h__ +#define SprintfLiteralChecker_h__ + +#include "plugin.h" + +class SprintfLiteralChecker : public BaseCheck { +public: + SprintfLiteralChecker(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/StmtToBlockMap.h b/build/clang-plugin/StmtToBlockMap.h new file mode 100644 index 0000000000..09b0cac415 --- /dev/null +++ b/build/clang-plugin/StmtToBlockMap.h @@ -0,0 +1,90 @@ +/* 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 StmtToBlockMap_h__ +#define StmtToBlockMap_h__ + +#include "Utils.h" + +// This method is copied from clang-tidy's ExprSequence.cpp. +// +// Returns the Stmt nodes that are parents of 'S', skipping any potential +// intermediate non-Stmt nodes. +// +// In almost all cases, this function returns a single parent or no parents at +// all. +inline SmallVector<const Stmt *, 1> getParentStmts(const Stmt *S, + ASTContext *Context) { + SmallVector<const Stmt *, 1> Result; + + auto Parents = Context->getParents(*S); + + SmallVector<clang::DynTypedNode, 1> NodesToProcess(Parents.begin(), + Parents.end()); + + while (!NodesToProcess.empty()) { + clang::DynTypedNode Node = NodesToProcess.back(); + NodesToProcess.pop_back(); + + if (const auto *S = Node.get<Stmt>()) { + Result.push_back(S); + } else { + Parents = Context->getParents(Node); + NodesToProcess.append(Parents.begin(), Parents.end()); + } + } + + return Result; +} + +// This class is a modified version of the class from clang-tidy's +// ExprSequence.cpp +// +// Maps `Stmt`s to the `CFGBlock` that contains them. Some `Stmt`s may be +// contained in more than one `CFGBlock`; in this case, they are mapped to the +// innermost block (i.e. the one that is furthest from the root of the tree). +// An optional outparameter provides the index into the block where the `Stmt` +// was found. +class StmtToBlockMap { +public: + // Initializes the map for the given `CFG`. + StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext) + : Context(TheContext) { + for (const auto *B : *TheCFG) { + for (size_t I = 0; I < B->size(); ++I) { + if (auto S = (*B)[I].getAs<CFGStmt>()) { + Map[S->getStmt()] = std::make_pair(B, I); + } + } + } + } + + // Returns the block that S is contained in. Some `Stmt`s may be contained + // in more than one `CFGBlock`; in this case, this function returns the + // innermost block (i.e. the one that is furthest from the root of the tree). + // + // The optional outparameter `Index` is set to the index into the block where + // the `Stmt` was found. + const CFGBlock *blockContainingStmt(const Stmt *S, + size_t *Index = nullptr) const { + while (!Map.count(S)) { + SmallVector<const Stmt *, 1> Parents = getParentStmts(S, Context); + if (Parents.empty()) + return nullptr; + S = Parents[0]; + } + + const auto &E = Map.lookup(S); + if (Index) + *Index = E.second; + return E.first; + } + +private: + ASTContext *Context; + + llvm::DenseMap<const Stmt *, std::pair<const CFGBlock *, size_t>> Map; +}; + +#endif // StmtToBlockMap_h__ diff --git a/build/clang-plugin/ThirdPartyPaths.h b/build/clang-plugin/ThirdPartyPaths.h new file mode 100644 index 0000000000..6a497923f2 --- /dev/null +++ b/build/clang-plugin/ThirdPartyPaths.h @@ -0,0 +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/. */ + +#ifndef ThirdPartyPaths_h__ +#define ThirdPartyPaths_h__ + +#include <stdint.h> + +// These two values are defined in ThirdPartyPaths.cpp, which is a file +// generated by ThirdPartyPaths.py. + +extern const char *MOZ_THIRD_PARTY_PATHS[]; + +extern const uint32_t MOZ_THIRD_PARTY_PATHS_COUNT; + +#endif diff --git a/build/clang-plugin/ThirdPartyPaths.py b/build/clang-plugin/ThirdPartyPaths.py new file mode 100644 index 0000000000..caaa919d43 --- /dev/null +++ b/build/clang-plugin/ThirdPartyPaths.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import json + + +def generate(output, *input_paths): + """ + This file generates a ThirdPartyPaths.cpp file from the ThirdPartyPaths.txt + file in /tools/rewriting, which is used by the Clang Plugin to help identify + sources which should be ignored. + """ + tpp_list = [] + lines = set() + + for path in input_paths: + with open(path) as f: + lines.update(f.readlines()) + + for line in lines: + line = line.strip() + if line.endswith("/"): + line = line[:-1] + tpp_list.append(line) + tpp_strings = ",\n ".join([json.dumps(tpp) for tpp in sorted(tpp_list)]) + + output.write( + """\ +/* THIS FILE IS GENERATED BY ThirdPartyPaths.py - DO NOT EDIT */ + +#include <stdint.h> + +const char* MOZ_THIRD_PARTY_PATHS[] = { + %s +}; + +extern const uint32_t MOZ_THIRD_PARTY_PATHS_COUNT = %d; + +""" + % (tpp_strings, len(tpp_list)) + ) diff --git a/build/clang-plugin/ThreadAllows.py b/build/clang-plugin/ThreadAllows.py new file mode 100644 index 0000000000..f3e1ee894c --- /dev/null +++ b/build/clang-plugin/ThreadAllows.py @@ -0,0 +1,82 @@ +# 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/. +import json +import os +import posixpath +from os import PathLike + +# `typing.Literal` not available until Python 3.8; +# `typing_extensions` not generally available here +from typing import Iterable, Set + +FIRST_LINE = "// This file was generated by {}. DO NOT EDIT.".format( + # `posixpath` for forward slashes, for presentation purposes + posixpath.relpath(__file__, os.getenv("TOPSRCDIR", "/")) +) + + +def generate_allowed_items( + which: str, # should be: Literal["files", "names"], + paths: Iterable[PathLike], +) -> str: + def remove_trailing_comment(s: str) -> str: + return s[0 : s.find("#")] + + def read_items_from_path(path: PathLike) -> Set[str]: + out = set() + with open(path) as file: + for line in file.readlines(): + line = remove_trailing_comment(line).strip() + if not line: + continue # comment or empty line; discard + out.add(line) + return out + + allowed = set().union(*(read_items_from_path(path) for path in paths)) + # BUG: `json.dumps` may not correctly handle use of the quote character in + # thread names + allowed_list_s = ",\n ".join(json.dumps(elem) for elem in sorted(allowed)) + + return f"""\ +static const char *allow_thread_{which}[] = {{ + {allowed_list_s} +}};""" + + +def generate_allows( + *, allowed_names: Iterable[PathLike], allowed_files: Iterable[PathLike] +) -> str: + """ + This function reads in the specified sets of files -- ordinarily, + ["ThreadAllows.txt"] and ["ThreadFileAllows.txt"] -- and generates the text + of a header file containing two arrays with their contents, for inclusion by + the thread-name checker. + + The checker will reject the creation of any thread via NS_NewNamedThread + unless either: + - the thread's name is a literal string which is found in the set of + allowed thread names; or + - the thread's creation occurs within a file which is found in the set of + unchecked files. + + The latter condition exists mostly for the definition of NS_NewNamedThread, + but there also exist a few cases where the thread name is dynamically + computed (and so can't be checked). + """ + output_string = ( + FIRST_LINE + + "\n\n" + + generate_allowed_items("files", allowed_files) + + "\n\n" + + generate_allowed_items("names", allowed_names) + + "\n" + ) + return output_string + + +# Entry point used by build/clang-plugin/moz.build (q.v.). +def generate_file(output, allowed_names, allowed_files): + output.write( + generate_allows(allowed_names=[allowed_names], allowed_files=[allowed_files]) + ) diff --git a/build/clang-plugin/ThreadAllows.txt b/build/clang-plugin/ThreadAllows.txt new file mode 100644 index 0000000000..801e57504f --- /dev/null +++ b/build/clang-plugin/ThreadAllows.txt @@ -0,0 +1,115 @@ +# This file is ingested by `ThreadAllows.py` to produce a list of thread names +# which our clang plugin will allow to be used with `NS_NewNamedThread`. +# +# Permitted thread names are a maximum of 15 characters in length, and must be +# string literals at their point-of-use in the code -- i.e., in the invocation +# of `NS_NewNamedThread`. +# +# Within this file, each permitted thread name is on a separate line. Comments +# begin with `#`, as seen here. Leading and trailing whitespace, trailing +# comments, and blank lines are ignored, and may be used freely. +# +# Please explain the addition of any new thread names in comments, preferably +# with a pointer to a relevant bug. (Do not add thread names only used in tests +# to this file; instead, add the test file to `ThreadFileAllows.txt`.) + +###### +# Gecko/Firefox thread names +# (See also "Unsorted thread names", below.) + +# Used by `nsUpdateProcessor` to check for updates. May also be used for polling +# the update process. +UpdateProcessor + +###### +# Thunderbird-only thread names +IMAP + +###### +# Other +Checker Test # used only as part of tests for the thread-checker itself +Testing Thread # used only as part of tests for toolkit/components/url-classifier/tests/gtest/ + +###### +# Unsorted thread names +# +# Thread names below this point are grandfathered in. Please do not add new +# thread names to this list -- and please remove any that you can, whether by +# documenting and moving them or by confirming that they are no longer required. +# +# In particular, if a thread name is only used for testing, please consider +# moving its declarator to `ThreadFileAllows.txt`. + +BGReadURLs +BHMgr Monitor +BHMgr Processor +COM Intcpt Log +COM MTA +Cache I/O +Cameras IPC +CanvasRenderer +Compositor +Cookie +CrashRep Inject +DDMediaLogs +DOMCacheThread +DataChannel IO +DataStorage +DesktopCapture +File Dialog +FileWatcher IO +Font Loader +FontEnumThread +Function Broker +GMPThread +Gamepad +GraphRunner +HTML5 Parser +ICS parser +IPC Launch +IPDL Background +IdentityCrypto +ImageBridgeChld +LS Thread +MDCDMThread +MediaCache +MediaTelemetry +MediaTrackGrph +MemoryPoller +mtransport +NamedPipeSrv +Netlink Monitor +OSKeyStore +OutputDrain +PaintThread +Permission +PlayEventSound +ProcessHangMon +ProfSymbolTable +ProfilerChild +ProxyResolution +RemoteLzyStream +RemVidChild +Renderer +ResetCleanup +SaveScripts +Socket Thread +SpeechWorker +StressRunner +SuicideManager +System Proxy +TelemetryModule +Timer +ToastBgThread +TRR Background +URL Classifier +VideoCapture +VRService +VsyncIOThread +Wifi Monitor +Worker Launcher +speechd init +thread +thread shutdown +wifi tickler +WMFCDMThread diff --git a/build/clang-plugin/ThreadFileAllows.txt b/build/clang-plugin/ThreadFileAllows.txt new file mode 100644 index 0000000000..a10cbbea3e --- /dev/null +++ b/build/clang-plugin/ThreadFileAllows.txt @@ -0,0 +1,75 @@ +# This file is ingested by `ThreadAllows.py` to produce a list of files which +# our clang plugin will allow to use `NS_NewNamedThread`. +# +# Files may be specified with any number of slash-separated path-elements; all +# provided path-elements must match. (Because we often move and/or symlink +# header files, this means headers will usually have no path-elements.) +# +# Note that this file contains a list of _files_, not _paths_. The clang plugin +# has no notion of $TOPSRCDIR. + +###### +# Release files + +# declaration and definition of `NS_NewNamedThread` +nsThreadUtils.h +xpcom/threads/nsThreadUtils.cpp + +# Thread-pools are permitted to make dynamically many threads, using dynamic +# thread names with explicit numbering. +xpcom/threads/nsThreadPool.cpp + +###### +# Test files + +# Tests for XPCOM threads themselves. +xpcom/tests/gtest/TestThreadManager.cpp +xpcom/tests/gtest/TestThreads.cpp +xpcom/tests/gtest/TestThreadUtils.cpp + +# Tests which use dynamic thread names. +xpcom/tests/gtest/TestHandleWatcher.cpp + +###### +# Unsorted release files +# +# Files below this point are grandfathered in. Please do not add new files to +# this list -- and please remove any that you can, whether by documenting and +# moving them or by confirming that they are no longer required. +dom/indexedDB/ActorsParent.cpp +dom/quota/ActorsParent.cpp +DecodePool.cpp +GeckoChildProcessHost.cpp +LazyIdleThread.cpp +LazyIdleThread.h +VRThread.cpp +mozStorageConnection.cpp +nr_socket_prsock.cpp + +###### +# Unsorted test files +# +# Files below this point are quasi-grandfathered in: these are test files which +# create new threads whose names were formerly in ThreadAllows.txt (without +# justification), and have been moved here (without justification). +dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp +image/test/fuzzing/TestDecoders.cpp +image/test/gtest/TestDecodeToSurface.cpp +ipc/ipdl/test/gtest/IPDLUnitTest.cpp +security/sandbox/common/test/SandboxTestingThread.h +storage/test/gtest/test_interruptSynchronousConnection.cpp +storage/test/gtest/test_unlock_notify.cpp +toolkit/components/telemetry/geckoview/gtest/TestGeckoViewStreaming.cpp +toolkit/components/telemetry/tests/gtest/TestScalars.cpp +toolkit/components/url-classifier/tests/gtest/Common.cpp +tools/fuzzing/ipc/IPCFuzzController.cpp +tools/profiler/tests/gtest/GeckoProfiler.cpp +xpcom/tests/gtest/TestAtoms.cpp +xpcom/tests/gtest/TestAutoRefCnt.cpp +xpcom/tests/gtest/TestDelayedRunnable.cpp +xpcom/tests/gtest/TestLogging.cpp +xpcom/tests/gtest/TestPipes.cpp +xpcom/tests/gtest/TestRacingServiceManager.cpp +xpcom/tests/gtest/TestRWLock.cpp +xpcom/tests/gtest/TestThrottledEventQueue.cpp +xpcom/tests/gtest/TestTimers.cpp diff --git a/build/clang-plugin/TrivialCtorDtorChecker.cpp b/build/clang-plugin/TrivialCtorDtorChecker.cpp new file mode 100644 index 0000000000..59576a5b64 --- /dev/null +++ b/build/clang-plugin/TrivialCtorDtorChecker.cpp @@ -0,0 +1,29 @@ +/* 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 "TrivialCtorDtorChecker.h" +#include "CustomMatchers.h" + +void TrivialCtorDtorChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(cxxRecordDecl(hasTrivialCtorDtor()).bind("node"), + this); +} + +void TrivialCtorDtorChecker::check(const MatchFinder::MatchResult &Result) { + const char *Error = "class %0 must have trivial constructors and destructors"; + const CXXRecordDecl *Node = Result.Nodes.getNodeAs<CXXRecordDecl>("node"); + + if (!Node->hasDefinition()) { + return; + } + + // We need to accept non-constexpr trivial constructors as well. This occurs + // when a struct contains pod members, which will not be initialized. As + // constexpr values are initialized, the constructor is non-constexpr. + bool BadCtor = !(Node->hasConstexprDefaultConstructor() || + Node->hasTrivialDefaultConstructor()); + bool BadDtor = !Node->hasTrivialDestructor(); + if (BadCtor || BadDtor) + diag(Node->getBeginLoc(), Error, DiagnosticIDs::Error) << Node; +} diff --git a/build/clang-plugin/TrivialCtorDtorChecker.h b/build/clang-plugin/TrivialCtorDtorChecker.h new file mode 100644 index 0000000000..6b44016781 --- /dev/null +++ b/build/clang-plugin/TrivialCtorDtorChecker.h @@ -0,0 +1,18 @@ +/* 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 TrivialCtorDtorChecker_h__ +#define TrivialCtorDtorChecker_h__ + +#include "plugin.h" + +class TrivialCtorDtorChecker : public BaseCheck { +public: + TrivialCtorDtorChecker(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/TrivialDtorChecker.cpp b/build/clang-plugin/TrivialDtorChecker.cpp new file mode 100644 index 0000000000..ffcd2ae101 --- /dev/null +++ b/build/clang-plugin/TrivialDtorChecker.cpp @@ -0,0 +1,23 @@ +/* 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 "TrivialDtorChecker.h" +#include "CustomMatchers.h" + +void TrivialDtorChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher(cxxRecordDecl(hasTrivialDtor()).bind("node"), this); +} + +void TrivialDtorChecker::check(const MatchFinder::MatchResult &Result) { + const char *Error = "class %0 must have a trivial destructor"; + const CXXRecordDecl *Node = Result.Nodes.getNodeAs<CXXRecordDecl>("node"); + + if (!Node->hasDefinition()) { + return; + } + + bool BadDtor = !Node->hasTrivialDestructor(); + if (BadDtor) + diag(Node->getBeginLoc(), Error, DiagnosticIDs::Error) << Node; +} diff --git a/build/clang-plugin/TrivialDtorChecker.h b/build/clang-plugin/TrivialDtorChecker.h new file mode 100644 index 0000000000..dd0be727e6 --- /dev/null +++ b/build/clang-plugin/TrivialDtorChecker.h @@ -0,0 +1,18 @@ +/* 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 TrivialDtorChecker_h__ +#define TrivialDtorChecker_h__ + +#include "plugin.h" + +class TrivialDtorChecker : public BaseCheck { +public: + TrivialDtorChecker(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 new file mode 100644 index 0000000000..c25caf43ee --- /dev/null +++ b/build/clang-plugin/Utils.h @@ -0,0 +1,499 @@ +/* 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 Utils_h__ +#define Utils_h__ + +#include "CustomAttributes.h" +#include "ThirdPartyPaths.h" +#include "ThreadAllows.h" +#include "plugin.h" + +#if CLANG_VERSION_FULL >= 1300 +// Starting with clang-13 some functions from StringRef have been renamed +#define compare_lower compare_insensitive +#endif + +inline StringRef getFilename(const SourceManager &SM, SourceLocation Loc) { + // We use the presumed location to handle #line directives and such, so the + // plugin is friendly to icecc / sccache users. + auto PL = SM.getPresumedLoc(Loc); + if (PL.isValid()) { + return StringRef(PL.getFilename()); + } + return SM.getFilename(Loc); +} + +// Check if the given expression contains an assignment expression. +// This can either take the form of a Binary Operator or a +// Overloaded Operator Call. +inline bool hasSideEffectAssignment(const Expr *Expression) { + if (auto OpCallExpr = dyn_cast_or_null<CXXOperatorCallExpr>(Expression)) { + auto BinOp = OpCallExpr->getOperator(); + if (BinOp == OO_Equal || (BinOp >= OO_PlusEqual && BinOp <= OO_PipeEqual)) { + return true; + } + } else if (auto BinOpExpr = dyn_cast_or_null<BinaryOperator>(Expression)) { + if (BinOpExpr->isAssignmentOp()) { + return true; + } + } + + // Recurse to children. + for (const Stmt *SubStmt : Expression->children()) { + auto ChildExpr = dyn_cast_or_null<Expr>(SubStmt); + if (ChildExpr && hasSideEffectAssignment(ChildExpr)) { + return true; + } + } + + return false; +} + +template <class T> +inline bool ASTIsInSystemHeader(const ASTContext &AC, const T &D) { + auto &SourceManager = AC.getSourceManager(); + auto ExpansionLoc = SourceManager.getExpansionLoc(D.getBeginLoc()); + if (ExpansionLoc.isInvalid()) { + return false; + } + return SourceManager.isInSystemHeader(ExpansionLoc); +} + +template <typename T> inline StringRef getNameChecked(const T &D) { + return D->getIdentifier() ? D->getName() : ""; +} + +/// A cached data of whether classes are refcounted or not. +typedef DenseMap<const CXXRecordDecl *, std::pair<const Decl *, bool>> + RefCountedMap; +extern RefCountedMap RefCountedClasses; + +inline bool classHasAddRefRelease(const CXXRecordDecl *D) { + const RefCountedMap::iterator &It = RefCountedClasses.find(D); + if (It != RefCountedClasses.end()) { + return It->second.second; + } + + bool SeenAddRef = false; + bool SeenRelease = false; + for (CXXRecordDecl::method_iterator Method = D->method_begin(); + Method != D->method_end(); ++Method) { + const auto &Name = getNameChecked(Method); + if (Name == "AddRef") { + SeenAddRef = true; + } else if (Name == "Release") { + SeenRelease = true; + } + } + RefCountedClasses[D] = std::make_pair(D, SeenAddRef && SeenRelease); + return SeenAddRef && SeenRelease; +} + +inline bool isClassRefCounted(QualType T); + +inline bool isClassRefCounted(const CXXRecordDecl *D) { + // Normalize so that D points to the definition if it exists. + if (!D->hasDefinition()) + return false; + D = D->getDefinition(); + // Base class: anyone with AddRef/Release is obviously a refcounted class. + if (classHasAddRefRelease(D)) + return true; + + // Look through all base cases to figure out if the parent is a refcounted + // class. + for (CXXRecordDecl::base_class_const_iterator Base = D->bases_begin(); + Base != D->bases_end(); ++Base) { + bool Super = isClassRefCounted(Base->getType()); + if (Super) { + return true; + } + } + + return false; +} + +inline bool isClassRefCounted(QualType T) { + while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe()) + T = ArrTy->getElementType(); + CXXRecordDecl *Clazz = T->getAsCXXRecordDecl(); + return Clazz ? isClassRefCounted(Clazz) : false; +} + +inline const FieldDecl *getClassRefCntMember(const CXXRecordDecl *D) { + for (RecordDecl::field_iterator Field = D->field_begin(), E = D->field_end(); + Field != E; ++Field) { + if (getNameChecked(Field) == "mRefCnt") { + return *Field; + } + } + return 0; +} + +inline bool typeHasVTable(QualType T) { + while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe()) + T = ArrTy->getElementType(); + CXXRecordDecl *Offender = T->getAsCXXRecordDecl(); + return Offender && Offender->hasDefinition() && Offender->isDynamicClass(); +} + +inline StringRef getDeclarationNamespace(const Decl *Declaration) { + const DeclContext *DC = + Declaration->getDeclContext()->getEnclosingNamespaceContext(); + const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC); + if (!ND) { + return ""; + } + + while (const DeclContext *ParentDC = ND->getParent()) { + if (!isa<NamespaceDecl>(ParentDC)) { + break; + } + ND = cast<NamespaceDecl>(ParentDC); + } + + const auto &Name = ND->getName(); + return Name; +} + +inline bool isInIgnoredNamespaceForImplicitCtor(const Decl *Declaration) { + StringRef Name = getDeclarationNamespace(Declaration); + if (Name == "") { + return false; + } + + return Name == "std" || // standard C++ lib + Name == "__gnu_cxx" || // gnu C++ lib + Name == "boost" || // boost + Name == "webrtc" || // upstream webrtc + Name == "rtc" || // upstream webrtc 'base' package + Name.startswith("icu_") || // icu + Name == "google" || // protobuf + Name == "google_breakpad" || // breakpad + Name == "soundtouch" || // libsoundtouch + Name == "stagefright" || // libstagefright + Name == "MacFileUtilities" || // MacFileUtilities + Name == "dwarf2reader" || // dwarf2reader + Name == "arm_ex_to_module" || // arm_ex_to_module + Name == "testing" || // gtest + Name == "Json" || // jsoncpp + Name == "rlbox" || // rlbox + Name == "v8"; // irregexp +} + +inline bool isInIgnoredNamespaceForImplicitConversion(const Decl *Declaration) { + StringRef Name = getDeclarationNamespace(Declaration); + if (Name == "") { + return false; + } + + return Name == "std" || // standard C++ lib + Name == "__gnu_cxx" || // gnu C++ lib + Name == "google_breakpad" || // breakpad + Name == "webrtc" || // libwebrtc + Name == "testing" || // gtest + Name == "rlbox"; // rlbox +} + +inline bool isIgnoredPathForImplicitConversion(const Decl *Declaration) { + Declaration = Declaration->getCanonicalDecl(); + SourceLocation Loc = Declaration->getLocation(); + const SourceManager &SM = Declaration->getASTContext().getSourceManager(); + SmallString<1024> FileName = getFilename(SM, Loc); + llvm::sys::fs::make_absolute(FileName); + llvm::sys::path::reverse_iterator Begin = llvm::sys::path::rbegin(FileName), + End = llvm::sys::path::rend(FileName); + for (; Begin != End; ++Begin) { + if (Begin->compare_lower(StringRef("graphite2")) == 0) { + return true; + } + if (Begin->compare_lower(StringRef("chromium")) == 0) { + // Ignore security/sandbox/chromium but not ipc/chromium. + ++Begin; + return Begin != End && Begin->compare_lower(StringRef("sandbox")) == 0; + } + } + return false; +} + +inline bool isIgnoredPathForSprintfLiteral(const CallExpr *Call, + const SourceManager &SM) { + SourceLocation Loc = Call->getBeginLoc(); + SmallString<1024> FileName = getFilename(SM, Loc); + llvm::sys::fs::make_absolute(FileName); + llvm::sys::path::reverse_iterator Begin = llvm::sys::path::rbegin(FileName), + End = llvm::sys::path::rend(FileName); + for (; Begin != End; ++Begin) { + if (Begin->compare_lower(StringRef("angle")) == 0 || + Begin->compare_lower(StringRef("chromium")) == 0 || + Begin->compare_lower(StringRef("crashreporter")) == 0 || + Begin->compare_lower(StringRef("google-breakpad")) == 0 || + Begin->compare_lower(StringRef("gflags")) == 0 || + Begin->compare_lower(StringRef("harfbuzz")) == 0 || + Begin->compare_lower(StringRef("icu")) == 0 || + Begin->compare_lower(StringRef("jsoncpp")) == 0 || + Begin->compare_lower(StringRef("libstagefright")) == 0 || + Begin->compare_lower(StringRef("transport")) == 0 || + Begin->compare_lower(StringRef("protobuf")) == 0 || + Begin->compare_lower(StringRef("skia")) == 0 || + Begin->compare_lower(StringRef("sfntly")) == 0 || + // Gtest uses snprintf as GTEST_SNPRINTF_ with sizeof + Begin->compare_lower(StringRef("testing")) == 0) { + return true; + } + if (Begin->compare_lower(StringRef("webrtc")) == 0) { + // Ignore trunk/webrtc, but not media/webrtc + ++Begin; + return Begin != End && Begin->compare_lower(StringRef("trunk")) == 0; + } + } + return false; +} + +inline bool isInterestingDeclForImplicitConversion(const Decl *Declaration) { + return !isInIgnoredNamespaceForImplicitConversion(Declaration) && + !isIgnoredPathForImplicitConversion(Declaration); +} + +inline bool isIgnoredExprForMustUse(const Expr *E) { + if (const CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) { + switch (OpCall->getOperator()) { + case OO_Equal: + case OO_PlusEqual: + case OO_MinusEqual: + case OO_StarEqual: + case OO_SlashEqual: + case OO_PercentEqual: + case OO_CaretEqual: + case OO_AmpEqual: + case OO_PipeEqual: + case OO_LessLessEqual: + case OO_GreaterGreaterEqual: + return true; + default: + return false; + } + } + + if (const BinaryOperator *Op = dyn_cast<BinaryOperator>(E)) { + return Op->isAssignmentOp(); + } + + return false; +} + +inline bool typeIsRefPtr(QualType Q) { + CXXRecordDecl *D = Q->getAsCXXRecordDecl(); + if (!D || !D->getIdentifier()) { + return false; + } + + StringRef name = D->getName(); + if (name == "RefPtr" || name == "nsCOMPtr") { + return true; + } + return false; +} + +// 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 *MaybeSkipOneTrivial(const Stmt *s) { + if (!s) { + return nullptr; + } + if (auto *ewc = dyn_cast<ExprWithCleanups>(s)) { + return ewc->getSubExpr(); + } + if (auto *mte = dyn_cast<MaterializeTemporaryExpr>(s)) { + // With clang 10 and up `getTemporary` has been replaced with the more + // versatile `getSubExpr`. +#if CLANG_VERSION_FULL >= 1000 + return mte->getSubExpr(); +#else + return mte->GetTemporaryExpr(); +#endif + } + if (auto *bte = dyn_cast<CXXBindTemporaryExpr>(s)) { + return bte->getSubExpr(); + } + if (auto *ce = dyn_cast<CastExpr>(s)) { + s = ce->getSubExpr(); + } + if (auto *pe = dyn_cast<ParenExpr>(s)) { + s = pe->getSubExpr(); + } + // Not a trivial. + return s; +} + +inline const Stmt *IgnoreTrivials(const Stmt *s) { + while (true) { + const Stmt *newS = MaybeSkipOneTrivial(s); + if (newS == s) { + return newS; + } + s = newS; + } + + // Unreachable + return nullptr; +} + +inline const Expr *IgnoreTrivials(const Expr *e) { + return cast_or_null<Expr>(IgnoreTrivials(static_cast<const Stmt *>(e))); +} + +// Returns the input if the input is not a trivial. +inline const Expr *MaybeSkipOneTrivial(const Expr *e) { + return cast_or_null<Expr>(MaybeSkipOneTrivial(static_cast<const Stmt *>(e))); +} + +const FieldDecl *getBaseRefCntMember(QualType T); + +inline const FieldDecl *getBaseRefCntMember(const CXXRecordDecl *D) { + const FieldDecl *RefCntMember = getClassRefCntMember(D); + if (RefCntMember && isClassRefCounted(D)) { + return RefCntMember; + } + + for (CXXRecordDecl::base_class_const_iterator Base = D->bases_begin(), + E = D->bases_end(); + Base != E; ++Base) { + RefCntMember = getBaseRefCntMember(Base->getType()); + if (RefCntMember) { + return RefCntMember; + } + } + return 0; +} + +inline const FieldDecl *getBaseRefCntMember(QualType T) { + while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe()) + T = ArrTy->getElementType(); + CXXRecordDecl *Clazz = T->getAsCXXRecordDecl(); + return Clazz ? getBaseRefCntMember(Clazz) : 0; +} + +inline bool isPlacementNew(const CXXNewExpr *Expression) { + // Regular new expressions aren't placement new + if (Expression->getNumPlacementArgs() == 0) + return false; + const FunctionDecl *Declaration = Expression->getOperatorNew(); + if (Declaration && hasCustomAttribute<moz_heap_allocator>(Declaration)) { + return false; + } + return true; +} + +extern DenseMap<StringRef, bool> InThirdPartyPathCache; + +inline bool inThirdPartyPath(SourceLocation Loc, const SourceManager &SM) { + StringRef OriginalFileName = getFilename(SM, Loc); + auto pair = InThirdPartyPathCache.find(OriginalFileName); + if (pair != InThirdPartyPathCache.end()) { + return pair->second; + } + + SmallString<1024> FileName = OriginalFileName; + llvm::sys::fs::make_absolute(FileName); + + for (uint32_t i = 0; i < MOZ_THIRD_PARTY_PATHS_COUNT; ++i) { + auto PathB = sys::path::begin(FileName); + auto PathE = sys::path::end(FileName); + + auto ThirdPartyB = sys::path::begin(MOZ_THIRD_PARTY_PATHS[i]); + auto ThirdPartyE = sys::path::end(MOZ_THIRD_PARTY_PATHS[i]); + + for (; PathB != PathE; ++PathB) { + // Perform an inner loop to compare path segments, checking if the current + // segment is the start of the current third party path. + auto IPathB = PathB; + auto IThirdPartyB = ThirdPartyB; + for (; IPathB != PathE && IThirdPartyB != ThirdPartyE; + ++IPathB, ++IThirdPartyB) { + if (IPathB->compare_lower(*IThirdPartyB) != 0) { + break; + } + } + + // We found a match! + if (IThirdPartyB == ThirdPartyE) { + InThirdPartyPathCache.insert(std::make_pair(OriginalFileName, true)); + return true; + } + } + } + + InThirdPartyPathCache.insert(std::make_pair(OriginalFileName, false)); + return false; +} + +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->getBeginLoc(); + const SourceManager &SM = context->getSourceManager(); + auto ExpansionLoc = SM.getExpansionLoc(Loc); + if (ExpansionLoc.isInvalid()) { + return inThirdPartyPath(Loc, SM); + } + return inThirdPartyPath(ExpansionLoc, SM); +} + +/// Polyfill for CXXOperatorCallExpr::isInfixBinaryOp() +inline bool isInfixBinaryOp(const CXXOperatorCallExpr *OpCall) { +#if CLANG_VERSION_FULL >= 400 + return OpCall->isInfixBinaryOp(); +#else + // Taken from clang source. + if (OpCall->getNumArgs() != 2) + return false; + + switch (OpCall->getOperator()) { + case OO_Call: + case OO_Subscript: + return false; + default: + return true; + } +#endif +} + +#undef compare_lower +#endif diff --git a/build/clang-plugin/VariableUsageHelpers.cpp b/build/clang-plugin/VariableUsageHelpers.cpp new file mode 100644 index 0000000000..abb9eb280f --- /dev/null +++ b/build/clang-plugin/VariableUsageHelpers.cpp @@ -0,0 +1,275 @@ +/* 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 "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) { + auto 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; +} + +// We declare our EscapesFunctionError enum to be an error code enum. +namespace std { +template <> struct is_error_code_enum<EscapesFunctionError> : true_type {}; +} // namespace std + +// We define the EscapesFunctionErrorCategory which contains the error messages +// corresponding to each enum variant. +namespace { +struct EscapesFunctionErrorCategory : std::error_category { + const char *name() const noexcept override; + std::string message(int ev) const override; +}; + +const char *EscapesFunctionErrorCategory::name() const noexcept { + return "escapes function"; +} + +std::string EscapesFunctionErrorCategory::message(int ev) const { + switch (static_cast<EscapesFunctionError>(ev)) { + case EscapesFunctionError::ConstructorDeclNotFound: + return "constructor declaration not found"; + + case EscapesFunctionError::FunctionDeclNotFound: + return "function declaration not found"; + + case EscapesFunctionError::FunctionIsBuiltin: + return "function is builtin"; + + case EscapesFunctionError::FunctionIsVariadic: + return "function is variadic"; + + case EscapesFunctionError::ExprNotInCall: + return "expression is not in call"; + + case EscapesFunctionError::NoParamForArg: + return "no parameter for argument"; + + case EscapesFunctionError::ArgAndParamNotPointers: + return "argument and parameter are not pointers"; + } +} + +const EscapesFunctionErrorCategory TheEscapesFunctionErrorCategory{}; +} // namespace + +std::error_code make_error_code(EscapesFunctionError e) { + return {static_cast<int>(e), TheEscapesFunctionErrorCategory}; +} + +ErrorOr<std::tuple<const Stmt *, const Decl *>> +escapesFunction(const Expr *Arg, const CXXConstructExpr *Construct) { + // We get the function declaration corresponding to the call. + auto CtorDecl = Construct->getConstructor(); + if (!CtorDecl) { + return EscapesFunctionError::ConstructorDeclNotFound; + } + + return escapesFunction(Arg, CtorDecl, Construct->getArgs(), + Construct->getNumArgs()); +} + +ErrorOr<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 EscapesFunctionError::FunctionDeclNotFound; + } + + return escapesFunction(Arg, FuncDecl, Call->getArgs(), Call->getNumArgs()); +} + +ErrorOr<std::tuple<const Stmt *, const Decl *>> +escapesFunction(const Expr *Arg, const CXXOperatorCallExpr *OpCall) { + // We get the function declaration corresponding to the operator call. + auto FuncDecl = OpCall->getDirectCallee(); + if (!FuncDecl) { + return EscapesFunctionError::FunctionDeclNotFound; + } + + auto Args = OpCall->getArgs(); + auto NumArgs = OpCall->getNumArgs(); + // If this is an infix binary operator defined as a one-param method, we + // remove the first argument as it is inserted explicitly and creates a + // mismatch with the parameters of the method declaration. + if (isInfixBinaryOp(OpCall) && FuncDecl->getNumParams() == 1) { + Args++; + NumArgs--; + } + + return escapesFunction(Arg, FuncDecl, Args, NumArgs); +} + +ErrorOr<std::tuple<const Stmt *, const Decl *>> +escapesFunction(const Expr *Arg, const FunctionDecl *FuncDecl, + const Expr *const *Arguments, unsigned NumArgs) { + if (!NumArgs) { + return std::make_tuple((const Stmt *)nullptr, (const Decl *)nullptr); + } + + if (FuncDecl->getBuiltinID() != 0 || + ASTIsInSystemHeader(FuncDecl->getASTContext(), *FuncDecl)) { + return EscapesFunctionError::FunctionIsBuiltin; + } + + // FIXME: should probably be handled at some point, but it's too annoying + // for now. + if (FuncDecl->isVariadic()) { + return EscapesFunctionError::FunctionIsVariadic; + } + + // We find the argument number corresponding to the Arg expression. + unsigned ArgNum = 0; + for (unsigned i = 0; i < NumArgs; i++) { + if (IgnoreTrivials(Arg) == IgnoreTrivials(Arguments[i])) { + break; + } + ++ArgNum; + } + // If we don't find it, we early-return NoneType. + if (ArgNum >= NumArgs) { + return EscapesFunctionError::ExprNotInCall; + } + + // Now we get the associated parameter. + if (ArgNum >= FuncDecl->getNumParams()) { + return EscapesFunctionError::NoParamForArg; + } + 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().getNonReferenceType()->isPointerType() && + Arg->getType().getNonReferenceType()->isBuiltinType()) || + (!Param->getType().getNonReferenceType()->isPointerType() && + Param->getType().getNonReferenceType()->isBuiltinType())) { + return EscapesFunctionError::ArgAndParamNotPointers; + } + + // 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 std::make_tuple(Usage, (const Decl *)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 std::make_tuple(Usage, (const Decl *)VarDeclaration); + } else if (auto FieldDeclaration = + dyn_cast<FieldDecl>(DeclRef->getDecl())) { + // This is the case where the parameter escapes through a field. + + return std::make_tuple(Usage, (const Decl *)FieldDeclaration); + } + } else if (isa<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 std::make_tuple(Usage, (const Decl *)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 std::make_tuple((const Stmt *)nullptr, (const Decl *)nullptr); +} diff --git a/build/clang-plugin/VariableUsageHelpers.h b/build/clang-plugin/VariableUsageHelpers.h new file mode 100644 index 0000000000..d498857eea --- /dev/null +++ b/build/clang-plugin/VariableUsageHelpers.h @@ -0,0 +1,63 @@ +/* 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 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); + +/// This is the error enumeration for escapesFunction(), describing all the +/// possible error cases. +enum class EscapesFunctionError { + ConstructorDeclNotFound = 1, + FunctionDeclNotFound, + FunctionIsBuiltin, + FunctionIsVariadic, + ExprNotInCall, + NoParamForArg, + ArgAndParamNotPointers +}; + +/// Required by the std::error_code system to convert our enum into a general +/// error code. +std::error_code make_error_code(EscapesFunctionError); + +/// Returns a (statement, decl) tuple if an argument from an argument list +/// 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, it will return an error_code of type EscapesFunctionError +/// representing the precise issue. +/// +/// 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. +ErrorOr<std::tuple<const Stmt *, const Decl *>> +escapesFunction(const Expr *Arg, const FunctionDecl *FuncDecl, + const Expr *const *Arguments, unsigned NumArgs); + +/// Helper function taking a call expression. +ErrorOr<std::tuple<const Stmt *, const Decl *>> +escapesFunction(const Expr *Arg, const CallExpr *Call); + +/// Helper function taking a construct expression. +ErrorOr<std::tuple<const Stmt *, const Decl *>> +escapesFunction(const Expr *Arg, const CXXConstructExpr *Construct); + +/// Helper function taking an operator call expression. +ErrorOr<std::tuple<const Stmt *, const Decl *>> +escapesFunction(const Expr *Arg, const CXXOperatorCallExpr *OpCall); + +#endif diff --git a/build/clang-plugin/alpha/AlphaChecks.inc b/build/clang-plugin/alpha/AlphaChecks.inc new file mode 100644 index 0000000000..4e0d050edd --- /dev/null +++ b/build/clang-plugin/alpha/AlphaChecks.inc @@ -0,0 +1,10 @@ +/* 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 and are considered +// to be in alpha stage development. + +// CHECK(AlphaChecker, "alpha-checker") +CHECK(NonStdMoveChecker, "non-std-move") +CHECK(TempRefPtrChecker, "performance-temp-refptr") diff --git a/build/clang-plugin/alpha/AlphaIncludes.inc b/build/clang-plugin/alpha/AlphaIncludes.inc new file mode 100644 index 0000000000..376c35b878 --- /dev/null +++ b/build/clang-plugin/alpha/AlphaIncludes.inc @@ -0,0 +1,2 @@ +#include "NonStdMoveChecker.h" +#include "TempRefPtrChecker.h" diff --git a/build/clang-plugin/alpha/NonStdMoveChecker.cpp b/build/clang-plugin/alpha/NonStdMoveChecker.cpp new file mode 100644 index 0000000000..e9ede00cea --- /dev/null +++ b/build/clang-plugin/alpha/NonStdMoveChecker.cpp @@ -0,0 +1,115 @@ +/* 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 "NonStdMoveChecker.h" +#include "CustomMatchers.h" +#include "clang/Lex/Lexer.h" + +constexpr const char *kConstructExpr = "construct"; +constexpr const char *kOperatorCallExpr = "operator-call"; +constexpr const char *kSourceExpr = "source-expr"; +constexpr const char *kMaterializeExpr = "materialize-expr"; + +void NonStdMoveChecker::registerMatchers(MatchFinder *AstMatcher) { + + // Assignment through forget + AstMatcher->addMatcher( + cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + hasAnyArgument(materializeTemporaryExpr( + has(cxxBindTemporaryExpr(has(cxxMemberCallExpr( + has(memberExpr(member(hasName("forget")))), + on(expr().bind(kSourceExpr))))))) + .bind(kMaterializeExpr))) + .bind(kOperatorCallExpr), + this); + + // Construction through forget + + AstMatcher->addMatcher( + cxxConstructExpr(has(materializeTemporaryExpr( + has(cxxBindTemporaryExpr(has(cxxMemberCallExpr( + has(memberExpr(member(hasName("forget")))), + on(expr().bind(kSourceExpr))))))) + .bind(kMaterializeExpr))) + .bind(kConstructExpr), + this); +} + +#if CLANG_VERSION_FULL >= 1600 +std::optional<FixItHint> +#else +Optional<FixItHint> +#endif +NonStdMoveChecker::makeFixItHint(const MatchFinder::MatchResult &Result, + const Expr *const TargetExpr) { + const auto *MaterializeExpr = Result.Nodes.getNodeAs<Expr>(kMaterializeExpr); + + // TODO: In principle, we should check here if TargetExpr if + // assignable/constructible from std::move(SourceExpr). Not sure how to do + // this. Currently, we only filter out the case where the targetTypeTemplate + // is already_AddRefed, where this is known to fail. + + const auto *targetTypeTemplate = getNonTemplateSpecializedCXXRecordDecl( + TargetExpr->getType().getCanonicalType()); + const auto *sourceTypeTemplate = getNonTemplateSpecializedCXXRecordDecl( + MaterializeExpr->getType().getCanonicalType()); + + if (targetTypeTemplate && sourceTypeTemplate) { + // TODO is there a better way to check this than by name? otherwise, the + // names probably are necessarily unique in the scope + if (targetTypeTemplate->getName() == sourceTypeTemplate->getName() && + targetTypeTemplate->getName() == "already_AddRefed") { + return {}; + } + } + + const auto *SourceExpr = Result.Nodes.getNodeAs<Expr>(kSourceExpr); + + const auto sourceText = Lexer::getSourceText( + CharSourceRange::getTokenRange(SourceExpr->getSourceRange()), + Result.Context->getSourceManager(), Result.Context->getLangOpts()); + + return FixItHint::CreateReplacement(MaterializeExpr->getSourceRange(), + ("std::move(" + sourceText + ")").str()); +} + +void NonStdMoveChecker::check(const MatchFinder::MatchResult &Result) { + // TODO: Include source and target type name in messages. + + const auto *OCE = + Result.Nodes.getNodeAs<CXXOperatorCallExpr>(kOperatorCallExpr); + + if (OCE) { + const auto *refPtrDecl = + dyn_cast<const CXXRecordDecl>(OCE->getCalleeDecl()->getDeclContext()); + + const auto XFixItHint = makeFixItHint(Result, OCE); + // TODO: produce diagnostic but no FixItHint in this case? + if (XFixItHint) { + diag(OCE->getBeginLoc(), "non-standard move assignment to %0 obscures " + "move, use std::move instead") + << refPtrDecl << *XFixItHint; + } + } + + const auto *CoE = Result.Nodes.getNodeAs<CXXConstructExpr>(kConstructExpr); + + if (CoE) { + const auto *refPtrDecl = + dyn_cast<const CXXRecordDecl>(CoE->getConstructor()->getDeclContext()); + + const auto XFixItHint = makeFixItHint(Result, CoE); + // TODO: produce diagnostic but no FixItHint in this case? + if (XFixItHint) { + diag(CoE->getBeginLoc(), "non-standard move construction of %0 obscures " + "move, use std::move instead") + << refPtrDecl << *XFixItHint; + } + } + + // TODO: What about swap calls immediately after default-construction? These + // can also be replaced by move-construction, but this may require + // control-flow analysis. +} diff --git a/build/clang-plugin/alpha/NonStdMoveChecker.h b/build/clang-plugin/alpha/NonStdMoveChecker.h new file mode 100644 index 0000000000..bb0565cdf0 --- /dev/null +++ b/build/clang-plugin/alpha/NonStdMoveChecker.h @@ -0,0 +1,29 @@ +/* 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 NonStdMoveChecker_h__ +#define NonStdMoveChecker_h__ + +#include "plugin.h" + +class NonStdMoveChecker final : public BaseCheck { +public: + NonStdMoveChecker(StringRef CheckName, ContextType *Context = nullptr) + : BaseCheck(CheckName, Context) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void check(const MatchFinder::MatchResult &Result) override; + +private: + CompilerInstance *CI; + + static +#if CLANG_VERSION_FULL >= 1600 + std::optional<FixItHint> +#else + Optional<FixItHint> +#endif + makeFixItHint(const MatchFinder::MatchResult &Result, const Expr *TargetExpr); +}; + +#endif diff --git a/build/clang-plugin/alpha/TempRefPtrChecker.cpp b/build/clang-plugin/alpha/TempRefPtrChecker.cpp new file mode 100644 index 0000000000..0a4d078368 --- /dev/null +++ b/build/clang-plugin/alpha/TempRefPtrChecker.cpp @@ -0,0 +1,57 @@ +/* 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 "TempRefPtrChecker.h" +#include "CustomMatchers.h" + +constexpr const char *kCallExpr = "call-expr"; +constexpr const char *kOperatorCallExpr = "operator-call"; + +void TempRefPtrChecker::registerMatchers(MatchFinder *AstMatcher) { + AstMatcher->addMatcher( + cxxOperatorCallExpr( + hasOverloadedOperatorName("->"), + hasAnyArgument(implicitCastExpr( + hasSourceExpression(materializeTemporaryExpr(anyOf( + hasDescendant(callExpr().bind(kCallExpr)), anything()))))), + callee(hasDeclContext(classTemplateSpecializationDecl( + isSmartPtrToRefCountedDecl(), + // ignore any calls on temporary RefPtr<MozPromise<T>>, + // since these typically need to be locally ref-counted, + // e.g. in Then chains where the promise might be resolved + // concurrently + unless(hasTemplateArgument( + 0, refersToType(hasDeclaration( + cxxRecordDecl(hasName("mozilla::MozPromise")))))))))) + .bind(kOperatorCallExpr), + this); +} + +void TempRefPtrChecker::check(const MatchFinder::MatchResult &Result) { + const auto *OCE = + Result.Nodes.getNodeAs<CXXOperatorCallExpr>(kOperatorCallExpr); + + const auto *refPtrDecl = + dyn_cast<const CXXRecordDecl>(OCE->getCalleeDecl()->getDeclContext()); + + diag(OCE->getOperatorLoc(), + "performance issue: temporary %0 is only dereferenced here once which " + "involves short-lived AddRef/Release calls") + << refPtrDecl; + + const auto *InnerCE = Result.Nodes.getNodeAs<CallExpr>(kCallExpr); + if (InnerCE) { + const auto functionName = + InnerCE->getCalleeDecl()->getAsFunction()->getQualifiedNameAsString(); + + if (functionName != "mozilla::MakeRefPtr") { + diag( + OCE->getOperatorLoc(), + "consider changing function %0 to return a raw reference instead (be " + "sure that the pointee is held alive by someone else though!)", + DiagnosticIDs::Note) + << functionName; + } + } +} diff --git a/build/clang-plugin/alpha/TempRefPtrChecker.h b/build/clang-plugin/alpha/TempRefPtrChecker.h new file mode 100644 index 0000000000..ebed50c3a0 --- /dev/null +++ b/build/clang-plugin/alpha/TempRefPtrChecker.h @@ -0,0 +1,21 @@ +/* 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 TempRefPtrChecker_h__ +#define TempRefPtrChecker_h__ + +#include "plugin.h" + +class TempRefPtrChecker final : public BaseCheck { +public: + TempRefPtrChecker(StringRef CheckName, ContextType *Context = nullptr) + : BaseCheck(CheckName, Context) {} + void registerMatchers(MatchFinder *AstMatcher) override; + void check(const MatchFinder::MatchResult &Result) override; + +private: + CompilerInstance *CI; +}; + +#endif diff --git a/build/clang-plugin/alpha/sources.mozbuild b/build/clang-plugin/alpha/sources.mozbuild new file mode 100644 index 0000000000..03ed90d3c1 --- /dev/null +++ b/build/clang-plugin/alpha/sources.mozbuild @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +HOST_SOURCES += [ + "NonStdMoveChecker.cpp", + "TempRefPtrChecker.cpp", +] diff --git a/build/clang-plugin/alpha/tests/TestNonStdMove.cpp b/build/clang-plugin/alpha/tests/TestNonStdMove.cpp new file mode 100644 index 0000000000..379f9655dd --- /dev/null +++ b/build/clang-plugin/alpha/tests/TestNonStdMove.cpp @@ -0,0 +1,125 @@ +#include <mozilla/RefPtr.h> + +// we can't include nsCOMPtr.h here, so let's redefine a basic version +template<typename T> +struct nsCOMPtr { + nsCOMPtr() = default; + + template<typename U> + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<U>&& aSrc); + + template<typename U> + nsCOMPtr& operator=(already_AddRefed<U>&& aSrc); +}; + + +using namespace mozilla; + +struct RefCountedBase { + void AddRef(); + void Release(); + + void method_test(); +}; + +struct RefCountedDerived : RefCountedBase {}; + +struct RefCountedBaseHolder { + RefPtr<RefCountedBase> GetRefCountedBase() const { + return mRefCountedBase; + } + +private: + RefPtr<RefCountedBase> mRefCountedBase = MakeRefPtr<RefCountedBase>(); +}; + + +void test_assign_same_type() { + RefPtr<RefCountedBase> a = MakeRefPtr<RefCountedBase>(); + RefPtr<RefCountedBase> b; + + b = a.forget(); // expected-warning {{non-standard move assignment}} +} + +void test_assign_implicit_cast() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + RefPtr<RefCountedBase> b; + + b = a.forget(); // expected-warning {{non-standard move assignment}} +} + +void test_assign_different_template() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + nsCOMPtr<RefCountedBase> b; + + b = a.forget(); // expected-warning {{non-standard move assignment}} +} + +void test_construct_different_template() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + nsCOMPtr<RefCountedBase> b = a.forget(); // expected-warning {{non-standard move construction}} +} + +void test_assign_already_addrefed() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + already_AddRefed<RefCountedDerived> b; + + b = a.forget(); +} + +void test_construct_already_addrefed() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + already_AddRefed<RefCountedDerived> b = a.forget(); +} + +void test_construct_same_type() { + RefPtr<RefCountedBase> a = MakeRefPtr<RefCountedBase>(); + RefPtr<RefCountedBase> b = a.forget(); // expected-warning {{non-standard move construction}} +} + +void test_construct_implicit_cast() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + RefPtr<RefCountedBase> b = a.forget(); // expected-warning {{non-standard move construction}} +} + +void test_construct_brace_same_type() { + RefPtr<RefCountedBase> a = MakeRefPtr<RefCountedBase>(); + auto b = RefPtr<RefCountedBase>{a.forget()}; // expected-warning {{non-standard move construction}} +} + +void test_construct_brace_implicit_cast() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + auto b = RefPtr<RefCountedBase>{a.forget()}; // expected-warning {{non-standard move construction}} +} + +void test_construct_function_style_same_type() { + RefPtr<RefCountedBase> a = MakeRefPtr<RefCountedBase>(); + auto b = RefPtr<RefCountedBase>(a.forget()); // expected-warning {{non-standard move construction}} +} + +void test_construct_function_style_implicit_cast() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + auto b = RefPtr<RefCountedBase>(a.forget()); // expected-warning {{non-standard move construction}} +} + +void test_construct_result_type() { + RefPtr<RefCountedBase> a = MakeRefPtr<RefCountedBase>(); + already_AddRefed<RefCountedBase> b = a.forget(); +} + +void test_construct_implicitly_cast_result_type() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + already_AddRefed<RefCountedBase> b = a.forget(); +} + +void foo(already_AddRefed<RefCountedBase>&& aArg); + +void test_call_with_result_type() { + RefPtr<RefCountedBase> a = MakeRefPtr<RefCountedBase>(); + foo(a.forget()); +} + +void test_call_with_implicitly_cast_result_type() { + RefPtr<RefCountedDerived> a = MakeRefPtr<RefCountedDerived>(); + foo(a.forget()); +} diff --git a/build/clang-plugin/alpha/tests/TestTempRefPtr.cpp b/build/clang-plugin/alpha/tests/TestTempRefPtr.cpp new file mode 100644 index 0000000000..51f756b8e6 --- /dev/null +++ b/build/clang-plugin/alpha/tests/TestTempRefPtr.cpp @@ -0,0 +1,52 @@ +#include <mozilla/RefPtr.h> + +using namespace mozilla; + +struct RefCountedBase { + void AddRef(); + void Release(); + + void method_test(); +}; + +struct RefCountedBaseHolder { + RefPtr<RefCountedBase> GetRefCountedBase() const { + return mRefCountedBase; + } + +private: + RefPtr<RefCountedBase> mRefCountedBase = MakeRefPtr<RefCountedBase>(); +}; + + +void test_arrow_temporary_new_refptr_function_style_cast() { + RefPtr<RefCountedBase>(new RefCountedBase())->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}} +} + +void test_arrow_temporary_new_refptr_brace() { + RefPtr<RefCountedBase>{new RefCountedBase()}->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}} +} + +void test_arrow_temporary_new_c_style_cast() { + ((RefPtr<RefCountedBase>)(new RefCountedBase()))->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}} +} + +void test_arrow_temporary_new_static_cast() { + static_cast<RefPtr<RefCountedBase>>(new RefCountedBase())->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}} +} + +void test_arrow_temporary_new_refptr_makerefptr() { + MakeRefPtr<RefCountedBase>()->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}} +} + +void test_arrow_temporary_get_refptr_from_member_function() { + const RefCountedBaseHolder holder; + holder.GetRefCountedBase()->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}} expected-note {{consider changing function RefCountedBaseHolder::GetRefCountedBase to return a raw reference instead}} +} + +void test_ref(RefCountedBase &aRefCountedBase); + +void test_star_temporary_new_refptr_function_style_cast() { + // TODO: Should we warn about operator* as well? + test_ref(*RefPtr<RefCountedBase>(new RefCountedBase())); +} diff --git a/build/clang-plugin/alpha/tests/sources.mozbuild b/build/clang-plugin/alpha/tests/sources.mozbuild new file mode 100644 index 0000000000..6268746232 --- /dev/null +++ b/build/clang-plugin/alpha/tests/sources.mozbuild @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + "TestNonStdMove.cpp", + "TestTempRefPtr.cpp", +] diff --git a/build/clang-plugin/external/CustomAttributes.inc b/build/clang-plugin/external/CustomAttributes.inc new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/build/clang-plugin/external/CustomAttributes.inc diff --git a/build/clang-plugin/external/ExternalChecks.inc b/build/clang-plugin/external/ExternalChecks.inc new file mode 100644 index 0000000000..d5f0b0334c --- /dev/null +++ b/build/clang-plugin/external/ExternalChecks.inc @@ -0,0 +1,8 @@ +/* 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/. */ + +// Placeholder file to be overwritten with external checks during build +// The list of checker classes that are compatible with clang-tidy-external. + +// CHECK(ExternalChecker, "external-checker") diff --git a/build/clang-plugin/external/ExternalIncludes.inc b/build/clang-plugin/external/ExternalIncludes.inc new file mode 100644 index 0000000000..9fda16de8a --- /dev/null +++ b/build/clang-plugin/external/ExternalIncludes.inc @@ -0,0 +1,9 @@ +/* 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/. */ + +// Placeholder file to be overwritten with external checks during build +// The list of #include directives necessary for the checker classes that +// are compatible with clang-tidy-external. + +// #include "ExternalChecker.h" diff --git a/build/clang-plugin/external/sources.mozbuild b/build/clang-plugin/external/sources.mozbuild new file mode 100644 index 0000000000..01daf87080 --- /dev/null +++ b/build/clang-plugin/external/sources.mozbuild @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# Placeholder file to be overwritten with external checks during build +HOST_SOURCES += [ + # 'ExternalChecker.cpp', +] diff --git a/build/clang-plugin/external/tests/sources.mozbuild b/build/clang-plugin/external/tests/sources.mozbuild new file mode 100644 index 0000000000..1f3b4a61c9 --- /dev/null +++ b/build/clang-plugin/external/tests/sources.mozbuild @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# Placeholder file to be overwritten with external checks during build +SOURCES += [ + # 'ExternalTest.cpp', +] diff --git a/build/clang-plugin/import_mozilla_checks.py b/build/clang-plugin/import_mozilla_checks.py new file mode 100755 index 0000000000..d573dafcf1 --- /dev/null +++ b/build/clang-plugin/import_mozilla_checks.py @@ -0,0 +1,177 @@ +#!/usr/bin/python3 +# 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/. + +import errno +import glob +import os +import shutil + +import ThirdPartyPaths +import ThreadAllows + + +def copy_dir_contents(src, dest): + for f in glob.glob("%s/*" % src): + try: + destname = "%s/%s" % (dest, os.path.basename(f)) + if os.path.isdir(f): + shutil.copytree(f, destname) + else: + shutil.copy2(f, destname) + except OSError as e: + if e.errno == errno.ENOTDIR: + shutil.copy2(f, destname) + elif e.errno == errno.EEXIST: + if os.path.isdir(f): + copy_dir_contents(f, destname) + else: + os.remove(destname) + shutil.copy2(f, destname) + else: + raise Exception("Directory not copied. Error: %s" % e) + + +def write_cmake(module_path, import_options): + names = [" " + os.path.basename(f) for f in glob.glob("%s/*.cpp" % module_path)] + + if import_options["external"]: + names += [ + " " + os.path.join("external", os.path.basename(f)) + for f in glob.glob("%s/external/*.cpp" % (module_path)) + ] + + if import_options["alpha"]: + names += [ + " " + os.path.join("alpha", os.path.basename(f)) + for f in glob.glob("%s/alpha/*.cpp" % (module_path)) + ] + + with open(os.path.join(module_path, "CMakeLists.txt"), "w") as f: + f.write( + """set(LLVM_LINK_COMPONENTS support) + +add_definitions( -DCLANG_TIDY ) + +add_clang_library(clangTidyMozillaModule + ThirdPartyPaths.cpp +%(names)s + + LINK_LIBS + clangTidy + clangTidyReadabilityModule + clangTidyUtils + clangTidyMPIModule + ) + +clang_target_link_libraries(clangTidyMozillaModule + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangLex + )""" + % {"names": "\n".join(names)} + ) + + +def add_moz_module(cmake_path): + with open(cmake_path, "r") as f: + lines = f.readlines() + f.close() + + try: + idx = lines.index("set(ALL_CLANG_TIDY_CHECKS\n") + lines.insert(idx + 1, " clangTidyMozillaModule\n") + + with open(cmake_path, "w") as f: + for line in lines: + f.write(line) + except ValueError: + raise Exception("Unable to find ALL_CLANG_TIDY_CHECKS in {}".format(cmake_path)) + + +def write_third_party_paths(mozilla_path, module_path): + tpp_txt = os.path.join(mozilla_path, "../../tools/rewriting/ThirdPartyPaths.txt") + generated_txt = os.path.join(mozilla_path, "../../tools/rewriting/Generated.txt") + with open(os.path.join(module_path, "ThirdPartyPaths.cpp"), "w") as f: + ThirdPartyPaths.generate(f, tpp_txt, generated_txt) + + +def generate_thread_allows(mozilla_path, module_path): + names = os.path.join(mozilla_path, "../../build/clang-plugin/ThreadAllows.txt") + files = os.path.join(mozilla_path, "../../build/clang-plugin/ThreadFileAllows.txt") + with open(os.path.join(module_path, "ThreadAllows.h"), "w") as f: + f.write( + ThreadAllows.generate_allows(allowed_names=[names], allowed_files=[files]) + ) + + +def do_import(mozilla_path, clang_tidy_path, import_options): + module = "mozilla" + module_path = os.path.join(clang_tidy_path, module) + try: + os.makedirs(module_path) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + copy_dir_contents(mozilla_path, module_path) + write_third_party_paths(mozilla_path, module_path) + generate_thread_allows(mozilla_path, module_path) + write_cmake(module_path, import_options) + add_moz_module(os.path.join(module_path, "..", "CMakeLists.txt")) + with open(os.path.join(module_path, "..", "CMakeLists.txt"), "a") as f: + f.write("add_subdirectory(%s)\n" % module) + # A better place for this would be in `ClangTidyForceLinker.h` but `ClangTidyMain.cpp` + # is also OK. + with open(os.path.join(module_path, "..", "tool", "ClangTidyMain.cpp"), "a") as f: + f.write( + """ +// This anchor is used to force the linker to link the MozillaModule. +extern volatile int MozillaModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MozillaModuleAnchorDestination = + MozillaModuleAnchorSource; +""" + ) + + +def main(): + import argparse + + parser = argparse.ArgumentParser( + usage="import_mozilla_checks.py <mozilla-clang-plugin-path> <clang-tidy-path> [option]", + description="Imports the Mozilla static analysis checks into a clang-tidy source tree.", + ) + parser.add_argument( + "mozilla_path", help="Full path to mozilla-central/build/clang-plugin" + ) + parser.add_argument( + "clang_tidy_path", help="Full path to llvm-project/clang-tools-extra/clang-tidy" + ) + parser.add_argument( + "--import-alpha", + help="Enable import of in-tree alpha checks", + action="store_true", + ) + parser.add_argument( + "--import-external", + help="Enable import of in-tree external checks", + action="store_true", + ) + args = parser.parse_args() + + if not os.path.isdir(args.mozilla_path): + print("Invalid path to mozilla clang plugin") + + if not os.path.isdir(args.clang_tidy_path): + print("Invalid path to clang-tidy source directory") + + import_options = {"alpha": args.import_alpha, "external": args.import_external} + + do_import(args.mozilla_path, args.clang_tidy_path, import_options) + + +if __name__ == "__main__": + main() diff --git a/build/clang-plugin/moz.build b/build/clang-plugin/moz.build new file mode 100644 index 0000000000..c9c2c520db --- /dev/null +++ b/build/clang-plugin/moz.build @@ -0,0 +1,129 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +HostSharedLibrary("clang-plugin") + +HOST_SOURCES += ["!ThirdPartyPaths.cpp"] + +HOST_SOURCES += [ + "ArithmeticArgChecker.cpp", + "AssertAssignmentChecker.cpp", + "CanRunScriptChecker.cpp", + "CustomAttributes.cpp", + "CustomTypeAnnotation.cpp", + "DanglingOnTemporaryChecker.cpp", + "DiagnosticsMatcher.cpp", + "ExplicitImplicitChecker.cpp", + "ExplicitOperatorBoolChecker.cpp", + "JSHandleRootedTypedefChecker.cpp", + "KnownLiveChecker.cpp", + "KungFuDeathGripChecker.cpp", + "MozCheckAction.cpp", + "MustOverrideChecker.cpp", + "MustReturnFromCallerChecker.cpp", + "NaNExprChecker.cpp", + "NeedsNoVTableTypeChecker.cpp", + "NoAddRefReleaseOnReturnChecker.cpp", + "NoAutoTypeChecker.cpp", + "NoDuplicateRefCntMemberChecker.cpp", + "NoExplicitMoveConstructorChecker.cpp", + "NoNewThreadsChecker.cpp", + "NonMemMovableMemberChecker.cpp", + "NonMemMovableTemplateArgChecker.cpp", + "NonParamInsideFunctionDeclChecker.cpp", + "NonTrivialTypeInFfiChecker.cpp", + "NoPrincipalGetURI.cpp", + "NoUsingNamespaceMozillaJavaChecker.cpp", + "OverrideBaseCallChecker.cpp", + "OverrideBaseCallUsageChecker.cpp", + "ParamTraitsEnumChecker.cpp", + "RefCountedCopyConstructorChecker.cpp", + "RefCountedInsideLambdaChecker.cpp", + "RefCountedThisInsideConstructorChecker.cpp", + "ScopeChecker.cpp", + "SprintfLiteralChecker.cpp", + "TrivialCtorDtorChecker.cpp", + "TrivialDtorChecker.cpp", + "VariableUsageHelpers.cpp", +] + +# Ideally, we wouldn't have compile-time choices wrt checks. bug 1617153. +if CONFIG["OS_ARCH"] == "WINNT": + HOST_DEFINES["TARGET_IS_WINDOWS"] = True + HOST_SOURCES += [ + "FopenUsageChecker.cpp", + "LoadLibraryUsageChecker.cpp", + ] + +if CONFIG["ENABLE_MOZSEARCH_PLUGIN"]: + HOST_SOURCES += [ + "mozsearch-plugin/BindingOperations.cpp", + "mozsearch-plugin/FileOperations.cpp", + "mozsearch-plugin/from-clangd/HeuristicResolver.cpp", + "mozsearch-plugin/MozsearchIndexer.cpp", + "mozsearch-plugin/StringOperations.cpp", + ] + +GeneratedFile( + "ThirdPartyPaths.cpp", + script="ThirdPartyPaths.py", + entry_point="generate", + inputs=[ + "/tools/rewriting/ThirdPartyPaths.txt", + "/tools/rewriting/Generated.txt", + ], +) + +GeneratedFile( + "ThreadAllows.h", + script="ThreadAllows.py", + entry_point="generate_file", + inputs=[ + "/build/clang-plugin/ThreadAllows.txt", + "/build/clang-plugin/ThreadFileAllows.txt", + ], +) + +HOST_COMPILE_FLAGS["STL"] = [] +HOST_COMPILE_FLAGS["VISIBILITY"] = [] + +# libc++ is required to build plugins against clang on OS X. +if CONFIG["HOST_OS_ARCH"] == "Darwin": + HOST_CXXFLAGS += ["-stdlib=libc++"] + +# As of clang 8, llvm-config doesn't output the flags used to build clang +# itself, so we don't end up with -fPIC as a side effect. llvm.org/PR8220 +if CONFIG["HOST_OS_ARCH"] != "WINNT": + HOST_CXXFLAGS += ["-fPIC"] + +DIRS += [ + "tests", +] + +include("external/sources.mozbuild") + +if CONFIG["ENABLE_CLANG_PLUGIN_ALPHA"]: + HOST_DEFINES["MOZ_CLANG_PLUGIN_ALPHA"] = "1" + include("alpha/sources.mozbuild") + +# In the current moz.build world, we need to override essentially every +# variable to limit ourselves to what we need to build the clang plugin. +if CONFIG["HOST_OS_ARCH"] == "WINNT": + extra_cxxflags = ["-GR-", "-EHsc"] + # Clang 14 headers enforce a requirement upon Visual Studio 2019 headers, + # for support of newer C++ versions, which is necessary for clang itself, + # but as of writing, it's not necessary for the plugin code, so enable + # the escape hatch, at least until we generally upgrade to VS 2019. + HOST_DEFINES["LLVM_FORCE_USE_OLD_TOOLCHAIN"] = True +else: + extra_cxxflags = ["-fno-rtti", "-fno-exceptions"] + +if CONFIG["LLVM_CXXFLAGS"]: + HOST_COMPILE_FLAGS["HOST_CXXFLAGS"] = CONFIG["LLVM_CXXFLAGS"] + extra_cxxflags + +# Avoid -DDEBUG=1 on the command line, which conflicts with a #define +# DEBUG(...) in llvm headers. +DEFINES["DEBUG"] = False diff --git a/build/clang-plugin/mozsearch-plugin/BindingOperations.cpp b/build/clang-plugin/mozsearch-plugin/BindingOperations.cpp new file mode 100644 index 0000000000..79f7f3aebf --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/BindingOperations.cpp @@ -0,0 +1,792 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "BindingOperations.h" + +#include <clang/AST/Attr.h> +#include <clang/AST/Expr.h> +#include <clang/AST/RecursiveASTVisitor.h> + +#include <algorithm> +#include <array> +#include <cuchar> +#include <set> +#include <string> +#include <unordered_map> +#include <vector> + +#ifdef __cpp_lib_optional +#include <optional> +template<typename T> using optional = std::optional<T>; +#else +#include <llvm/ADT/Optional.h> +template<typename T> using optional = clang::Optional<T>; +#endif + +using namespace clang; + +namespace { + +template<typename InputIt> +bool hasReverseQualifiedName(InputIt first, InputIt last, const NamedDecl &tag) +{ + const NamedDecl *currentDecl = &tag; + InputIt currentName; + for (currentName = first; currentName != last; currentName++) { + if (!currentDecl || !currentDecl->getIdentifier() || currentDecl->getName() != *currentName) + return false; + + currentDecl = dyn_cast<NamedDecl>(currentDecl->getDeclContext()); + } + if (currentName != last) + return false; + + if (currentDecl != nullptr) + return false; + + return true; +} + +bool isMozillaJniObjectBase(const CXXRecordDecl &klass) +{ + const auto qualifiedName = std::array<StringRef, 3>{"mozilla", "jni", "ObjectBase"}; + return hasReverseQualifiedName(qualifiedName.crbegin(), qualifiedName.crend(), klass); +} + +bool isMozillaJniNativeImpl(const CXXRecordDecl &klass) +{ + const auto qualifiedName = std::array<StringRef, 3>{"mozilla", "jni", "NativeImpl"}; + return hasReverseQualifiedName(qualifiedName.crbegin(), qualifiedName.crend(), klass); +} + +const NamedDecl *fieldNamed(StringRef name, const RecordDecl &strukt) +{ + for (const auto *decl : strukt.decls()) { + const auto *namedDecl = dyn_cast<VarDecl>(decl); + if (!namedDecl) + continue; + + if (!namedDecl->getIdentifier() || namedDecl->getName() != name) + continue; + + return namedDecl; + } + + return {}; +} + +optional<StringRef> nameFieldValue(const RecordDecl &strukt) +{ + const auto *nameField = dyn_cast_or_null<VarDecl>(fieldNamed("name", strukt)); + if (!nameField) + return {}; + + const auto *def = nameField->getDefinition(); + if (!def) + return {}; + + const auto *name = dyn_cast_or_null<StringLiteral>(def->getInit()); + if (!name) + return {}; + + return name->getString(); +} + +struct AbstractBinding { + // Subset of tools/analysis/BindingSlotLang + enum class Lang { + Cpp, + Jvm, + }; + static constexpr size_t LangLength = 2; + static constexpr std::array<StringRef, LangLength> langNames = { + "cpp", + "jvm", + }; + + static optional<Lang> langFromString(StringRef langName) + { + const auto it = std::find(langNames.begin(), langNames.end(), langName); + if (it == langNames.end()) + return {}; + + return Lang(it - langNames.begin()); + } + static StringRef stringFromLang(Lang lang) + { + return langNames[size_t(lang)]; + } + + // Subset of tools/analysis/BindingSlotKind + enum class Kind { + Class, + Method, + Getter, + Setter, + Const, + }; + static constexpr size_t KindLength = 5; + static constexpr std::array<StringRef, KindLength> kindNames = { + "class", + "method", + "getter", + "setter", + "const", + }; + + static optional<Kind> kindFromString(StringRef kindName) + { + const auto it = std::find(kindNames.begin(), kindNames.end(), kindName); + if (it == kindNames.end()) + return {}; + + return Kind(it - kindNames.begin()); + } + static StringRef stringFromKind(Kind kind) + { + return kindNames[size_t(kind)]; + } + + Lang lang; + Kind kind; + StringRef symbol; +}; +constexpr size_t AbstractBinding::KindLength; +constexpr std::array<StringRef, AbstractBinding::KindLength> AbstractBinding::kindNames; +constexpr size_t AbstractBinding::LangLength; +constexpr std::array<StringRef, AbstractBinding::LangLength> AbstractBinding::langNames; + +struct BindingTo : public AbstractBinding { + BindingTo(AbstractBinding b) : AbstractBinding(std::move(b)) {} + static constexpr StringRef ANNOTATION = "binding_to"; +}; +constexpr StringRef BindingTo::ANNOTATION; + +struct BoundAs : public AbstractBinding { + BoundAs(AbstractBinding b) : AbstractBinding(std::move(b)) {} + static constexpr StringRef ANNOTATION = "bound_as"; +}; +constexpr StringRef BoundAs::ANNOTATION; + +template<typename B> +void setBindingAttr(ASTContext &C, Decl &decl, B binding) +{ + // recent LLVM: CreateImplicit then setDelayedArgs + Expr *langExpr = StringLiteral::Create(C, AbstractBinding::stringFromLang(binding.lang), StringLiteral::UTF8, false, {}, {}); + Expr *kindExpr = StringLiteral::Create(C, AbstractBinding::stringFromKind(binding.kind), StringLiteral::UTF8, false, {}, {}); + Expr *symbolExpr = StringLiteral::Create(C, binding.symbol, StringLiteral::UTF8, false, {}, {}); + auto **args = new (C, 16) Expr *[3]{langExpr, kindExpr, symbolExpr}; + auto *attr = AnnotateAttr::CreateImplicit(C, B::ANNOTATION, args, 3); + decl.addAttr(attr); +} + +optional<AbstractBinding> readBinding(const AnnotateAttr &attr) +{ + if (attr.args_size() != 3) + return {}; + + const auto *langExpr = attr.args().begin()[0]; + const auto *kindExpr = attr.args().begin()[1]; + const auto *symbolExpr = attr.args().begin()[2]; + if (!langExpr || !kindExpr || !symbolExpr) + return {}; + + const auto *langName = dyn_cast<StringLiteral>(langExpr->IgnoreUnlessSpelledInSource()); + const auto *kindName = dyn_cast<StringLiteral>(kindExpr->IgnoreUnlessSpelledInSource()); + const auto *symbol = dyn_cast<StringLiteral>(symbolExpr->IgnoreUnlessSpelledInSource()); + if (!langName || !kindName || !symbol) + return {}; + + const auto lang = AbstractBinding::langFromString(langName->getString()); + const auto kind = AbstractBinding::kindFromString(kindName->getString()); + + if (!lang || !kind) + return {}; + + return AbstractBinding { + .lang = *lang, + .kind = *kind, + .symbol = symbol->getString(), + }; +} + +optional<BindingTo> getBindingTo(const Decl &decl) +{ + for (const auto *attr : decl.specific_attrs<AnnotateAttr>()) { + if (attr->getAnnotation() != BindingTo::ANNOTATION) + continue; + + const auto binding = readBinding(*attr); + if (!binding) + continue; + + return BindingTo{*binding}; + } + return {}; +} + +// C++23: turn into generator +std::vector<BoundAs> getBoundAs(const Decl &decl) +{ + std::vector<BoundAs> found; + + for (const auto *attr : decl.specific_attrs<AnnotateAttr>()) { + if (attr->getAnnotation() != BoundAs::ANNOTATION) + continue; + + const auto binding = readBinding(*attr); + if (!binding) + continue; + + found.push_back(BoundAs{*binding}); + } + + return found; +} + +class FindCallCall : private RecursiveASTVisitor<FindCallCall> +{ +public: + struct Result { + using Kind = AbstractBinding::Kind; + + Kind kind; + StringRef name; + }; + + static optional<Result> search(Stmt *statement) + { + FindCallCall finder; + finder.TraverseStmt(statement); + return finder.result; + } + +private: + optional<Result> result; + + friend RecursiveASTVisitor<FindCallCall>; + + optional<Result> tryParseCallCall(CallExpr *callExpr){ + const auto *callee = dyn_cast_or_null<CXXMethodDecl>(callExpr->getDirectCallee()); + if (!callee) + return {}; + + if (!callee->getIdentifier()) + return {}; + + const auto action = callee->getIdentifier()->getName(); + + if (action != "Call" && action != "Get" && action != "Set") + return {}; + + const auto *parentClass = dyn_cast_or_null<ClassTemplateSpecializationDecl>(callee->getParent()); + + if (!parentClass) + return {}; + + const auto *parentTemplate = parentClass->getTemplateInstantiationPattern(); + + if (!parentTemplate || !parentTemplate->getIdentifier()) + return {}; + + const auto parentName = parentTemplate->getIdentifier()->getName(); + + AbstractBinding::Kind kind; + if (action == "Call") { + if (parentName == "Constructor" || parentName == "Method") { + kind = AbstractBinding::Kind::Method; + } else { + return {}; + } + } else if (parentName == "Field") { + if (action == "Get") { + kind = AbstractBinding::Kind::Getter; + } else if (action == "Set") { + kind = AbstractBinding::Kind::Setter; + } else { + return {}; + } + } else { + return {}; + } + + const auto *templateArg = parentClass->getTemplateArgs().get(0).getAsType()->getAsRecordDecl(); + + if (!templateArg) + return {}; + + const auto name = nameFieldValue(*templateArg); + if (!name) + return {}; + + return Result { + .kind = kind, + .name = *name, + }; + + return {}; + } + bool VisitCallExpr(CallExpr *callExpr) + { + return !(result = tryParseCallCall(callExpr)); + } +}; + +constexpr StringRef JVM_SCIP_SYMBOL_PREFIX = "S_jvm_"; + +std::string javaScipSymbol(StringRef prefix, StringRef name, AbstractBinding::Kind kind) +{ + auto symbol = (prefix + name).str(); + + switch (kind) { + case AbstractBinding::Kind::Class: + std::replace(symbol.begin(), symbol.end(), '$', '#'); + symbol += "#"; + break; + case AbstractBinding::Kind::Method: + symbol += "()."; + break; + case AbstractBinding::Kind::Const: + case AbstractBinding::Kind::Getter: + case AbstractBinding::Kind::Setter: + symbol += "."; + break; + } + + return symbol; +} + +void addSlotOwnerAttribute(llvm::json::OStream &J, const Decl &decl) +{ + if (const auto bindingTo = getBindingTo(decl)) { + J.attributeBegin("slotOwner"); + J.objectBegin(); + J.attribute("slotKind", AbstractBinding::stringFromKind(bindingTo->kind)); + J.attribute("slotLang", "cpp"); + J.attribute("ownerLang", AbstractBinding::stringFromLang(bindingTo->lang)); + J.attribute("sym", bindingTo->symbol); + J.objectEnd(); + J.attributeEnd(); + } +} +void addBindingSlotsAttribute(llvm::json::OStream &J, const Decl &decl) +{ + const auto allBoundAs = getBoundAs(decl); + if (!allBoundAs.empty()) { + J.attributeBegin("bindingSlots"); + J.arrayBegin(); + for (const auto boundAs : allBoundAs) { + J.objectBegin(); + J.attribute("slotKind", AbstractBinding::stringFromKind(boundAs.kind)); + J.attribute("slotLang", AbstractBinding::stringFromLang(boundAs.lang)); + J.attribute("ownerLang", "cpp"); + J.attribute("sym", boundAs.symbol); + J.objectEnd(); + } + J.arrayEnd(); + J.attributeEnd(); + } +} + +// The mangling scheme is documented at https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html +// The main takeaways are: +// - _0xxxx is the utf16 code unit xxxx +// - _1 is _ +// - _2 is ; +// - _3 is [ +// - __ is the separator between function name and overload specification +// - _ is otherwise the separator between packages/classes/methods +// +// This method takes a StringRef & and mutates it and can be called twice on a Jnicall function name to get +// first the demangled name +// second the demangled overload specification +// But we don't use the later for now because we have no way to map that to how SCIP resolves overloads. +optional<std::string> demangleJnicallPart(StringRef &remainder) +{ + std::string demangled; + + std::mbstate_t ps = {}; + + while (!remainder.empty()) { + switch (remainder[0]) { + case '0': { + remainder = remainder.drop_front(1); + + uint16_t codeUnit; + const auto ok = remainder.substr(1, 4).getAsInteger(16, codeUnit); + remainder = remainder.drop_front(4); + + if (!ok) // failed reading xxxx as hexadecimal from _0xxxx + return {}; + + std::array<char, MB_LEN_MAX> codePoint; + const auto mbLen = std::c16rtomb(codePoint.data(), codeUnit, &ps); + + if (mbLen == -1) // failed converting utf16 to utf8 + return {}; + + demangled += StringRef(codePoint.begin(), mbLen); + break; + } + case '1': + remainder = remainder.drop_front(1); + ps = {}; + demangled += '_'; + break; + case '2': + remainder = remainder.drop_front(1); + ps = {}; + demangled += ';'; + break; + case '3': + remainder = remainder.drop_front(1); + ps = {}; + demangled += '['; + break; + case '_': + remainder = remainder.drop_front(1); + ps = {}; + if (remainder.empty()) // the string ends with _ + return {}; + + switch (remainder[0]) { + case '0': + case '1': + case '2': + case '3': + demangled += '.'; + break; + default: + // either: + // * the string began with _[^0-3], which is not supposed to happen; or + // * we reached __[^0-3] meaning we finished the first part of the name and remainder holds the overload specification + return demangled; + } + default: + ps = {}; + demangled += '.'; + break; + } + StringRef token; + std::tie(token, remainder) = remainder.split('_'); + demangled += token; + } + + return demangled; +} + +optional<std::string> scipSymbolFromJnicallFunctionName(StringRef functionName) +{ + if (!functionName.consume_front("Java_")) + return {}; + + const auto demangledName = demangleJnicallPart(functionName); + + if (!demangledName || demangledName->empty()) + return {}; + + // demangleJavaName returns something like .some.package.Class$InnerClass.method + // - prepend S_jvm_ + // - remove the leading dot + // - replace the last dot with a # + // - replace the other dots with / + // - replace $ with # + // - add the ([+overloadNumber]). suffix + auto symbol = JVM_SCIP_SYMBOL_PREFIX.str(); + symbol += demangledName->substr(1); + const auto lastDot = symbol.rfind('.'); + if (lastDot != std::string::npos) + symbol[lastDot] = '#'; + std::replace(symbol.begin(), symbol.end(), '.', '/'); + std::replace(symbol.begin(), symbol.end(), '$', '#'); + + // Keep track of how many times we have seen this method, to build the ([+overloadNumber]). suffix. + // This assumes this function is called on C function definitions in the same order the matching overloads are declared in Java. + static std::unordered_map<std::string, uint> jnicallFunctions; + auto &overloadNumber = jnicallFunctions[symbol]; + + symbol += '('; + if (overloadNumber) { + symbol += '+'; + symbol += overloadNumber; + overloadNumber++; + } + symbol += ")."; + + return symbol; +}; + +} // anonymous namespace + +// class [wrapper] : public mozilla::jni::ObjectBase<[wrapper]> +// { +// static constexpr char name[] = "[nameFieldValue]"; +// } +void findBindingToJavaClass(ASTContext &C, CXXRecordDecl &klass) +{ + for (const auto &baseSpecifier : klass.bases()) { + const auto *base = baseSpecifier.getType()->getAsCXXRecordDecl(); + if (!base) + continue; + + if (!isMozillaJniObjectBase(*base)) + continue; + + const auto name = nameFieldValue(klass); + if (!name) + continue; + + const auto symbol = javaScipSymbol(JVM_SCIP_SYMBOL_PREFIX, *name, BindingTo::Kind::Class); + const auto binding = BindingTo {{ + .lang = BindingTo::Lang::Jvm, + .kind = BindingTo::Kind::Class, + .symbol = symbol, + }}; + + setBindingAttr(C, klass, binding); + return; + } +} + +// When a Java method is marked as native, the JRE looks by default for a function +// named Java_<mangled method name>[__<mangled overload specification>]. +void findBindingToJavaFunction(ASTContext &C, FunctionDecl &function) +{ + const auto *identifier = function.getIdentifier(); + if (!identifier) + return; + + const auto name = identifier->getName(); + const auto symbol = scipSymbolFromJnicallFunctionName(name); + if (!symbol) + return; + + const auto binding = BoundAs {{ + .lang = BindingTo::Lang::Jvm, + .kind = BindingTo::Kind::Method, + .symbol = *symbol, + }}; + + setBindingAttr(C, function, binding); +} + +// class [parent] +// { +// struct [methodStruct] { +// static constexpr char name[] = "[methodNameFieldValue]"; +// } +// [method] +// { +// ... +// mozilla::jni::{Method,Constructor,Field}<[methodStruct]>::{Call,Get,Set}(...) +// ... +// } +// } +void findBindingToJavaMember(ASTContext &C, CXXMethodDecl &method) +{ + const auto *parent = method.getParent(); + if (!parent) + return; + const auto classBinding = getBindingTo(*parent); + if (!classBinding) + return; + + auto *body = method.getBody(); + if (!body) + return; + + const auto found = FindCallCall::search(body); + if (!found) + return; + + const auto symbol = javaScipSymbol(classBinding->symbol, found->name, found->kind); + const auto binding = BindingTo {{ + .lang = BindingTo::Lang::Jvm, + .kind = found->kind, + .symbol = symbol, + }}; + + setBindingAttr(C, method, binding); +} + +// class [parent] +// { +// struct [methodStruct] { +// static constexpr char name[] = "[methodNameFieldValue]"; +// } +// [method] +// { +// ... +// mozilla::jni::{Method,Constructor,Field}<[methodStruct]>::{Call,Get,Set}(...) +// ... +// } +// } +void findBindingToJavaConstant(ASTContext &C, VarDecl &field) +{ + const auto *parent = dyn_cast_or_null<CXXRecordDecl>(field.getDeclContext()); + if (!parent) + return; + + const auto classBinding = getBindingTo(*parent); + if (!classBinding) + return; + + const auto symbol = javaScipSymbol(classBinding->symbol, field.getName(), BindingTo::Kind::Const); + const auto binding = BindingTo {{ + .lang = BindingTo::Lang::Jvm, + .kind = BindingTo::Kind::Const, + .symbol = symbol, + }}; + + setBindingAttr(C, field, binding); +} + +// class [klass] : public [wrapper]::Natives<[klass]> {...} +// class [wrapper] : public mozilla::jni::ObjectBase<[wrapper]> +// { +// static constexpr char name[] = "[nameFieldValue]"; +// +// struct [methodStruct] { +// static constexpr char name[] = "[methodNameFieldValue]"; +// } +// +// template<typename T> +// class [wrapper]::Natives : public mozilla::jni::NativeImpl<[wrapper], T> { +// static const JNINativeMethod methods[] = { +// mozilla::jni::MakeNativeMethod<[wrapper]::[methodStruct]>( +// mozilla::jni::NativeStub<[wrapper]::[methodStruct], Impl> +// ::template Wrap<&Impl::[method]>), +// } +// } +// } +void findBoundAsJavaClasses(ASTContext &C, CXXRecordDecl &klass) +{ + for (const auto &baseSpecifier : klass.bases()) { + const auto *base = baseSpecifier.getType()->getAsCXXRecordDecl(); + if (!base) + continue; + + for (const auto &baseBaseSpecifier : base->bases()) { + const auto *baseBase = dyn_cast_or_null<ClassTemplateSpecializationDecl>(baseBaseSpecifier.getType()->getAsCXXRecordDecl()); + if (!baseBase) + continue; + + if (!isMozillaJniNativeImpl(*baseBase)) + continue; + + const auto *wrapper = baseBase->getTemplateArgs().get(0).getAsType()->getAsCXXRecordDecl(); + + if (!wrapper) + continue; + + const auto name = nameFieldValue(*wrapper); + if (!name) + continue; + + const auto javaClassSymbol = javaScipSymbol(JVM_SCIP_SYMBOL_PREFIX, *name, BoundAs::Kind::Class); + const auto classBinding = BoundAs {{ + .lang = BoundAs::Lang::Jvm, + .kind = BoundAs::Kind::Class, + .symbol = javaClassSymbol, + }}; + setBindingAttr(C, klass, classBinding); + + const auto *methodsDecl = dyn_cast_or_null<VarDecl>(fieldNamed("methods", *base)); + if (!methodsDecl) + continue; + + const auto *methodsDef = methodsDecl->getDefinition(); + if (!methodsDef) + continue; + + const auto *inits = dyn_cast_or_null<InitListExpr>(methodsDef->getInit()); + if (!inits) + continue; + + std::set<const CXXMethodDecl *> alreadyBound; + + for (const auto *init : inits->inits()) { + const auto *call = dyn_cast<CallExpr>(init->IgnoreUnlessSpelledInSource()); + if (!call) + continue; + + const auto *funcDecl = call->getDirectCallee(); + if (!funcDecl) + continue; + + const auto *templateArgs = funcDecl->getTemplateSpecializationArgs(); + if (!templateArgs) + continue; + + const auto *strukt = dyn_cast_or_null<RecordDecl>(templateArgs->get(0).getAsType()->getAsRecordDecl()); + if (!strukt) + continue; + + const auto *wrapperRef = dyn_cast_or_null<DeclRefExpr>(call->getArg(0)->IgnoreUnlessSpelledInSource()); + if (!wrapperRef) + continue; + + const auto *boundRef = dyn_cast_or_null<UnaryOperator>(wrapperRef->template_arguments().front().getArgument().getAsExpr()); + if (!boundRef) + continue; + + auto addToBound = [&](CXXMethodDecl &boundDecl, uint overloadNum) { + const auto methodName = nameFieldValue(*strukt); + if (!methodName) + return; + + auto javaMethodSymbol = javaClassSymbol; + javaMethodSymbol += *methodName; + javaMethodSymbol += '('; + if (overloadNum > 0) { + javaMethodSymbol += '+'; + javaMethodSymbol += std::to_string(overloadNum); + } + javaMethodSymbol += ")."; + + const auto binding = BoundAs {{ + .lang = BoundAs::Lang::Jvm, + .kind = BoundAs::Kind::Method, + .symbol = javaMethodSymbol, + }}; + setBindingAttr(C, boundDecl, binding); + }; + + if (auto *bound = dyn_cast_or_null<DeclRefExpr>(boundRef->getSubExpr())) { + auto *method = dyn_cast_or_null<CXXMethodDecl>(bound->getDecl()); + if (!method) + continue; + addToBound(*method, 0); + } else if (const auto *bound = dyn_cast_or_null<UnresolvedLookupExpr>(boundRef->getSubExpr())) { + // XXX This is hackish + // In case of overloads it's not obvious which one we should use + // this expects the declaration order between C++ and Java to match + auto declarations = std::vector<Decl*>(bound->decls_begin(), bound->decls_end()); + auto byLocation = [](Decl *a, Decl *b){ return a->getLocation() < b->getLocation(); }; + std::sort(declarations.begin(), declarations.end(), byLocation); + + uint i = 0; + for (auto *decl : declarations) { + auto *method = dyn_cast<CXXMethodDecl>(decl); + if (!method) + continue; + if (alreadyBound.find(method) == alreadyBound.end()) { + addToBound(*method, i); + alreadyBound.insert(method); + break; + } + i++; + } + } + } + } + } +} + +void emitBindingAttributes(llvm::json::OStream &J, const Decl &decl) +{ + addSlotOwnerAttribute(J, decl); + addBindingSlotsAttribute(J, decl); +} diff --git a/build/clang-plugin/mozsearch-plugin/BindingOperations.h b/build/clang-plugin/mozsearch-plugin/BindingOperations.h new file mode 100644 index 0000000000..259594c5c3 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/BindingOperations.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <clang/AST/ASTContext.h> +#include <clang/AST/Decl.h> +#include <clang/AST/DeclCXX.h> +#include <llvm/Support/JSON.h> + +void findBindingToJavaClass(clang::ASTContext &C, clang::CXXRecordDecl &klass); +void findBoundAsJavaClasses(clang::ASTContext &C, clang::CXXRecordDecl &klass); +void findBindingToJavaFunction(clang::ASTContext &C, clang::FunctionDecl &function); +void findBindingToJavaMember(clang::ASTContext &C, clang::CXXMethodDecl &method); +void findBindingToJavaConstant(clang::ASTContext &C, clang::VarDecl &field); + +void emitBindingAttributes(llvm::json::OStream &json, const clang::Decl &decl); diff --git a/build/clang-plugin/mozsearch-plugin/FileOperations.cpp b/build/clang-plugin/mozsearch-plugin/FileOperations.cpp new file mode 100644 index 0000000000..9307f4989d --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/FileOperations.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "FileOperations.h" + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) || defined(_WIN64) +#include <direct.h> +#include <io.h> +#include <windows.h> +#include "StringOperations.h" +#else +#include <sys/file.h> +#include <sys/time.h> +#include <unistd.h> +#endif + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +// Make sure that all directories on path exist, excluding the final element of +// the path. +void ensurePath(std::string Path) { + size_t Pos = 0; + if (Path[0] == PATHSEP_CHAR) { + Pos++; + } + + while ((Pos = Path.find(PATHSEP_CHAR, Pos)) != std::string::npos) { + std::string Portion = Path.substr(0, Pos); + if (!Portion.empty()) { +#if defined(_WIN32) || defined(_WIN64) + int Err = _mkdir(Portion.c_str()); +#else + int Err = mkdir(Portion.c_str(), 0775); +#endif + if (Err == -1 && errno != EEXIST) { + perror("mkdir failed"); + exit(1); + } + } + + Pos++; + } +} + +#if defined(_WIN32) || defined(_WIN64) +AutoLockFile::AutoLockFile(const std::string &SrcFile, const std::string &DstFile) { + this->Filename = DstFile; + std::string Hash = hash(SrcFile); + std::string MutexName = std::string("Local\\searchfox-") + Hash; + std::wstring WideMutexName; + WideMutexName.assign(MutexName.begin(), MutexName.end()); + Handle = CreateMutex(nullptr, false, WideMutexName.c_str()); + if (Handle == NULL) { + return; + } + + if (WaitForSingleObject(Handle, INFINITE) != WAIT_OBJECT_0) { + return; + } +} + +AutoLockFile::~AutoLockFile() { + ReleaseMutex(Handle); + CloseHandle(Handle); +} + +bool AutoLockFile::success() { + return Handle != NULL; +} + +FILE *AutoLockFile::openTmp() { + int TmpDescriptor = _open((Filename + ".tmp").c_str(), _O_WRONLY | _O_APPEND | _O_CREAT | _O_BINARY, 0666); + return _fdopen(TmpDescriptor, "ab"); +} + +bool AutoLockFile::moveTmp() { + if (_unlink(Filename.c_str()) == -1) { + if (errno != ENOENT) { + return false; + } + } + return rename((Filename + ".tmp").c_str(), Filename.c_str()) == 0; +} + +std::string getAbsolutePath(const std::string &Filename) { + char Full[_MAX_PATH]; + if (!_fullpath(Full, Filename.c_str(), _MAX_PATH)) { + return std::string(""); + } + return std::string(Full); +} +#else +AutoLockFile::AutoLockFile(const std::string &SrcFile, const std::string &DstFile) { + this->Filename = DstFile; + FileDescriptor = open(SrcFile.c_str(), O_RDONLY); + if (FileDescriptor == -1) { + return; + } + + do { + int rv = flock(FileDescriptor, LOCK_EX); + if (rv == 0) { + break; + } + } while (true); +} + +AutoLockFile::~AutoLockFile() { close(FileDescriptor); } + +bool AutoLockFile::success() { return FileDescriptor != -1; } + +FILE* AutoLockFile::openTmp() { + int TmpDescriptor = open((Filename + ".tmp").c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666); + return fdopen(TmpDescriptor, "ab"); +} + +bool AutoLockFile::moveTmp() { + if (unlink(Filename.c_str()) == -1) { + if (errno != ENOENT) { + return false; + } + } + return rename((Filename + ".tmp").c_str(), Filename.c_str()) == 0; +} + +std::string getAbsolutePath(const std::string &Filename) { + char Full[4096]; + if (!realpath(Filename.c_str(), Full)) { + return std::string(""); + } + return std::string(Full); +} +#endif diff --git a/build/clang-plugin/mozsearch-plugin/FileOperations.h b/build/clang-plugin/mozsearch-plugin/FileOperations.h new file mode 100644 index 0000000000..90764484da --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/FileOperations.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 FileOperations_h +#define FileOperations_h + +#include <stdio.h> +#include <string> + +#if defined(_WIN32) || defined(_WIN64) +#include <windows.h> +#define PATHSEP_CHAR '\\' +#define PATHSEP_STRING "\\" +#else +#define PATHSEP_CHAR '/' +#define PATHSEP_STRING "/" +#endif + +// Make sure that all directories on path exist, excluding the final element of +// the path. +void ensurePath(std::string Path); + +std::string getAbsolutePath(const std::string &Filename); + +// Used to synchronize access when writing to an analysis file, so that +// concurrently running clang instances don't clobber each other's data. +// On Windows, we use a named mutex. On POSIX platforms, we use flock on the +// source files. flock is advisory locking, and doesn't interfere with clang's +// own opening of the source files (i.e. to interfere, clang would have to be +// using flock itself, which it does not). +struct AutoLockFile { + // Absolute path to the analysis file + std::string Filename; + +#if defined(_WIN32) || defined(_WIN64) + // Handle for the named Mutex + HANDLE Handle = NULL; +#else + // fd for the *source* file that corresponds to the analysis file. We use + // the source file because it doesn't change while the analysis file gets + // repeatedly replaced by a new version written to a separate tmp file. + // This fd is used when using flock to synchronize access. + int FileDescriptor = -1; +#endif + + // SrcFile should be the absolute path to the source code file, and DstFile + // the absolute path to the corresponding analysis file. This constructor + // will block until exclusive access has been obtained. + AutoLockFile(const std::string &SrcFile, const std::string &DstFile); + ~AutoLockFile(); + + // Check after constructing to ensure the mutex was properly set up. + bool success(); + + // There used to be an `openFile` method here but we switched to directly + // using a std::ifstream for the input file in able to take advantage of its + // support for variable length lines (as opposed to fgets which takes a fixed + // size buffer). + + // Open a new tmp file for writing the new analysis data to. Caller is + // responsible for fclose'ing it. + FILE *openTmp(); + // Replace the existing analysis file with the new "tmp" one that has the new + // data. Returns false on error. + bool moveTmp(); +}; + +#endif diff --git a/build/clang-plugin/mozsearch-plugin/MozsearchIndexer.cpp b/build/clang-plugin/mozsearch-plugin/MozsearchIndexer.cpp new file mode 100644 index 0000000000..f6958384a8 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/MozsearchIndexer.cpp @@ -0,0 +1,2436 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Mangle.h" +#include "clang/AST/RecordLayout.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" + +#include <fstream> +#include <iostream> +#include <map> +#include <memory> +#include <sstream> +#include <string> +#include <tuple> +#include <unordered_set> + +#include <stdio.h> +#include <stdlib.h> + +#include "BindingOperations.h" +#include "FileOperations.h" +#include "StringOperations.h" +#include "from-clangd/HeuristicResolver.h" + +#if CLANG_VERSION_MAJOR < 8 +// Starting with Clang 8.0 some basic functions have been renamed +#define getBeginLoc getLocStart +#define getEndLoc getLocEnd +#endif +// We want std::make_unique, but that's only available in c++14. In versions +// prior to that, we need to fall back to llvm's make_unique. It's also the +// case that we expect clang 10 to build with c++14 and clang 9 and earlier to +// build with c++11, at least as suggested by the llvm-config --cxxflags on +// non-windows platforms. mozilla-central seems to build with -std=c++17 on +// windows so we need to make this decision based on __cplusplus instead of +// the CLANG_VERSION_MAJOR. +#if __cplusplus < 201402L +using llvm::make_unique; +#else +using std::make_unique; +#endif + +using namespace clang; + +const std::string GENERATED("__GENERATED__" PATHSEP_STRING); + +// Absolute path to directory containing source code. +std::string Srcdir; + +// Absolute path to objdir (including generated code). +std::string Objdir; + +// Absolute path where analysis JSON output will be stored. +std::string Outdir; + +enum class FileType { + // The file was either in the source tree nor objdir. It might be a system + // include, for example. + Unknown, + // A file from the source tree. + Source, + // A file from the objdir. + Generated, +}; + +// Takes an absolute path to a file, and returns the type of file it is. If +// it's a Source or Generated file, the provided inout path argument is modified +// in-place so that it is relative to the source dir or objdir, respectively. +FileType relativizePath(std::string& path) { + if (path.compare(0, Objdir.length(), Objdir) == 0) { + path.replace(0, Objdir.length(), GENERATED); + return FileType::Generated; + } + // Empty filenames can get turned into Srcdir when they are resolved as + // absolute paths, so we should exclude files that are exactly equal to + // Srcdir or anything outside Srcdir. + if (path.length() > Srcdir.length() && path.compare(0, Srcdir.length(), Srcdir) == 0) { + // Remove the trailing `/' as well. + path.erase(0, Srcdir.length() + 1); + return FileType::Source; + } + return FileType::Unknown; +} + +#if !defined(_WIN32) && !defined(_WIN64) +#include <sys/time.h> + +static double time() { + struct timeval Tv; + gettimeofday(&Tv, nullptr); + return double(Tv.tv_sec) + double(Tv.tv_usec) / 1000000.; +} +#endif + +// Return true if |input| is a valid C++ identifier. We don't want to generate +// analysis information for operators, string literals, etc. by accident since +// it trips up consumers of the data. +static bool isValidIdentifier(std::string Input) { + for (char C : Input) { + if (!(isalpha(C) || isdigit(C) || C == '_')) { + return false; + } + } + return true; +} + +struct RAIITracer { + RAIITracer(const char *log) : mLog(log) { + printf("<%s>\n", mLog); + } + + ~RAIITracer() { + printf("</%s>\n", mLog); + } + + const char* mLog; +}; + +#define TRACEFUNC RAIITracer tracer(__FUNCTION__); + +class IndexConsumer; + +// For each C++ file seen by the analysis (.cpp or .h), we track a +// FileInfo. This object tracks whether the file is "interesting" (i.e., whether +// it's in the source dir or the objdir). We also store the analysis output +// here. +struct FileInfo { + FileInfo(std::string &Rname) : Realname(Rname) { + switch (relativizePath(Realname)) { + case FileType::Generated: + Interesting = true; + Generated = true; + break; + case FileType::Source: + Interesting = true; + Generated = false; + break; + case FileType::Unknown: + Interesting = false; + Generated = false; + break; + } + } + std::string Realname; + std::vector<std::string> Output; + bool Interesting; + bool Generated; +}; + +class IndexConsumer; + +class PreprocessorHook : public PPCallbacks { + IndexConsumer *Indexer; + +public: + PreprocessorHook(IndexConsumer *C) : Indexer(C) {} + + virtual void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override; + + virtual void InclusionDirective(SourceLocation HashLoc, + const Token &IncludeTok, + StringRef FileName, + bool IsAngled, + CharSourceRange FileNameRange, +#if CLANG_VERSION_MAJOR >= 16 + OptionalFileEntryRef File, +#elif CLANG_VERSION_MAJOR >= 15 + Optional<FileEntryRef> File, +#else + const FileEntry *File, +#endif + StringRef SearchPath, + StringRef RelativePath, + const Module *Imported, + SrcMgr::CharacteristicKind FileType) override; + + virtual void MacroDefined(const Token &Tok, + const MacroDirective *Md) override; + + virtual void MacroExpands(const Token &Tok, const MacroDefinition &Md, + SourceRange Range, const MacroArgs *Ma) override; + virtual void MacroUndefined(const Token &Tok, const MacroDefinition &Md, + const MacroDirective *Undef) override; + virtual void Defined(const Token &Tok, const MacroDefinition &Md, + SourceRange Range) override; + virtual void Ifdef(SourceLocation Loc, const Token &Tok, + const MacroDefinition &Md) override; + virtual void Ifndef(SourceLocation Loc, const Token &Tok, + const MacroDefinition &Md) override; +}; + +class IndexConsumer : public ASTConsumer, + public RecursiveASTVisitor<IndexConsumer>, + public DiagnosticConsumer { +private: + CompilerInstance &CI; + SourceManager &SM; + LangOptions &LO; + std::map<FileID, std::unique_ptr<FileInfo>> FileMap; + MangleContext *CurMangleContext; + ASTContext *AstContext; + std::unique_ptr<clangd::HeuristicResolver> Resolver; + + typedef RecursiveASTVisitor<IndexConsumer> Super; + + // Tracks the set of declarations that the current expression/statement is + // nested inside of. + struct AutoSetContext { + AutoSetContext(IndexConsumer *Self, NamedDecl *Context, bool VisitImplicit = false) + : Self(Self), Prev(Self->CurDeclContext), Decl(Context) { + this->VisitImplicit = VisitImplicit || (Prev ? Prev->VisitImplicit : false); + Self->CurDeclContext = this; + } + + ~AutoSetContext() { Self->CurDeclContext = Prev; } + + IndexConsumer *Self; + AutoSetContext *Prev; + NamedDecl *Decl; + bool VisitImplicit; + }; + AutoSetContext *CurDeclContext; + + FileInfo *getFileInfo(SourceLocation Loc) { + FileID Id = SM.getFileID(Loc); + + std::map<FileID, std::unique_ptr<FileInfo>>::iterator It; + It = FileMap.find(Id); + if (It == FileMap.end()) { + // We haven't seen this file before. We need to make the FileInfo + // structure information ourselves + std::string Filename = std::string(SM.getFilename(Loc)); + std::string Absolute; + // If Loc is a macro id rather than a file id, it Filename might be + // empty. Also for some types of file locations that are clang-internal + // like "<scratch>" it can return an empty Filename. In these cases we + // want to leave Absolute as empty. + if (!Filename.empty()) { + Absolute = getAbsolutePath(Filename); + if (Absolute.empty()) { + Absolute = Filename; + } + } + std::unique_ptr<FileInfo> Info = make_unique<FileInfo>(Absolute); + It = FileMap.insert(std::make_pair(Id, std::move(Info))).first; + } + return It->second.get(); + } + + // Helpers for processing declarations + // Should we ignore this location? + bool isInterestingLocation(SourceLocation Loc) { + if (Loc.isInvalid()) { + return false; + } + + return getFileInfo(Loc)->Interesting; + } + + // Convert location to "line:column" or "line:column-column" given length. + // In resulting string rep, line is 1-based and zero-padded to 5 digits, while + // column is 0-based and unpadded. + std::string locationToString(SourceLocation Loc, size_t Length = 0) { + std::pair<FileID, unsigned> Pair = SM.getDecomposedLoc(Loc); + + bool IsInvalid; + unsigned Line = SM.getLineNumber(Pair.first, Pair.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + unsigned Column = SM.getColumnNumber(Pair.first, Pair.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + + if (Length) { + return stringFormat("%05d:%d-%d", Line, Column - 1, Column - 1 + Length); + } else { + return stringFormat("%05d:%d", Line, Column - 1); + } + } + + // Convert SourceRange to "line-line". + // In the resulting string rep, line is 1-based. + std::string lineRangeToString(SourceRange Range) { + std::pair<FileID, unsigned> Begin = SM.getDecomposedLoc(Range.getBegin()); + std::pair<FileID, unsigned> End = SM.getDecomposedLoc(Range.getEnd()); + + bool IsInvalid; + unsigned Line1 = SM.getLineNumber(Begin.first, Begin.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + unsigned Line2 = SM.getLineNumber(End.first, End.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + + return stringFormat("%d-%d", Line1, Line2); + } + + // Convert SourceRange to "line:column-line:column". + // In the resulting string rep, line is 1-based, column is 0-based. + std::string fullRangeToString(SourceRange Range) { + std::pair<FileID, unsigned> Begin = SM.getDecomposedLoc(Range.getBegin()); + std::pair<FileID, unsigned> End = SM.getDecomposedLoc(Range.getEnd()); + + bool IsInvalid; + unsigned Line1 = SM.getLineNumber(Begin.first, Begin.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + unsigned Column1 = SM.getColumnNumber(Begin.first, Begin.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + unsigned Line2 = SM.getLineNumber(End.first, End.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + unsigned Column2 = SM.getColumnNumber(End.first, End.second, &IsInvalid); + if (IsInvalid) { + return ""; + } + + return stringFormat("%d:%d-%d:%d", Line1, Column1 - 1, Line2, Column2 - 1); + } + + // Returns the qualified name of `d` without considering template parameters. + std::string getQualifiedName(const NamedDecl *D) { + const DeclContext *Ctx = D->getDeclContext(); + if (Ctx->isFunctionOrMethod()) { + return D->getQualifiedNameAsString(); + } + + std::vector<const DeclContext *> Contexts; + + // Collect contexts. + while (Ctx && isa<NamedDecl>(Ctx)) { + Contexts.push_back(Ctx); + Ctx = Ctx->getParent(); + } + + std::string Result; + + std::reverse(Contexts.begin(), Contexts.end()); + + for (const DeclContext *DC : Contexts) { + if (const auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(DC)) { + Result += Spec->getNameAsString(); + + if (Spec->getSpecializationKind() == TSK_ExplicitSpecialization) { + std::string Backing; + llvm::raw_string_ostream Stream(Backing); + const TemplateArgumentList &TemplateArgs = Spec->getTemplateArgs(); + printTemplateArgumentList( + Stream, TemplateArgs.asArray(), PrintingPolicy(CI.getLangOpts())); + Result += Stream.str(); + } + } else if (const auto *Nd = dyn_cast<NamespaceDecl>(DC)) { + if (Nd->isAnonymousNamespace() || Nd->isInline()) { + continue; + } + Result += Nd->getNameAsString(); + } else if (const auto *Rd = dyn_cast<RecordDecl>(DC)) { + if (!Rd->getIdentifier()) { + Result += "(anonymous)"; + } else { + Result += Rd->getNameAsString(); + } + } else if (const auto *Fd = dyn_cast<FunctionDecl>(DC)) { + Result += Fd->getNameAsString(); + } else if (const auto *Ed = dyn_cast<EnumDecl>(DC)) { + // C++ [dcl.enum]p10: Each enum-name and each unscoped + // enumerator is declared in the scope that immediately contains + // the enum-specifier. Each scoped enumerator is declared in the + // scope of the enumeration. + if (Ed->isScoped() || Ed->getIdentifier()) + Result += Ed->getNameAsString(); + else + continue; + } else { + Result += cast<NamedDecl>(DC)->getNameAsString(); + } + Result += "::"; + } + + if (D->getDeclName()) + Result += D->getNameAsString(); + else + Result += "(anonymous)"; + + return Result; + } + + std::string mangleLocation(SourceLocation Loc, + std::string Backup = std::string()) { + FileInfo *F = getFileInfo(Loc); + std::string Filename = F->Realname; + if (Filename.length() == 0 && Backup.length() != 0) { + return Backup; + } + if (F->Generated) { + // Since generated files may be different on different platforms, + // we need to include a platform-specific thing in the hash. Otherwise + // we can end up with hash collisions where different symbols from + // different platforms map to the same thing. + char* Platform = getenv("MOZSEARCH_PLATFORM"); + Filename = std::string(Platform ? Platform : "") + std::string("@") + Filename; + } + return hash(Filename + std::string("@") + locationToString(Loc)); + } + + bool isAcceptableSymbolChar(char c) { + return isalpha(c) || isdigit(c) || c == '_' || c == '/'; + } + + std::string mangleFile(std::string Filename, FileType Type) { + // "Mangle" the file path, such that: + // 1. The majority of paths will still be mostly human-readable. + // 2. The sanitization algorithm doesn't produce collisions where two + // different unsanitized paths can result in the same sanitized paths. + // 3. The produced symbol doesn't cause problems with downstream consumers. + // In order to accomplish this, we keep alphanumeric chars, underscores, + // and slashes, and replace everything else with an "@xx" hex encoding. + // The majority of path characters are letters and slashes which don't get + // encoded, so that satisfies (1). Since "@" characters in the unsanitized + // path get encoded, there should be no "@" characters in the sanitized path + // that got preserved from the unsanitized input, so that should satisfy (2). + // And (3) was done by trial-and-error. Note in particular the dot (.) + // character needs to be encoded, or the symbol-search feature of mozsearch + // doesn't work correctly, as all dot characters in the symbol query get + // replaced by #. + for (size_t i = 0; i < Filename.length(); i++) { + char c = Filename[i]; + if (isAcceptableSymbolChar(c)) { + continue; + } + char hex[4]; + sprintf(hex, "@%02X", ((int)c) & 0xFF); + Filename.replace(i, 1, hex); + i += 2; + } + + if (Type == FileType::Generated) { + // Since generated files may be different on different platforms, + // we need to include a platform-specific thing in the hash. Otherwise + // we can end up with hash collisions where different symbols from + // different platforms map to the same thing. + char* Platform = getenv("MOZSEARCH_PLATFORM"); + Filename = std::string(Platform ? Platform : "") + std::string("@") + Filename; + } + return Filename; + } + + std::string mangleQualifiedName(std::string Name) { + std::replace(Name.begin(), Name.end(), ' ', '_'); + return Name; + } + + std::string getMangledName(clang::MangleContext *Ctx, + const clang::NamedDecl *Decl) { + if (isa<FunctionDecl>(Decl) && cast<FunctionDecl>(Decl)->isExternC()) { + return cast<FunctionDecl>(Decl)->getNameAsString(); + } + + if (isa<FunctionDecl>(Decl) || isa<VarDecl>(Decl)) { + const DeclContext *DC = Decl->getDeclContext(); + if (isa<TranslationUnitDecl>(DC) || isa<NamespaceDecl>(DC) || + isa<LinkageSpecDecl>(DC) || + // isa<ExternCContextDecl>(DC) || + isa<TagDecl>(DC)) { + llvm::SmallVector<char, 512> Output; + llvm::raw_svector_ostream Out(Output); +#if CLANG_VERSION_MAJOR >= 11 + // This code changed upstream in version 11: + // https://github.com/llvm/llvm-project/commit/29e1a16be8216066d1ed733a763a749aed13ff47 + GlobalDecl GD; + if (const CXXConstructorDecl *D = dyn_cast<CXXConstructorDecl>(Decl)) { + GD = GlobalDecl(D, Ctor_Complete); + } else if (const CXXDestructorDecl *D = + dyn_cast<CXXDestructorDecl>(Decl)) { + GD = GlobalDecl(D, Dtor_Complete); + } else { + GD = GlobalDecl(Decl); + } + Ctx->mangleName(GD, Out); +#else + if (const CXXConstructorDecl *D = dyn_cast<CXXConstructorDecl>(Decl)) { + Ctx->mangleCXXCtor(D, CXXCtorType::Ctor_Complete, Out); + } else if (const CXXDestructorDecl *D = + dyn_cast<CXXDestructorDecl>(Decl)) { + Ctx->mangleCXXDtor(D, CXXDtorType::Dtor_Complete, Out); + } else { + Ctx->mangleName(Decl, Out); + } +#endif + return Out.str().str(); + } else { + return std::string("V_") + mangleLocation(Decl->getLocation()) + + std::string("_") + hash(std::string(Decl->getName())); + } + } else if (isa<TagDecl>(Decl) || isa<ObjCInterfaceDecl>(Decl)) { + if (!Decl->getIdentifier()) { + // Anonymous. + return std::string("T_") + mangleLocation(Decl->getLocation()); + } + + return std::string("T_") + mangleQualifiedName(getQualifiedName(Decl)); + } else if (isa<TypedefNameDecl>(Decl)) { + if (!Decl->getIdentifier()) { + // Anonymous. + return std::string("TA_") + mangleLocation(Decl->getLocation()); + } + + return std::string("TA_") + mangleQualifiedName(getQualifiedName(Decl)); + } else if (isa<NamespaceDecl>(Decl) || isa<NamespaceAliasDecl>(Decl)) { + if (!Decl->getIdentifier()) { + // Anonymous. + return std::string("NS_") + mangleLocation(Decl->getLocation()); + } + + return std::string("NS_") + mangleQualifiedName(getQualifiedName(Decl)); + } else if (const ObjCIvarDecl *D2 = dyn_cast<ObjCIvarDecl>(Decl)) { + const ObjCInterfaceDecl *Iface = D2->getContainingInterface(); + return std::string("F_<") + getMangledName(Ctx, Iface) + ">_" + + D2->getNameAsString(); + } else if (const FieldDecl *D2 = dyn_cast<FieldDecl>(Decl)) { + const RecordDecl *Record = D2->getParent(); + return std::string("F_<") + getMangledName(Ctx, Record) + ">_" + + D2->getNameAsString(); + } else if (const EnumConstantDecl *D2 = dyn_cast<EnumConstantDecl>(Decl)) { + const DeclContext *DC = Decl->getDeclContext(); + if (const NamedDecl *Named = dyn_cast<NamedDecl>(DC)) { + return std::string("E_<") + getMangledName(Ctx, Named) + ">_" + + D2->getNameAsString(); + } + } + + assert(false); + return std::string(""); + } + + void debugLocation(SourceLocation Loc) { + std::string S = locationToString(Loc); + StringRef Filename = SM.getFilename(Loc); + printf("--> %s %s\n", std::string(Filename).c_str(), S.c_str()); + } + + void debugRange(SourceRange Range) { + printf("Range\n"); + debugLocation(Range.getBegin()); + debugLocation(Range.getEnd()); + } + +public: + IndexConsumer(CompilerInstance &CI) + : CI(CI), SM(CI.getSourceManager()), LO(CI.getLangOpts()), CurMangleContext(nullptr), + AstContext(nullptr), CurDeclContext(nullptr), TemplateStack(nullptr) { + CI.getPreprocessor().addPPCallbacks( + make_unique<PreprocessorHook>(this)); + } + + virtual DiagnosticConsumer *clone(DiagnosticsEngine &Diags) const { + return new IndexConsumer(CI); + } + +#if !defined(_WIN32) && !defined(_WIN64) + struct AutoTime { + AutoTime(double *Counter) : Counter(Counter), Start(time()) {} + ~AutoTime() { + if (Start) { + *Counter += time() - Start; + } + } + void stop() { + *Counter += time() - Start; + Start = 0; + } + double *Counter; + double Start; + }; +#endif + + // All we need is to follow the final declaration. + virtual void HandleTranslationUnit(ASTContext &Ctx) { + CurMangleContext = + clang::ItaniumMangleContext::create(Ctx, CI.getDiagnostics()); + + AstContext = &Ctx; + Resolver = std::make_unique<clangd::HeuristicResolver>(Ctx); + TraverseDecl(Ctx.getTranslationUnitDecl()); + + // Emit the JSON data for all files now. + std::map<FileID, std::unique_ptr<FileInfo>>::iterator It; + for (It = FileMap.begin(); It != FileMap.end(); It++) { + if (!It->second->Interesting) { + continue; + } + + FileInfo &Info = *It->second; + + std::string Filename = Outdir + Info.Realname; + std::string SrcFilename = Info.Generated + ? Objdir + Info.Realname.substr(GENERATED.length()) + : Srcdir + PATHSEP_STRING + Info.Realname; + + ensurePath(Filename); + + // We lock the output file in case some other clang process is trying to + // write to it at the same time. + AutoLockFile Lock(SrcFilename, Filename); + + if (!Lock.success()) { + fprintf(stderr, "Unable to lock file %s\n", Filename.c_str()); + exit(1); + } + + // Merge our results with the existing lines from the output file. + // This ensures that header files that are included multiple times + // in different ways are analyzed completely. + std::ifstream Fin(Filename.c_str(), std::ios::in | std::ios::binary); + FILE *OutFp = Lock.openTmp(); + if (!OutFp) { + fprintf(stderr, "Unable to open tmp out file for %s\n", Filename.c_str()); + exit(1); + } + + // Sort our new results and get an iterator to them + std::sort(Info.Output.begin(), Info.Output.end()); + std::vector<std::string>::const_iterator NewLinesIter = Info.Output.begin(); + std::string LastNewWritten; + + // Loop over the existing (sorted) lines in the analysis output file. + // (The good() check also handles the case where Fin did not exist when we + // went to open it.) + while(Fin.good()) { + std::string OldLine; + std::getline(Fin, OldLine); + // Skip blank lines. + if (OldLine.length() == 0) { + continue; + } + // We need to put the newlines back that getline() eats. + OldLine.push_back('\n'); + + // Write any results from Info.Output that are lexicographically + // smaller than OldLine (read from the existing file), but make sure + // to skip duplicates. Keep advancing NewLinesIter until we reach an + // entry that is lexicographically greater than OldLine. + for (; NewLinesIter != Info.Output.end(); NewLinesIter++) { + if (*NewLinesIter > OldLine) { + break; + } + if (*NewLinesIter == OldLine) { + continue; + } + if (*NewLinesIter == LastNewWritten) { + // dedupe the new entries being written + continue; + } + if (fwrite(NewLinesIter->c_str(), NewLinesIter->length(), 1, OutFp) != 1) { + fprintf(stderr, "Unable to write %zu bytes[1] to tmp output file for %s\n", + NewLinesIter->length(), Filename.c_str()); + exit(1); + } + LastNewWritten = *NewLinesIter; + } + + // Write the entry read from the existing file. + if (fwrite(OldLine.c_str(), OldLine.length(), 1, OutFp) != 1) { + fprintf(stderr, "Unable to write %zu bytes[2] to tmp output file for %s\n", + OldLine.length(), Filename.c_str()); + exit(1); + } + } + + // We finished reading from Fin + Fin.close(); + + // Finish iterating our new results, discarding duplicates + for (; NewLinesIter != Info.Output.end(); NewLinesIter++) { + if (*NewLinesIter == LastNewWritten) { + continue; + } + if (fwrite(NewLinesIter->c_str(), NewLinesIter->length(), 1, OutFp) != 1) { + fprintf(stderr, "Unable to write %zu bytes[3] to tmp output file for %s\n", + NewLinesIter->length(), Filename.c_str()); + exit(1); + } + LastNewWritten = *NewLinesIter; + } + + // Done writing all the things, close it and replace the old output file + // with the new one. + fclose(OutFp); + if (!Lock.moveTmp()) { + fprintf(stderr, "Unable to move tmp output file into place for %s (err %d)\n", Filename.c_str(), errno); + exit(1); + } + } + } + + // Unfortunately, we have to override all these methods in order to track the + // context we're inside. + + bool TraverseEnumDecl(EnumDecl *D) { + AutoSetContext Asc(this, D); + return Super::TraverseEnumDecl(D); + } + bool TraverseRecordDecl(RecordDecl *D) { + AutoSetContext Asc(this, D); + return Super::TraverseRecordDecl(D); + } + bool TraverseCXXRecordDecl(CXXRecordDecl *D) { + AutoSetContext Asc(this, D); + return Super::TraverseCXXRecordDecl(D); + } + bool TraverseFunctionDecl(FunctionDecl *D) { + AutoSetContext Asc(this, D); + const FunctionDecl *Def; + // (See the larger AutoTemplateContext comment for more information.) If a + // method on a templated class is declared out-of-line, we need to analyze + // the definition inside the scope of the template or else we won't properly + // handle member access on the templated type. + if (TemplateStack && D->isDefined(Def) && Def && D != Def) { + TraverseFunctionDecl(const_cast<FunctionDecl *>(Def)); + } + return Super::TraverseFunctionDecl(D); + } + bool TraverseCXXMethodDecl(CXXMethodDecl *D) { + AutoSetContext Asc(this, D); + const FunctionDecl *Def; + // See TraverseFunctionDecl. + if (TemplateStack && D->isDefined(Def) && Def && D != Def) { + TraverseFunctionDecl(const_cast<FunctionDecl *>(Def)); + } + return Super::TraverseCXXMethodDecl(D); + } + bool TraverseCXXConstructorDecl(CXXConstructorDecl *D) { + AutoSetContext Asc(this, D, /*VisitImplicit=*/true); + const FunctionDecl *Def; + // See TraverseFunctionDecl. + if (TemplateStack && D->isDefined(Def) && Def && D != Def) { + TraverseFunctionDecl(const_cast<FunctionDecl *>(Def)); + } + return Super::TraverseCXXConstructorDecl(D); + } + bool TraverseCXXConversionDecl(CXXConversionDecl *D) { + AutoSetContext Asc(this, D); + const FunctionDecl *Def; + // See TraverseFunctionDecl. + if (TemplateStack && D->isDefined(Def) && Def && D != Def) { + TraverseFunctionDecl(const_cast<FunctionDecl *>(Def)); + } + return Super::TraverseCXXConversionDecl(D); + } + bool TraverseCXXDestructorDecl(CXXDestructorDecl *D) { + AutoSetContext Asc(this, D); + const FunctionDecl *Def; + // See TraverseFunctionDecl. + if (TemplateStack && D->isDefined(Def) && Def && D != Def) { + TraverseFunctionDecl(const_cast<FunctionDecl *>(Def)); + } + return Super::TraverseCXXDestructorDecl(D); + } + + // Used to keep track of the context in which a token appears. + struct Context { + // Ultimately this becomes the "context" JSON property. + std::string Name; + + // Ultimately this becomes the "contextsym" JSON property. + std::string Symbol; + + Context() {} + Context(std::string Name, std::string Symbol) + : Name(Name), Symbol(Symbol) {} + }; + + Context translateContext(NamedDecl *D) { + const FunctionDecl *F = dyn_cast<FunctionDecl>(D); + if (F && F->isTemplateInstantiation()) { + D = F->getTemplateInstantiationPattern(); + } + + return Context(D->getQualifiedNameAsString(), getMangledName(CurMangleContext, D)); + } + + Context getContext(SourceLocation Loc) { + if (SM.isMacroBodyExpansion(Loc)) { + // If we're inside a macro definition, we don't return any context. It + // will probably not be what the user expects if we do. + return Context(); + } + + if (CurDeclContext) { + return translateContext(CurDeclContext->Decl); + } + return Context(); + } + + // Similar to GetContext(SourceLocation), but it skips the declaration passed + // in. This is useful if we want the context of a declaration that's already + // on the stack. + Context getContext(Decl *D) { + if (SM.isMacroBodyExpansion(D->getLocation())) { + // If we're inside a macro definition, we don't return any context. It + // will probably not be what the user expects if we do. + return Context(); + } + + AutoSetContext *Ctxt = CurDeclContext; + while (Ctxt) { + if (Ctxt->Decl != D) { + return translateContext(Ctxt->Decl); + } + Ctxt = Ctxt->Prev; + } + return Context(); + } + + // Analyzing template code is tricky. Suppose we have this code: + // + // template<class T> + // bool Foo(T* ptr) { return T::StaticMethod(ptr); } + // + // If we analyze the body of Foo without knowing the type T, then we will not + // be able to generate any information for StaticMethod. However, analyzing + // Foo for every possible instantiation is inefficient and it also generates + // too much data in some cases. For example, the following code would generate + // one definition of Baz for every instantiation, which is undesirable: + // + // template<class T> + // class Bar { struct Baz { ... }; }; + // + // To solve this problem, we analyze templates only once. We do so in a + // GatherDependent mode where we look for "dependent scoped member + // expressions" (i.e., things like StaticMethod). We keep track of the + // locations of these expressions. If we find one or more of them, we analyze + // the template for each instantiation, in an AnalyzeDependent mode. This mode + // ignores all source locations except for the ones where we found dependent + // scoped member expressions before. For these locations, we generate a + // separate JSON result for each instantiation. + // + // We inherit our parent's mode if it is exists. This is because if our + // parent is in analyze mode, it means we've already lived a full life in + // gather mode and we must not restart in gather mode or we'll cause the + // indexer to visit EVERY identifier, which is way too much data. + struct AutoTemplateContext { + AutoTemplateContext(IndexConsumer *Self) + : Self(Self) + , CurMode(Self->TemplateStack ? Self->TemplateStack->CurMode : Mode::GatherDependent) + , Parent(Self->TemplateStack) { + Self->TemplateStack = this; + } + + ~AutoTemplateContext() { Self->TemplateStack = Parent; } + + // We traverse templates in two modes: + enum class Mode { + // Gather mode does not traverse into specializations. It looks for + // locations where it would help to have more info from template + // specializations. + GatherDependent, + + // Analyze mode traverses into template specializations and records + // information about token locations saved in gather mode. + AnalyzeDependent, + }; + + // We found a dependent scoped member expression! Keep track of it for + // later. + void visitDependent(SourceLocation Loc) { + if (CurMode == Mode::AnalyzeDependent) { + return; + } + + DependentLocations.insert(Loc.getRawEncoding()); + if (Parent) { + Parent->visitDependent(Loc); + } + } + + bool inGatherMode() { + return CurMode == Mode::GatherDependent; + } + + // Do we need to perform the extra AnalyzeDependent passes (one per + // instantiation)? + bool needsAnalysis() const { + if (!DependentLocations.empty()) { + return true; + } + if (Parent) { + return Parent->needsAnalysis(); + } + return false; + } + + void switchMode() { CurMode = Mode::AnalyzeDependent; } + + // Do we want to analyze each template instantiation separately? + bool shouldVisitTemplateInstantiations() const { + if (CurMode == Mode::AnalyzeDependent) { + return true; + } + if (Parent) { + return Parent->shouldVisitTemplateInstantiations(); + } + return false; + } + + // For a given expression/statement, should we emit JSON data for it? + bool shouldVisit(SourceLocation Loc) { + if (CurMode == Mode::GatherDependent) { + return true; + } + if (DependentLocations.find(Loc.getRawEncoding()) != + DependentLocations.end()) { + return true; + } + if (Parent) { + return Parent->shouldVisit(Loc); + } + return false; + } + + private: + IndexConsumer *Self; + Mode CurMode; + std::unordered_set<unsigned> DependentLocations; + AutoTemplateContext *Parent; + }; + + AutoTemplateContext *TemplateStack; + + bool shouldVisitTemplateInstantiations() const { + if (TemplateStack) { + return TemplateStack->shouldVisitTemplateInstantiations(); + } + return false; + } + + bool shouldVisitImplicitCode() const { + return CurDeclContext && CurDeclContext->VisitImplicit; + } + + bool TraverseClassTemplateDecl(ClassTemplateDecl *D) { + AutoTemplateContext Atc(this); + Super::TraverseClassTemplateDecl(D); + + if (!Atc.needsAnalysis()) { + return true; + } + + Atc.switchMode(); + + if (D != D->getCanonicalDecl()) { + return true; + } + + for (auto *Spec : D->specializations()) { + for (auto *Rd : Spec->redecls()) { + // We don't want to visit injected-class-names in this traversal. + if (cast<CXXRecordDecl>(Rd)->isInjectedClassName()) + continue; + + TraverseDecl(Rd); + } + } + + return true; + } + + bool TraverseFunctionTemplateDecl(FunctionTemplateDecl *D) { + AutoTemplateContext Atc(this); + if (Atc.inGatherMode()) { + Super::TraverseFunctionTemplateDecl(D); + } + + if (!Atc.needsAnalysis()) { + return true; + } + + Atc.switchMode(); + + if (D != D->getCanonicalDecl()) { + return true; + } + + for (auto *Spec : D->specializations()) { + for (auto *Rd : Spec->redecls()) { + TraverseDecl(Rd); + } + } + + return true; + } + + bool shouldVisit(SourceLocation Loc) { + if (TemplateStack) { + return TemplateStack->shouldVisit(Loc); + } + return true; + } + + enum { + // Flag to omit the identifier from being cross-referenced across files. + // This is usually desired for local variables. + NoCrossref = 1 << 0, + // Flag to indicate the token with analysis data is not an identifier. Indicates + // we want to skip the check that tries to ensure a sane identifier token. + NotIdentifierToken = 1 << 1, + // This indicates that the end of the provided SourceRange is valid and + // should be respected. If this flag is not set, the visitIdentifier + // function should use only the start of the SourceRange and auto-detect + // the end based on whatever token is found at the start. + LocRangeEndValid = 1 << 2 + }; + + void emitStructuredInfo(SourceLocation Loc, const RecordDecl *decl) { + std::string json_str; + llvm::raw_string_ostream ros(json_str); + llvm::json::OStream J(ros); + // Start the top-level object. + J.objectBegin(); + + unsigned StartOffset = SM.getFileOffset(Loc); + unsigned EndOffset = + StartOffset + Lexer::MeasureTokenLength(Loc, SM, CI.getLangOpts()); + J.attribute("loc", locationToString(Loc, EndOffset - StartOffset)); + J.attribute("structured", 1); + J.attribute("pretty", getQualifiedName(decl)); + J.attribute("sym", getMangledName(CurMangleContext, decl)); + + J.attribute("kind", TypeWithKeyword::getTagTypeKindName(decl->getTagKind())); + + const ASTContext &C = *AstContext; + const ASTRecordLayout &Layout = C.getASTRecordLayout(decl); + + J.attribute("sizeBytes", Layout.getSize().getQuantity()); + + emitBindingAttributes(J, *decl); + + auto cxxDecl = dyn_cast<CXXRecordDecl>(decl); + + if (cxxDecl) { + J.attributeBegin("supers"); + J.arrayBegin(); + for (const CXXBaseSpecifier &Base : cxxDecl->bases()) { + const CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl(); + + J.objectBegin(); + + J.attribute("sym", getMangledName(CurMangleContext, BaseDecl)); + + J.attributeBegin("props"); + J.arrayBegin(); + if (Base.isVirtual()) { + J.value("virtual"); + } + J.arrayEnd(); + J.attributeEnd(); + + J.objectEnd(); + } + J.arrayEnd(); + J.attributeEnd(); + + J.attributeBegin("methods"); + J.arrayBegin(); + for (const CXXMethodDecl *MethodDecl : cxxDecl->methods()) { + J.objectBegin(); + + J.attribute("pretty", getQualifiedName(MethodDecl)); + J.attribute("sym", getMangledName(CurMangleContext, MethodDecl)); + + // TODO: Better figure out what to do for non-isUserProvided methods + // which means there's potentially semantic data that doesn't correspond + // to a source location in the source. Should we be emitting + // structured info for those when we're processing the class here? + + J.attributeBegin("props"); + J.arrayBegin(); + if (MethodDecl->isStatic()) { + J.value("static"); + } + if (MethodDecl->isInstance()) { + J.value("instance"); + } + if (MethodDecl->isVirtual()) { + J.value("virtual"); + } + if (MethodDecl->isUserProvided()) { + J.value("user"); + } + if (MethodDecl->isDefaulted()) { + J.value("defaulted"); + } + if (MethodDecl->isDeleted()) { + J.value("deleted"); + } + if (MethodDecl->isConstexpr()) { + J.value("constexpr"); + } + J.arrayEnd(); + J.attributeEnd(); + + J.objectEnd(); + } + J.arrayEnd(); + J.attributeEnd(); + } + + J.attributeBegin("fields"); + J.arrayBegin(); + uint64_t iField = 0; + for (RecordDecl::field_iterator It = decl->field_begin(), + End = decl->field_end(); It != End; ++It, ++iField) { + const FieldDecl &Field = **It; + uint64_t localOffsetBits = Layout.getFieldOffset(iField); + CharUnits localOffsetBytes = C.toCharUnitsFromBits(localOffsetBits); + + J.objectBegin(); + J.attribute("pretty", getQualifiedName(&Field)); + J.attribute("sym", getMangledName(CurMangleContext, &Field)); + QualType FieldType = Field.getType(); + QualType CanonicalFieldType = FieldType.getCanonicalType(); + J.attribute("type", CanonicalFieldType.getAsString()); + const TagDecl *tagDecl = CanonicalFieldType->getAsTagDecl(); + if (!tagDecl) { + // Try again piercing any pointers/references involved. Note that our + // typesym semantics are dubious-ish and right now crossref just does + // some parsing of "type" itself until we improve this rep. + CanonicalFieldType = CanonicalFieldType->getPointeeType(); + if (!CanonicalFieldType.isNull()) { + tagDecl = CanonicalFieldType->getAsTagDecl(); + } + } + if (tagDecl) { + J.attribute("typesym", getMangledName(CurMangleContext, tagDecl)); + } + J.attribute("offsetBytes", localOffsetBytes.getQuantity()); + if (Field.isBitField()) { + J.attributeBegin("bitPositions"); + J.objectBegin(); + + J.attribute("begin", unsigned(localOffsetBits - C.toBits(localOffsetBytes))); + J.attribute("width", Field.getBitWidthValue(C)); + + J.objectEnd(); + J.attributeEnd(); + } else { + // Try and get the field as a record itself so we can know its size, but + // we don't actually want to recurse into it. + if (auto FieldRec = Field.getType()->getAs<RecordType>()) { + auto const &FieldLayout = C.getASTRecordLayout(FieldRec->getDecl()); + J.attribute("sizeBytes", FieldLayout.getSize().getQuantity()); + } else { + // We were unable to get it as a record, which suggests it's a normal + // type, in which case let's just ask for the type size. (Maybe this + // would also work for the above case too?) + uint64_t typeSizeBits = C.getTypeSize(Field.getType()); + CharUnits typeSizeBytes = C.toCharUnitsFromBits(typeSizeBits); + J.attribute("sizeBytes", typeSizeBytes.getQuantity()); + } + } + J.objectEnd(); + } + J.arrayEnd(); + J.attributeEnd(); + + // End the top-level object. + J.objectEnd(); + + FileInfo *F = getFileInfo(Loc); + // we want a newline. + ros << '\n'; + F->Output.push_back(std::move(ros.str())); + } + + void emitStructuredInfo(SourceLocation Loc, const FunctionDecl *decl) { + std::string json_str; + llvm::raw_string_ostream ros(json_str); + llvm::json::OStream J(ros); + // Start the top-level object. + J.objectBegin(); + + unsigned StartOffset = SM.getFileOffset(Loc); + unsigned EndOffset = + StartOffset + Lexer::MeasureTokenLength(Loc, SM, CI.getLangOpts()); + J.attribute("loc", locationToString(Loc, EndOffset - StartOffset)); + J.attribute("structured", 1); + J.attribute("pretty", getQualifiedName(decl)); + J.attribute("sym", getMangledName(CurMangleContext, decl)); + + emitBindingAttributes(J, *decl); + + J.attributeBegin("args"); + J.arrayBegin(); + + for (auto param : decl->parameters()) { + J.objectBegin(); + + J.attribute("name", param->getName()); + QualType ArgType = param->getOriginalType(); + J.attribute("type", ArgType.getAsString()); + + QualType CanonicalArgType = ArgType.getCanonicalType(); + const TagDecl *canonDecl = CanonicalArgType->getAsTagDecl(); + if (!canonDecl) { + // Try again piercing any pointers/references involved. Note that our + // typesym semantics are dubious-ish and right now crossref just does + // some parsing of "type" itself until we improve this rep. + CanonicalArgType = CanonicalArgType->getPointeeType(); + if (!CanonicalArgType.isNull()) { + canonDecl = CanonicalArgType->getAsTagDecl(); + } + } + if (canonDecl) { + J.attribute("typesym", getMangledName(CurMangleContext, canonDecl)); + } + + J.objectEnd(); + } + + J.arrayEnd(); + J.attributeEnd(); + + + auto cxxDecl = dyn_cast<CXXMethodDecl>(decl); + + if (cxxDecl) { + J.attribute("kind", "method"); + if (auto parentDecl = cxxDecl->getParent()) { + J.attribute("parentsym", getMangledName(CurMangleContext, parentDecl)); + } + + J.attributeBegin("overrides"); + J.arrayBegin(); + for (const CXXMethodDecl *MethodDecl : cxxDecl->overridden_methods()) { + J.objectBegin(); + + // TODO: Make sure we're doing template traversals appropriately... + // findOverriddenMethods (now removed) liked to do: + // if (Decl->isTemplateInstantiation()) { + // Decl = dyn_cast<CXXMethodDecl>(Decl->getTemplateInstantiationPattern()); + // } + // I think our pre-emptive dereferencing/avoidance of templates may + // protect us from this, but it needs more investigation. + + J.attribute("sym", getMangledName(CurMangleContext, MethodDecl)); + + J.objectEnd(); + } + J.arrayEnd(); + J.attributeEnd(); + + } else { + J.attribute("kind", "function"); + } + + // ## Props + J.attributeBegin("props"); + J.arrayBegin(); + // some of these are only possible on a CXXMethodDecl, but we want them all + // in the same array, so condition these first ones. + if (cxxDecl) { + if (cxxDecl->isStatic()) { + J.value("static"); + } + if (cxxDecl->isInstance()) { + J.value("instance"); + } + if (cxxDecl->isVirtual()) { + J.value("virtual"); + } + if (cxxDecl->isUserProvided()) { + J.value("user"); + } + } + if (decl->isDefaulted()) { + J.value("defaulted"); + } + if (decl->isDeleted()) { + J.value("deleted"); + } + if (decl->isConstexpr()) { + J.value("constexpr"); + } + J.arrayEnd(); + J.attributeEnd(); + + // End the top-level object. + J.objectEnd(); + + FileInfo *F = getFileInfo(Loc); + // we want a newline. + ros << '\n'; + F->Output.push_back(std::move(ros.str())); + } + + /** + * Emit structured info for a field. Right now the intent is for this to just + * be a pointer to its parent's structured info with this method entirely + * avoiding getting the ASTRecordLayout. + * + * TODO: Give more thought on where to locate the canonical info on fields and + * how to normalize their exposure over the web. We could relink the info + * both at cross-reference time and web-server lookup time. This is also + * called out in `analysis.md`. + */ + void emitStructuredInfo(SourceLocation Loc, const FieldDecl *decl) { + // XXX the call to decl::getParent will assert below for ObjCIvarDecl + // instances because their DecContext is not a RecordDecl. So just bail + // for now. + // TODO: better support ObjC. + if (const ObjCIvarDecl *D2 = dyn_cast<ObjCIvarDecl>(decl)) { + return; + } + + std::string json_str; + llvm::raw_string_ostream ros(json_str); + llvm::json::OStream J(ros); + // Start the top-level object. + J.objectBegin(); + + unsigned StartOffset = SM.getFileOffset(Loc); + unsigned EndOffset = + StartOffset + Lexer::MeasureTokenLength(Loc, SM, CI.getLangOpts()); + J.attribute("loc", locationToString(Loc, EndOffset - StartOffset)); + J.attribute("structured", 1); + J.attribute("pretty", getQualifiedName(decl)); + J.attribute("sym", getMangledName(CurMangleContext, decl)); + J.attribute("kind", "field"); + + if (auto parentDecl = decl->getParent()) { + J.attribute("parentsym", getMangledName(CurMangleContext, parentDecl)); + } + + // End the top-level object. + J.objectEnd(); + + FileInfo *F = getFileInfo(Loc); + // we want a newline. + ros << '\n'; + F->Output.push_back(std::move(ros.str())); + } + + /** + * Emit structured info for a variable if it is a static class member. + */ + void emitStructuredInfo(SourceLocation Loc, const VarDecl *decl) { + const auto *parentDecl = dyn_cast_or_null<RecordDecl>(decl->getDeclContext()); + + std::string json_str; + llvm::raw_string_ostream ros(json_str); + llvm::json::OStream J(ros); + // Start the top-level object. + J.objectBegin(); + + unsigned StartOffset = SM.getFileOffset(Loc); + unsigned EndOffset = + StartOffset + Lexer::MeasureTokenLength(Loc, SM, CI.getLangOpts()); + J.attribute("loc", locationToString(Loc, EndOffset - StartOffset)); + J.attribute("structured", 1); + J.attribute("pretty", getQualifiedName(decl)); + J.attribute("sym", getMangledName(CurMangleContext, decl)); + J.attribute("kind", "field"); + + if (parentDecl) { + J.attribute("parentsym", getMangledName(CurMangleContext, parentDecl)); + } + + emitBindingAttributes(J, *decl); + + // End the top-level object. + J.objectEnd(); + + FileInfo *F = getFileInfo(Loc); + // we want a newline. + ros << '\n'; + F->Output.push_back(std::move(ros.str())); + } + + // XXX Type annotating. + // QualType is the type class. It has helpers like TagDecl via getAsTagDecl. + // ValueDecl exposes a getType() method. + // + // Arguably it makes sense to only expose types that Searchfox has definitions + // for as first-class. Probably the way to go is like context/contextsym. + // We expose a "type" which is just a human-readable string which has no + // semantic purposes and is just a display string, plus then a "typesym" which + // we expose if we were able to map the type. + // + // Other meta-info: field offsets. Ancestor types. + + // This is the only function that emits analysis JSON data. It should be + // called for each identifier that corresponds to a symbol. + void visitIdentifier(const char *Kind, const char *SyntaxKind, + llvm::StringRef QualName, SourceRange LocRange, + std::string Symbol, + QualType MaybeType = QualType(), + Context TokenContext = Context(), int Flags = 0, + SourceRange PeekRange = SourceRange(), + SourceRange NestingRange = SourceRange(), + std::vector<SourceRange> *ArgRanges = nullptr) { + SourceLocation Loc = LocRange.getBegin(); + if (!shouldVisit(Loc)) { + return; + } + + // Find the file positions corresponding to the token. + unsigned StartOffset = SM.getFileOffset(Loc); + unsigned EndOffset = (Flags & LocRangeEndValid) + ? SM.getFileOffset(LocRange.getEnd()) + : StartOffset + Lexer::MeasureTokenLength(Loc, SM, CI.getLangOpts()); + + std::string LocStr = locationToString(Loc, EndOffset - StartOffset); + std::string RangeStr = locationToString(Loc, EndOffset - StartOffset); + std::string PeekRangeStr; + + if (!(Flags & NotIdentifierToken)) { + // Get the token's characters so we can make sure it's a valid token. + const char *StartChars = SM.getCharacterData(Loc); + std::string Text(StartChars, EndOffset - StartOffset); + if (!isValidIdentifier(Text)) { + return; + } + } + + FileInfo *F = getFileInfo(Loc); + + if (!(Flags & NoCrossref)) { + std::string json_str; + llvm::raw_string_ostream ros(json_str); + llvm::json::OStream J(ros); + // Start the top-level object. + J.objectBegin(); + + J.attribute("loc", LocStr); + J.attribute("target", 1); + J.attribute("kind", Kind); + J.attribute("pretty", QualName.data()); + J.attribute("sym", Symbol); + if (!TokenContext.Name.empty()) { + J.attribute("context", TokenContext.Name); + } + if (!TokenContext.Symbol.empty()) { + J.attribute("contextsym", TokenContext.Symbol); + } + if (PeekRange.isValid()) { + PeekRangeStr = lineRangeToString(PeekRange); + if (!PeekRangeStr.empty()) { + J.attribute("peekRange", PeekRangeStr); + } + } + + if (ArgRanges) { + J.attributeBegin("argRanges"); + J.arrayBegin(); + + for (auto range : *ArgRanges) { + std::string ArgRangeStr = fullRangeToString(range); + if (!ArgRangeStr.empty()) { + J.value(ArgRangeStr); + } + } + + J.arrayEnd(); + J.attributeEnd(); + } + + // End the top-level object. + J.objectEnd(); + // we want a newline. + ros << '\n'; + F->Output.push_back(std::move(ros.str())); + } + + // Generate a single "source":1 for all the symbols. If we search from here, + // we want to union the results for every symbol in `symbols`. + std::string json_str; + llvm::raw_string_ostream ros(json_str); + llvm::json::OStream J(ros); + // Start the top-level object. + J.objectBegin(); + + J.attribute("loc", RangeStr); + J.attribute("source", 1); + + if (NestingRange.isValid()) { + std::string NestingRangeStr = fullRangeToString(NestingRange); + if (!NestingRangeStr.empty()) { + J.attribute("nestingRange", NestingRangeStr); + } + } + + std::string Syntax; + if (Flags & NoCrossref) { + J.attribute("syntax", ""); + } else { + Syntax = Kind; + Syntax.push_back(','); + Syntax.append(SyntaxKind); + J.attribute("syntax", Syntax); + } + + if (!MaybeType.isNull()) { + J.attribute("type", MaybeType.getAsString()); + QualType canonical = MaybeType.getCanonicalType(); + const TagDecl *decl = canonical->getAsTagDecl(); + if (!decl) { + // Try again piercing any pointers/references involved. Note that our + // typesym semantics are dubious-ish and right now crossref just does + // some parsing of "type" itself until we improve this rep. + canonical = canonical->getPointeeType(); + if (!canonical.isNull()) { + decl = canonical->getAsTagDecl(); + } + } + if (decl) { + std::string Mangled = getMangledName(CurMangleContext, decl); + J.attribute("typesym", Mangled); + } + } + + std::string Pretty(SyntaxKind); + Pretty.push_back(' '); + Pretty.append(QualName.data()); + J.attribute("pretty", Pretty); + + J.attribute("sym", Symbol); + + if (Flags & NoCrossref) { + J.attribute("no_crossref", 1); + } + + if (ArgRanges) { + J.attributeBegin("argRanges"); + J.arrayBegin(); + + for (auto range : *ArgRanges) { + std::string ArgRangeStr = fullRangeToString(range); + if (!ArgRangeStr.empty()) { + J.value(ArgRangeStr); + } + } + + J.arrayEnd(); + J.attributeEnd(); + } + + // End the top-level object. + J.objectEnd(); + + // we want a newline. + ros << '\n'; + F->Output.push_back(std::move(ros.str())); + } + + void normalizeLocation(SourceLocation *Loc) { + *Loc = SM.getSpellingLoc(*Loc); + } + + // For cases where the left-brace is not directly accessible from the AST, + // helper to use the lexer to find the brace. Make sure you're picking the + // start location appropriately! + SourceLocation findLeftBraceFromLoc(SourceLocation Loc) { + return Lexer::findLocationAfterToken(Loc, tok::l_brace, SM, LO, false); + } + + // If the provided statement is compound, return its range. + SourceRange getCompoundStmtRange(Stmt* D) { + if (!D) { + return SourceRange(); + } + + CompoundStmt *D2 = dyn_cast<CompoundStmt>(D); + if (D2) { + return D2->getSourceRange(); + } + + return SourceRange(); + } + + SourceRange getFunctionPeekRange(FunctionDecl* D) { + // We always start at the start of the function decl, which may include the + // return type on a separate line. + SourceLocation Start = D->getBeginLoc(); + + // By default, we end at the line containing the function's name. + SourceLocation End = D->getLocation(); + + std::pair<FileID, unsigned> FuncLoc = SM.getDecomposedLoc(End); + + // But if there are parameters, we want to include those as well. + for (ParmVarDecl* Param : D->parameters()) { + std::pair<FileID, unsigned> ParamLoc = SM.getDecomposedLoc(Param->getLocation()); + + // It's possible there are macros involved or something. We don't include + // the parameters in that case. + if (ParamLoc.first == FuncLoc.first) { + // Assume parameters are in order, so we always take the last one. + End = Param->getEndLoc(); + } + } + + return SourceRange(Start, End); + } + + SourceRange getTagPeekRange(TagDecl* D) { + SourceLocation Start = D->getBeginLoc(); + + // By default, we end at the line containing the name. + SourceLocation End = D->getLocation(); + + std::pair<FileID, unsigned> FuncLoc = SM.getDecomposedLoc(End); + + if (CXXRecordDecl* D2 = dyn_cast<CXXRecordDecl>(D)) { + // But if there are parameters, we want to include those as well. + for (CXXBaseSpecifier& Base : D2->bases()) { + std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Base.getEndLoc()); + + // It's possible there are macros involved or something. We don't include + // the parameters in that case. + if (Loc.first == FuncLoc.first) { + // Assume parameters are in order, so we always take the last one. + End = Base.getEndLoc(); + } + } + } + + return SourceRange(Start, End); + } + + SourceRange getCommentRange(NamedDecl* D) { + const RawComment* RC = + AstContext->getRawCommentForDeclNoCache(D); + if (!RC) { + return SourceRange(); + } + + return RC->getSourceRange(); + } + + // Sanity checks that all ranges are in the same file, returning the first if + // they're in different files. Unions the ranges based on which is first. + SourceRange combineRanges(SourceRange Range1, SourceRange Range2) { + if (Range1.isInvalid()) { + return Range2; + } + if (Range2.isInvalid()) { + return Range1; + } + + std::pair<FileID, unsigned> Begin1 = SM.getDecomposedLoc(Range1.getBegin()); + std::pair<FileID, unsigned> End1 = SM.getDecomposedLoc(Range1.getEnd()); + std::pair<FileID, unsigned> Begin2 = SM.getDecomposedLoc(Range2.getBegin()); + std::pair<FileID, unsigned> End2 = SM.getDecomposedLoc(Range2.getEnd()); + + if (End1.first != Begin2.first) { + // Something weird is probably happening with the preprocessor. Just + // return the first range. + return Range1; + } + + // See which range comes first. + if (Begin1.second <= End2.second) { + return SourceRange(Range1.getBegin(), Range2.getEnd()); + } else { + return SourceRange(Range2.getBegin(), Range1.getEnd()); + } + } + + // Given a location and a range, returns the range if: + // - The location and the range live in the same file. + // - The range is well ordered (end is not before begin). + // Returns an empty range otherwise. + SourceRange validateRange(SourceLocation Loc, SourceRange Range) { + std::pair<FileID, unsigned> Decomposed = SM.getDecomposedLoc(Loc); + std::pair<FileID, unsigned> Begin = SM.getDecomposedLoc(Range.getBegin()); + std::pair<FileID, unsigned> End = SM.getDecomposedLoc(Range.getEnd()); + + if (Begin.first != Decomposed.first || End.first != Decomposed.first) { + return SourceRange(); + } + + if (Begin.second >= End.second) { + return SourceRange(); + } + + return Range; + } + + bool VisitNamedDecl(NamedDecl *D) { + SourceLocation Loc = D->getLocation(); + + // If the token is from a macro expansion and the expansion location + // is interesting, use that instead as it tends to be more useful. + SourceLocation expandedLoc = Loc; + if (SM.isMacroBodyExpansion(Loc)) { + Loc = SM.getFileLoc(Loc); + } + + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + if (isa<ParmVarDecl>(D) && !D->getDeclName().getAsIdentifierInfo()) { + // Unnamed parameter in function proto. + return true; + } + + int Flags = 0; + const char *Kind = "def"; + const char *PrettyKind = "?"; + bool wasTemplate = false; + SourceRange PeekRange(D->getBeginLoc(), D->getEndLoc()); + // The nesting range identifies the left brace and right brace, which + // heavily depends on the AST node type. + SourceRange NestingRange; + QualType qtype = QualType(); + if (FunctionDecl *D2 = dyn_cast<FunctionDecl>(D)) { + if (D2->isTemplateInstantiation()) { + wasTemplate = true; + D = D2->getTemplateInstantiationPattern(); + } + // We treat pure virtual declarations as definitions. + Kind = (D2->isThisDeclarationADefinition() || D2->isPure()) ? "def" : "decl"; + PrettyKind = "function"; + PeekRange = getFunctionPeekRange(D2); + + // Only emit the nesting range if: + // - This is a definition AND + // - This isn't a template instantiation. Function templates' + // instantiations can end up as a definition with a Loc at their point + // of declaration but with the CompoundStmt of the template's + // point of definition. This really messes up the nesting range logic. + // At the time of writing this, the test repo's `big_header.h`'s + // `WhatsYourVector_impl::forwardDeclaredTemplateThingInlinedBelow` as + // instantiated by `big_cpp.cpp` triggers this phenomenon. + // + // Note: As covered elsewhere, template processing is tricky and it's + // conceivable that we may change traversal patterns in the future, + // mooting this guard. + if (D2->isThisDeclarationADefinition() && + !D2->isTemplateInstantiation()) { + // The CompoundStmt range is the brace range. + NestingRange = getCompoundStmtRange(D2->getBody()); + } + } else if (TagDecl *D2 = dyn_cast<TagDecl>(D)) { + Kind = D2->isThisDeclarationADefinition() ? "def" : "forward"; + PrettyKind = "type"; + + if (D2->isThisDeclarationADefinition() && D2->getDefinition() == D2) { + PeekRange = getTagPeekRange(D2); + NestingRange = D2->getBraceRange(); + } else { + PeekRange = SourceRange(); + } + } else if (TypedefNameDecl *D2 = dyn_cast<TypedefNameDecl>(D)) { + Kind = "alias"; + PrettyKind = "type"; + PeekRange = SourceRange(Loc, Loc); + qtype = D2->getUnderlyingType(); + } else if (VarDecl *D2 = dyn_cast<VarDecl>(D)) { + if (D2->isLocalVarDeclOrParm()) { + Flags = NoCrossref; + } + + Kind = D2->isThisDeclarationADefinition() == VarDecl::DeclarationOnly + ? "decl" + : "def"; + PrettyKind = "variable"; + } else if (isa<NamespaceDecl>(D) || isa<NamespaceAliasDecl>(D)) { + Kind = "def"; + PrettyKind = "namespace"; + PeekRange = SourceRange(Loc, Loc); + NamespaceDecl *D2 = dyn_cast<NamespaceDecl>(D); + if (D2) { + // There's no exposure of the left brace so we have to find it. + NestingRange = SourceRange( + findLeftBraceFromLoc(D2->isAnonymousNamespace() ? D2->getBeginLoc() : Loc), + D2->getRBraceLoc()); + } + } else if (isa<FieldDecl>(D)) { + Kind = "def"; + PrettyKind = "field"; + } else if (isa<EnumConstantDecl>(D)) { + Kind = "def"; + PrettyKind = "enum constant"; + } else { + return true; + } + + if (ValueDecl *D2 = dyn_cast<ValueDecl>(D)) { + qtype = D2->getType(); + } + + SourceRange CommentRange = getCommentRange(D); + PeekRange = combineRanges(PeekRange, CommentRange); + PeekRange = validateRange(Loc, PeekRange); + NestingRange = validateRange(Loc, NestingRange); + + std::string Symbol = getMangledName(CurMangleContext, D); + + // In the case of destructors, Loc might point to the ~ character. In that + // case we want to skip to the name of the class. However, Loc might also + // point to other places that generate destructors, such as the use site of + // a macro that expands to generate a destructor, or a lambda (apparently + // clang 8 creates a destructor declaration for at least some lambdas). In + // the former case we'll use the macro use site as the location, and in the + // latter we'll just drop the declaration. + if (isa<CXXDestructorDecl>(D)) { + PrettyKind = "destructor"; + const char *P = SM.getCharacterData(Loc); + if (*P == '~') { + // Advance Loc to the class name + P++; + + unsigned Skipped = 1; + while (*P == ' ' || *P == '\t' || *P == '\r' || *P == '\n') { + P++; + Skipped++; + } + + Loc = Loc.getLocWithOffset(Skipped); + } else { + // See if the destructor is coming from a macro expansion + P = SM.getCharacterData(expandedLoc); + if (*P != '~') { + // It's not + return true; + } + // It is, so just use Loc as-is + } + } + + visitIdentifier(Kind, PrettyKind, getQualifiedName(D), SourceRange(Loc), Symbol, + qtype, + getContext(D), Flags, PeekRange, NestingRange); + + // In-progress structured info emission. + if (RecordDecl *D2 = dyn_cast<RecordDecl>(D)) { + if (D2->isThisDeclarationADefinition() && + // XXX getASTRecordLayout doesn't work for dependent types, so we + // avoid calling into emitStructuredInfo for now if there's a + // dependent type or if we're in any kind of template context. This + // should be re-evaluated once this is working for normal classes and + // we can better evaluate what is useful. + !D2->isDependentType() && + !TemplateStack) { + if (auto *D3 = dyn_cast<CXXRecordDecl>(D2)) { + findBindingToJavaClass(*AstContext, *D3); + findBoundAsJavaClasses(*AstContext, *D3); + } + emitStructuredInfo(Loc, D2); + } + } + if (FunctionDecl *D2 = dyn_cast<FunctionDecl>(D)) { + if ((D2->isThisDeclarationADefinition() || D2->isPure()) && + // a clause at the top should have generalized and set wasTemplate so + // it shouldn't be the case that isTemplateInstantiation() is true. + !D2->isTemplateInstantiation() && + !wasTemplate && + !D2->isFunctionTemplateSpecialization() && + !TemplateStack) { + if (auto *D3 = dyn_cast<CXXMethodDecl>(D2)) { + findBindingToJavaMember(*AstContext, *D3); + } else { + findBindingToJavaFunction(*AstContext, *D2); + } + emitStructuredInfo(Loc, D2); + } + } + if (FieldDecl *D2 = dyn_cast<FieldDecl>(D)) { + if (!D2->isTemplated() && + !TemplateStack) { + emitStructuredInfo(Loc, D2); + } + } + if (VarDecl *D2 = dyn_cast<VarDecl>(D)) { + if (!D2->isTemplated() && + !TemplateStack && + isa<CXXRecordDecl>(D2->getDeclContext())) { + findBindingToJavaConstant(*AstContext, *D2); + emitStructuredInfo(Loc, D2); + } + } + + return true; + } + + bool VisitCXXConstructExpr(CXXConstructExpr *E) { + SourceLocation Loc = E->getBeginLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + FunctionDecl *Ctor = E->getConstructor(); + if (Ctor->isTemplateInstantiation()) { + Ctor = Ctor->getTemplateInstantiationPattern(); + } + std::string Mangled = getMangledName(CurMangleContext, Ctor); + + // FIXME: Need to do something different for list initialization. + + visitIdentifier("use", "constructor", getQualifiedName(Ctor), Loc, Mangled, + QualType(), getContext(Loc)); + + return true; + } + + bool VisitCallExpr(CallExpr *E) { + Decl *Callee = E->getCalleeDecl(); + if (!Callee || !FunctionDecl::classof(Callee)) { + return true; + } + + const NamedDecl *NamedCallee = dyn_cast<NamedDecl>(Callee); + + SourceLocation Loc; + + const FunctionDecl *F = dyn_cast<FunctionDecl>(NamedCallee); + if (F->isTemplateInstantiation()) { + NamedCallee = F->getTemplateInstantiationPattern(); + } + + std::string Mangled = getMangledName(CurMangleContext, NamedCallee); + int Flags = 0; + + Expr *CalleeExpr = E->getCallee()->IgnoreParenImpCasts(); + + if (CXXOperatorCallExpr::classof(E)) { + // Just take the first token. + CXXOperatorCallExpr *Op = dyn_cast<CXXOperatorCallExpr>(E); + Loc = Op->getOperatorLoc(); + Flags |= NotIdentifierToken; + } else if (MemberExpr::classof(CalleeExpr)) { + MemberExpr *Member = dyn_cast<MemberExpr>(CalleeExpr); + Loc = Member->getMemberLoc(); + } else if (DeclRefExpr::classof(CalleeExpr)) { + // We handle this in VisitDeclRefExpr. + return true; + } else { + return true; + } + + normalizeLocation(&Loc); + + if (!isInterestingLocation(Loc)) { + return true; + } + + std::vector<SourceRange> argRanges; + for (auto argExpr : E->arguments()) { + argRanges.push_back(argExpr->getSourceRange()); + } + + visitIdentifier("use", "function", getQualifiedName(NamedCallee), Loc, Mangled, + E->getCallReturnType(*AstContext), getContext(Loc), Flags, + SourceRange(), SourceRange(), &argRanges); + + return true; + } + + bool VisitTagTypeLoc(TagTypeLoc L) { + SourceLocation Loc = L.getBeginLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + TagDecl *Decl = L.getDecl(); + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "type", getQualifiedName(Decl), Loc, Mangled, + L.getType(), getContext(Loc)); + return true; + } + + bool VisitTypedefTypeLoc(TypedefTypeLoc L) { + SourceLocation Loc = L.getBeginLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + NamedDecl *Decl = L.getTypedefNameDecl(); + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "type", getQualifiedName(Decl), Loc, Mangled, + L.getType(), getContext(Loc)); + return true; + } + + bool VisitInjectedClassNameTypeLoc(InjectedClassNameTypeLoc L) { + SourceLocation Loc = L.getBeginLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + NamedDecl *Decl = L.getDecl(); + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "type", getQualifiedName(Decl), Loc, Mangled, + L.getType(), getContext(Loc)); + return true; + } + + bool VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc L) { + SourceLocation Loc = L.getBeginLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + TemplateDecl *Td = L.getTypePtr()->getTemplateName().getAsTemplateDecl(); + if (ClassTemplateDecl *D = dyn_cast<ClassTemplateDecl>(Td)) { + NamedDecl *Decl = D->getTemplatedDecl(); + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "type", getQualifiedName(Decl), Loc, Mangled, + QualType(), getContext(Loc)); + } else if (TypeAliasTemplateDecl *D = dyn_cast<TypeAliasTemplateDecl>(Td)) { + NamedDecl *Decl = D->getTemplatedDecl(); + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "type", getQualifiedName(Decl), Loc, Mangled, + QualType(), getContext(Loc)); + } + + return true; + } + + bool VisitDependentNameTypeLoc(DependentNameTypeLoc L) { + SourceLocation Loc = L.getNameLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + for (const NamedDecl *D : + Resolver->resolveDependentNameType(L.getTypePtr())) { + visitHeuristicResult(Loc, D); + } + return true; + } + + bool VisitDeclRefExpr(DeclRefExpr *E) { + SourceLocation Loc = E->getExprLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + if (E->hasQualifier()) { + Loc = E->getNameInfo().getLoc(); + normalizeLocation(&Loc); + } + + NamedDecl *Decl = E->getDecl(); + if (const VarDecl *D2 = dyn_cast<VarDecl>(Decl)) { + int Flags = 0; + if (D2->isLocalVarDeclOrParm()) { + Flags = NoCrossref; + } + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "variable", getQualifiedName(Decl), Loc, Mangled, + D2->getType(), getContext(Loc), Flags); + } else if (isa<FunctionDecl>(Decl)) { + const FunctionDecl *F = dyn_cast<FunctionDecl>(Decl); + if (F->isTemplateInstantiation()) { + Decl = F->getTemplateInstantiationPattern(); + } + + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "function", getQualifiedName(Decl), Loc, Mangled, + E->getType(), getContext(Loc)); + } else if (isa<EnumConstantDecl>(Decl)) { + std::string Mangled = getMangledName(CurMangleContext, Decl); + visitIdentifier("use", "enum", getQualifiedName(Decl), Loc, Mangled, + E->getType(), getContext(Loc)); + } + + return true; + } + + bool VisitCXXConstructorDecl(CXXConstructorDecl *D) { + if (!isInterestingLocation(D->getLocation())) { + return true; + } + + for (CXXConstructorDecl::init_const_iterator It = D->init_begin(); + It != D->init_end(); ++It) { + const CXXCtorInitializer *Ci = *It; + if (!Ci->getMember() || !Ci->isWritten()) { + continue; + } + + SourceLocation Loc = Ci->getMemberLocation(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + continue; + } + + FieldDecl *Member = Ci->getMember(); + std::string Mangled = getMangledName(CurMangleContext, Member); + visitIdentifier("use", "field", getQualifiedName(Member), Loc, Mangled, + Member->getType(), getContext(D)); + } + + return true; + } + + bool VisitMemberExpr(MemberExpr *E) { + SourceLocation Loc = E->getExprLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + ValueDecl *Decl = E->getMemberDecl(); + if (FieldDecl *Field = dyn_cast<FieldDecl>(Decl)) { + std::string Mangled = getMangledName(CurMangleContext, Field); + visitIdentifier("use", "field", getQualifiedName(Field), Loc, Mangled, + Field->getType(), getContext(Loc)); + } + return true; + } + + // Helper function for producing heuristic results for usages in dependent + // code. These should be distinguished from concrete results (obtained for + // dependent code using the AutoTemplateContext machinery) once bug 1833552 is + // fixed. + // We don't expect this method to be intentionally called multiple times for + // a given (Loc, NamedDecl) pair because our callers should be mutually + // exclusive AST node types. However, it's fine if this method is called + // multiple time for a given pair because we explicitly de-duplicate records + // with an identical string representation (which is a good reason to have + // this helper, as it ensures identical representations). + void visitHeuristicResult(SourceLocation Loc, const NamedDecl *ND) { + if (const TemplateDecl *TD = dyn_cast<TemplateDecl>(ND)) { + ND = TD->getTemplatedDecl(); + } + QualType MaybeType; + const char *SyntaxKind = nullptr; + if (const FunctionDecl *F = dyn_cast<FunctionDecl>(ND)) { + MaybeType = F->getType(); + SyntaxKind = "function"; + } else if (const FieldDecl *F = dyn_cast<FieldDecl>(ND)) { + MaybeType = F->getType(); + SyntaxKind = "field"; + } else if (const EnumConstantDecl *E = dyn_cast<EnumConstantDecl>(ND)) { + MaybeType = E->getType(); + SyntaxKind = "enum"; + } else if (const TypedefNameDecl *T = dyn_cast<TypedefNameDecl>(ND)) { + MaybeType = T->getUnderlyingType(); + SyntaxKind = "type"; + } + if (SyntaxKind) { + std::string Mangled = getMangledName(CurMangleContext, ND); + visitIdentifier("use", SyntaxKind, getQualifiedName(ND), Loc, Mangled, + MaybeType, getContext(Loc)); + } + } + + bool VisitOverloadExpr(OverloadExpr *E) { + SourceLocation Loc = E->getExprLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + for (auto *Candidate : E->decls()) { + visitHeuristicResult(Loc, Candidate); + } + return true; + } + + bool VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *E) { + SourceLocation Loc = E->getMemberLoc(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + // If possible, provide a heuristic result without instantiation. + for (const NamedDecl *D : Resolver->resolveMemberExpr(E)) { + visitHeuristicResult(Loc, D); + } + + // Also record this location so that if we have instantiations, we can + // gather more accurate results from them. + if (TemplateStack) { + TemplateStack->visitDependent(Loc); + } + return true; + } + + bool VisitDependentScopeDeclRefExpr(DependentScopeDeclRefExpr *E) { + SourceLocation Loc = E->getLocation(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return true; + } + + for (const NamedDecl *D : Resolver->resolveDeclRefExpr(E)) { + visitHeuristicResult(Loc, D); + } + return true; + } + + void enterSourceFile(SourceLocation Loc) { + normalizeLocation(&Loc); + FileInfo* newFile = getFileInfo(Loc); + if (!newFile->Interesting) { + return; + } + FileType type = newFile->Generated ? FileType::Generated : FileType::Source; + std::string symbol = + std::string("FILE_") + mangleFile(newFile->Realname, type); + + // We use an explicit zero-length source range at the start of the file. If we + // don't set the LocRangeEndValid flag, the visitIdentifier code will use the + // entire first token, which could be e.g. a long multiline-comment. + visitIdentifier("def", "file", newFile->Realname, SourceRange(Loc), + symbol, QualType(), Context(), + NotIdentifierToken | LocRangeEndValid); + } + + void inclusionDirective(SourceRange FileNameRange, const FileEntry* File) { + std::string includedFile(File->tryGetRealPathName()); + FileType type = relativizePath(includedFile); + if (type == FileType::Unknown) { + return; + } + std::string symbol = + std::string("FILE_") + mangleFile(includedFile, type); + + visitIdentifier("use", "file", includedFile, FileNameRange, symbol, + QualType(), Context(), + NotIdentifierToken | LocRangeEndValid); + } + + void macroDefined(const Token &Tok, const MacroDirective *Macro) { + if (Macro->getMacroInfo()->isBuiltinMacro()) { + return; + } + SourceLocation Loc = Tok.getLocation(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return; + } + + IdentifierInfo *Ident = Tok.getIdentifierInfo(); + if (Ident) { + std::string Mangled = + std::string("M_") + mangleLocation(Loc, std::string(Ident->getName())); + visitIdentifier("def", "macro", Ident->getName(), Loc, Mangled); + } + } + + void macroUsed(const Token &Tok, const MacroInfo *Macro) { + if (!Macro) { + return; + } + if (Macro->isBuiltinMacro()) { + return; + } + SourceLocation Loc = Tok.getLocation(); + normalizeLocation(&Loc); + if (!isInterestingLocation(Loc)) { + return; + } + + IdentifierInfo *Ident = Tok.getIdentifierInfo(); + if (Ident) { + std::string Mangled = + std::string("M_") + + mangleLocation(Macro->getDefinitionLoc(), std::string(Ident->getName())); + visitIdentifier("use", "macro", Ident->getName(), Loc, Mangled); + } + } +}; + +void PreprocessorHook::FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID = FileID()) { + switch (Reason) { + case PPCallbacks::RenameFile: + case PPCallbacks::SystemHeaderPragma: + // Don't care about these, since we want the actual on-disk filenames + break; + case PPCallbacks::EnterFile: + Indexer->enterSourceFile(Loc); + break; + case PPCallbacks::ExitFile: + // Don't care about exiting files + break; + } +} + +void PreprocessorHook::InclusionDirective(SourceLocation HashLoc, + const Token &IncludeTok, + StringRef FileName, + bool IsAngled, + CharSourceRange FileNameRange, +#if CLANG_VERSION_MAJOR >= 16 + OptionalFileEntryRef File, +#elif CLANG_VERSION_MAJOR >= 15 + Optional<FileEntryRef> File, +#else + const FileEntry *File, +#endif + StringRef SearchPath, + StringRef RelativePath, + const Module *Imported, + SrcMgr::CharacteristicKind FileType) { +#if CLANG_VERSION_MAJOR >= 15 + if (!File) { + return; + } + Indexer->inclusionDirective(FileNameRange.getAsRange(), &File->getFileEntry()); +#else + Indexer->inclusionDirective(FileNameRange.getAsRange(), File); +#endif +} + +void PreprocessorHook::MacroDefined(const Token &Tok, + const MacroDirective *Md) { + Indexer->macroDefined(Tok, Md); +} + +void PreprocessorHook::MacroExpands(const Token &Tok, const MacroDefinition &Md, + SourceRange Range, const MacroArgs *Ma) { + Indexer->macroUsed(Tok, Md.getMacroInfo()); +} + +void PreprocessorHook::MacroUndefined(const Token &Tok, + const MacroDefinition &Md, + const MacroDirective *Undef) +{ + Indexer->macroUsed(Tok, Md.getMacroInfo()); +} + +void PreprocessorHook::Defined(const Token &Tok, const MacroDefinition &Md, + SourceRange Range) { + Indexer->macroUsed(Tok, Md.getMacroInfo()); +} + +void PreprocessorHook::Ifdef(SourceLocation Loc, const Token &Tok, + const MacroDefinition &Md) { + Indexer->macroUsed(Tok, Md.getMacroInfo()); +} + +void PreprocessorHook::Ifndef(SourceLocation Loc, const Token &Tok, + const MacroDefinition &Md) { + Indexer->macroUsed(Tok, Md.getMacroInfo()); +} + +class IndexAction : public PluginASTAction { +protected: + std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, + llvm::StringRef F) { + return make_unique<IndexConsumer>(CI); + } + + bool ParseArgs(const CompilerInstance &CI, + const std::vector<std::string> &Args) { + if (Args.size() != 3) { + DiagnosticsEngine &D = CI.getDiagnostics(); + unsigned DiagID = D.getCustomDiagID( + DiagnosticsEngine::Error, + "Need arguments for the source, output, and object directories"); + D.Report(DiagID); + return false; + } + + // Load our directories + Srcdir = getAbsolutePath(Args[0]); + if (Srcdir.empty()) { + DiagnosticsEngine &D = CI.getDiagnostics(); + unsigned DiagID = D.getCustomDiagID( + DiagnosticsEngine::Error, "Source directory '%0' does not exist"); + D.Report(DiagID) << Args[0]; + return false; + } + + ensurePath(Args[1] + PATHSEP_STRING); + Outdir = getAbsolutePath(Args[1]); + Outdir += PATHSEP_STRING; + + Objdir = getAbsolutePath(Args[2]); + if (Objdir.empty()) { + DiagnosticsEngine &D = CI.getDiagnostics(); + unsigned DiagID = D.getCustomDiagID(DiagnosticsEngine::Error, + "Objdir '%0' does not exist"); + D.Report(DiagID) << Args[2]; + return false; + } + Objdir += PATHSEP_STRING; + + printf("MOZSEARCH: %s %s %s\n", Srcdir.c_str(), Outdir.c_str(), + Objdir.c_str()); + + return true; + } + + void printHelp(llvm::raw_ostream &Ros) { + Ros << "Help for mozsearch plugin goes here\n"; + } +}; + +static FrontendPluginRegistry::Add<IndexAction> + Y("mozsearch-index", "create the mozsearch index database"); diff --git a/build/clang-plugin/mozsearch-plugin/README b/build/clang-plugin/mozsearch-plugin/README new file mode 100644 index 0000000000..d948e9aca3 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/README @@ -0,0 +1,12 @@ +This clang plugin code generates a JSON file for each compiler input +file. The JSON file contains information about the C++ symbols that +are referenced by the input file. The data is eventually consumed by +Searchfox. See https://github.com/mozsearch/mozsearch for more +information. + +This plugin is enabled with the --enable-clang-plugin and +--enable-mozsearch-plugin mozconfig options. The output of the plugin +is stored in $OBJDIR/mozsearch_index. + +This code is not a checker, unlike other parts of the Mozilla clang +plugin. It cannot be used with clang-tidy. diff --git a/build/clang-plugin/mozsearch-plugin/StringOperations.cpp b/build/clang-plugin/mozsearch-plugin/StringOperations.cpp new file mode 100644 index 0000000000..a2e60e42c6 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/StringOperations.cpp @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "StringOperations.h" + +static unsigned long djbHash(const char *Str) { + unsigned long Hash = 5381; + + for (const char *P = Str; *P; P++) { + // Hash * 33 + c + Hash = ((Hash << 5) + Hash) + *P; + } + + return Hash; +} + +// This doesn't actually return a hex string of |hash|, but it +// does... something. It doesn't really matter what. +static void hashToString(unsigned long Hash, char *Buffer) { + const char Table[] = {"0123456789abcdef"}; + char *P = Buffer; + while (Hash) { + *P = Table[Hash & 0xf]; + Hash >>= 4; + P++; + } + + *P = 0; +} + +std::string hash(const std::string &Str) { + static char HashStr[41]; + unsigned long H = djbHash(Str.c_str()); + hashToString(H, HashStr); + return std::string(HashStr); +} + +std::string toString(int N) { + return stringFormat("%d", N); +} diff --git a/build/clang-plugin/mozsearch-plugin/StringOperations.h b/build/clang-plugin/mozsearch-plugin/StringOperations.h new file mode 100644 index 0000000000..4aa5b31962 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/StringOperations.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 StringOperations_h +#define StringOperations_h + +#include <memory> +#include <string> +#include <string.h> + +std::string hash(const std::string &Str); + +template <typename... Args> +inline std::string stringFormat(const std::string &Format, Args... ArgList) { + size_t Len = snprintf(nullptr, 0, Format.c_str(), ArgList...); + std::unique_ptr<char[]> Buf(new char[Len + 1]); + snprintf(Buf.get(), Len + 1, Format.c_str(), ArgList...); + return std::string(Buf.get(), Buf.get() + Len); +} + +std::string toString(int N); + +#endif diff --git a/build/clang-plugin/mozsearch-plugin/from-clangd/HeuristicResolver.cpp b/build/clang-plugin/mozsearch-plugin/from-clangd/HeuristicResolver.cpp new file mode 100644 index 0000000000..719094dbf2 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/from-clangd/HeuristicResolver.cpp @@ -0,0 +1,349 @@ +//===--- HeuristicResolver.cpp ---------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "HeuristicResolver.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" + +namespace clang { +namespace clangd { + +// Convenience lambdas for use as the 'Filter' parameter of +// HeuristicResolver::resolveDependentMember(). +const auto NoFilter = [](const NamedDecl *D) { return true; }; +const auto NonStaticFilter = [](const NamedDecl *D) { + return D->isCXXInstanceMember(); +}; +const auto StaticFilter = [](const NamedDecl *D) { + return !D->isCXXInstanceMember(); +}; +const auto ValueFilter = [](const NamedDecl *D) { return isa<ValueDecl>(D); }; +const auto TypeFilter = [](const NamedDecl *D) { return isa<TypeDecl>(D); }; +const auto TemplateFilter = [](const NamedDecl *D) { + return isa<TemplateDecl>(D); +}; + +namespace { + +const Type *resolveDeclsToType(const std::vector<const NamedDecl *> &Decls, + ASTContext &Ctx) { + if (Decls.size() != 1) // Names an overload set -- just bail. + return nullptr; + if (const auto *TD = dyn_cast<TypeDecl>(Decls[0])) { + return Ctx.getTypeDeclType(TD).getTypePtr(); + } + if (const auto *VD = dyn_cast<ValueDecl>(Decls[0])) { + return VD->getType().getTypePtrOrNull(); + } + return nullptr; +} + +} // namespace + +// Helper function for HeuristicResolver::resolveDependentMember() +// which takes a possibly-dependent type `T` and heuristically +// resolves it to a CXXRecordDecl in which we can try name lookup. +CXXRecordDecl *HeuristicResolver::resolveTypeToRecordDecl(const Type *T) const { + assert(T); + + // Unwrap type sugar such as type aliases. + T = T->getCanonicalTypeInternal().getTypePtr(); + + if (const auto *DNT = T->getAs<DependentNameType>()) { + T = resolveDeclsToType(resolveDependentNameType(DNT), Ctx); + if (!T) + return nullptr; + T = T->getCanonicalTypeInternal().getTypePtr(); + } + + if (const auto *RT = T->getAs<RecordType>()) + return dyn_cast<CXXRecordDecl>(RT->getDecl()); + + if (const auto *ICNT = T->getAs<InjectedClassNameType>()) + T = ICNT->getInjectedSpecializationType().getTypePtrOrNull(); + if (!T) + return nullptr; + + const auto *TST = T->getAs<TemplateSpecializationType>(); + if (!TST) + return nullptr; + + const ClassTemplateDecl *TD = dyn_cast_or_null<ClassTemplateDecl>( + TST->getTemplateName().getAsTemplateDecl()); + if (!TD) + return nullptr; + + return TD->getTemplatedDecl(); +} + +const Type *HeuristicResolver::getPointeeType(const Type *T) const { + if (!T) + return nullptr; + + if (T->isPointerType()) + return T->castAs<PointerType>()->getPointeeType().getTypePtrOrNull(); + + // Try to handle smart pointer types. + + // Look up operator-> in the primary template. If we find one, it's probably a + // smart pointer type. + auto ArrowOps = resolveDependentMember( + T, Ctx.DeclarationNames.getCXXOperatorName(OO_Arrow), NonStaticFilter); + if (ArrowOps.empty()) + return nullptr; + + // Getting the return type of the found operator-> method decl isn't useful, + // because we discarded template arguments to perform lookup in the primary + // template scope, so the return type would just have the form U* where U is a + // template parameter type. + // Instead, just handle the common case where the smart pointer type has the + // form of SmartPtr<X, ...>, and assume X is the pointee type. + auto *TST = T->getAs<TemplateSpecializationType>(); + if (!TST) + return nullptr; + if (TST->template_arguments().size() == 0) + return nullptr; + const TemplateArgument &FirstArg = TST->template_arguments()[0]; + if (FirstArg.getKind() != TemplateArgument::Type) + return nullptr; + return FirstArg.getAsType().getTypePtrOrNull(); +} + +std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr( + const CXXDependentScopeMemberExpr *ME) const { + // If the expression has a qualifier, first try resolving the member + // inside the qualifier's type. + // Note that we cannot use a NonStaticFilter in either case, for a couple + // of reasons: + // 1. It's valid to access a static member using instance member syntax, + // e.g. `instance.static_member`. + // 2. We can sometimes get a CXXDependentScopeMemberExpr for static + // member syntax too, e.g. if `X::static_member` occurs inside + // an instance method, it's represented as a CXXDependentScopeMemberExpr + // with `this` as the base expression as `X` as the qualifier + // (which could be valid if `X` names a base class after instantiation). + if (NestedNameSpecifier *NNS = ME->getQualifier()) { + if (const Type *QualifierType = resolveNestedNameSpecifierToType(NNS)) { + auto Decls = + resolveDependentMember(QualifierType, ME->getMember(), NoFilter); + if (!Decls.empty()) + return Decls; + } + } + + // If that didn't yield any results, try resolving the member inside + // the expression's base type. + const Type *BaseType = ME->getBaseType().getTypePtrOrNull(); + if (ME->isArrow()) { + BaseType = getPointeeType(BaseType); + } + if (!BaseType) + return {}; + if (const auto *BT = BaseType->getAs<BuiltinType>()) { + // If BaseType is the type of a dependent expression, it's just + // represented as BuiltinType::Dependent which gives us no information. We + // can get further by analyzing the dependent expression. + Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase(); + if (Base && BT->getKind() == BuiltinType::Dependent) { + BaseType = resolveExprToType(Base); + } + } + return resolveDependentMember(BaseType, ME->getMember(), NoFilter); +} + +std::vector<const NamedDecl *> HeuristicResolver::resolveDeclRefExpr( + const DependentScopeDeclRefExpr *RE) const { + return resolveDependentMember(RE->getQualifier()->getAsType(), + RE->getDeclName(), StaticFilter); +} + +std::vector<const NamedDecl *> +HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const { + const auto *CalleeType = resolveExprToType(CE->getCallee()); + if (!CalleeType) + return {}; + if (const auto *FnTypePtr = CalleeType->getAs<PointerType>()) + CalleeType = FnTypePtr->getPointeeType().getTypePtr(); + if (const FunctionType *FnType = CalleeType->getAs<FunctionType>()) { + if (const auto *D = + resolveTypeToRecordDecl(FnType->getReturnType().getTypePtr())) { + return {D}; + } + } + return {}; +} + +std::vector<const NamedDecl *> +HeuristicResolver::resolveCalleeOfCallExpr(const CallExpr *CE) const { + if (const auto *ND = dyn_cast_or_null<NamedDecl>(CE->getCalleeDecl())) { + return {ND}; + } + + return resolveExprToDecls(CE->getCallee()); +} + +std::vector<const NamedDecl *> HeuristicResolver::resolveUsingValueDecl( + const UnresolvedUsingValueDecl *UUVD) const { + return resolveDependentMember(UUVD->getQualifier()->getAsType(), + UUVD->getNameInfo().getName(), ValueFilter); +} + +std::vector<const NamedDecl *> HeuristicResolver::resolveDependentNameType( + const DependentNameType *DNT) const { + return resolveDependentMember( + resolveNestedNameSpecifierToType(DNT->getQualifier()), + DNT->getIdentifier(), TypeFilter); +} + +std::vector<const NamedDecl *> +HeuristicResolver::resolveTemplateSpecializationType( + const DependentTemplateSpecializationType *DTST) const { + return resolveDependentMember( + resolveNestedNameSpecifierToType(DTST->getQualifier()), + DTST->getIdentifier(), TemplateFilter); +} + +std::vector<const NamedDecl *> +HeuristicResolver::resolveExprToDecls(const Expr *E) const { + if (const auto *ME = dyn_cast<CXXDependentScopeMemberExpr>(E)) { + return resolveMemberExpr(ME); + } + if (const auto *RE = dyn_cast<DependentScopeDeclRefExpr>(E)) { + return resolveDeclRefExpr(RE); + } + if (const auto *OE = dyn_cast<OverloadExpr>(E)) { + return {OE->decls_begin(), OE->decls_end()}; + } + if (const auto *CE = dyn_cast<CallExpr>(E)) { + return resolveTypeOfCallExpr(CE); + } + if (const auto *ME = dyn_cast<MemberExpr>(E)) + return {ME->getMemberDecl()}; + + return {}; +} + +const Type *HeuristicResolver::resolveExprToType(const Expr *E) const { + std::vector<const NamedDecl *> Decls = resolveExprToDecls(E); + if (!Decls.empty()) + return resolveDeclsToType(Decls, Ctx); + + return E->getType().getTypePtr(); +} + +const Type *HeuristicResolver::resolveNestedNameSpecifierToType( + const NestedNameSpecifier *NNS) const { + if (!NNS) + return nullptr; + + // The purpose of this function is to handle the dependent (Kind == + // Identifier) case, but we need to recurse on the prefix because + // that may be dependent as well, so for convenience handle + // the TypeSpec cases too. + switch (NNS->getKind()) { + case NestedNameSpecifier::TypeSpec: + case NestedNameSpecifier::TypeSpecWithTemplate: + return NNS->getAsType(); + case NestedNameSpecifier::Identifier: { + return resolveDeclsToType( + resolveDependentMember( + resolveNestedNameSpecifierToType(NNS->getPrefix()), + NNS->getAsIdentifier(), TypeFilter), + Ctx); + } + default: + break; + } + return nullptr; +} + +namespace { + +bool isOrdinaryMember(const NamedDecl *ND) { + return ND->isInIdentifierNamespace(Decl::IDNS_Ordinary | Decl::IDNS_Tag | + Decl::IDNS_Member); +} + +bool findOrdinaryMember(const CXXRecordDecl *RD, CXXBasePath &Path, + DeclarationName Name) { + Path.Decls = RD->lookup(Name).begin(); + for (DeclContext::lookup_iterator I = Path.Decls, E = I.end(); I != E; ++I) + if (isOrdinaryMember(*I)) + return true; + + return false; +} + +} // namespace + +bool HeuristicResolver::findOrdinaryMemberInDependentClasses( + const CXXBaseSpecifier *Specifier, CXXBasePath &Path, + DeclarationName Name) const { + CXXRecordDecl *RD = + resolveTypeToRecordDecl(Specifier->getType().getTypePtr()); + if (!RD) + return false; + return findOrdinaryMember(RD, Path, Name); +} + +std::vector<const NamedDecl *> HeuristicResolver::lookupDependentName( + CXXRecordDecl *RD, DeclarationName Name, + llvm::function_ref<bool(const NamedDecl *ND)> Filter) const { + std::vector<const NamedDecl *> Results; + + // Lookup in the class. + bool AnyOrdinaryMembers = false; + for (const NamedDecl *ND : RD->lookup(Name)) { + if (isOrdinaryMember(ND)) + AnyOrdinaryMembers = true; + if (Filter(ND)) + Results.push_back(ND); + } + if (AnyOrdinaryMembers) + return Results; + + // Perform lookup into our base classes. + CXXBasePaths Paths; + Paths.setOrigin(RD); + if (!RD->lookupInBases( + [&](const CXXBaseSpecifier *Specifier, CXXBasePath &Path) { + return findOrdinaryMemberInDependentClasses(Specifier, Path, Name); + }, + Paths, /*LookupInDependent=*/true)) + return Results; + for (DeclContext::lookup_iterator I = Paths.front().Decls, E = I.end(); + I != E; ++I) { + if (isOrdinaryMember(*I) && Filter(*I)) + Results.push_back(*I); + } + return Results; +} + +std::vector<const NamedDecl *> HeuristicResolver::resolveDependentMember( + const Type *T, DeclarationName Name, + llvm::function_ref<bool(const NamedDecl *ND)> Filter) const { + if (!T) + return {}; + if (auto *ET = T->getAs<EnumType>()) { + auto Result = ET->getDecl()->lookup(Name); + return {Result.begin(), Result.end()}; + } + if (auto *RD = resolveTypeToRecordDecl(T)) { + if (!RD->hasDefinition()) + return {}; + RD = RD->getDefinition(); + return lookupDependentName(RD, Name, Filter); + } + return {}; +} + +} // namespace clangd +} // namespace clang diff --git a/build/clang-plugin/mozsearch-plugin/from-clangd/HeuristicResolver.h b/build/clang-plugin/mozsearch-plugin/from-clangd/HeuristicResolver.h new file mode 100644 index 0000000000..dc04123d37 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/from-clangd/HeuristicResolver.h @@ -0,0 +1,122 @@ +//===--- HeuristicResolver.h - Resolution of dependent names -----*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEURISTICRESOLVER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEURISTICRESOLVER_H + +#include "clang/AST/Decl.h" +#include <vector> + +namespace clang { + +class ASTContext; +class CallExpr; +class CXXBasePath; +class CXXDependentScopeMemberExpr; +class DeclarationName; +class DependentScopeDeclRefExpr; +class NamedDecl; +class Type; +class UnresolvedUsingValueDecl; + +namespace clangd { + +// This class heuristic resolution of declarations and types in template code. +// +// As a compiler, clang only needs to perform certain types of processing on +// template code (such as resolving dependent names to declarations, or +// resolving the type of a dependent expression) after instantiation. Indeed, +// C++ language features such as template specialization mean such resolution +// cannot be done accurately before instantiation +// +// However, template code is written and read in uninstantiated form, and clangd +// would like to provide editor features like go-to-definition in template code +// where possible. To this end, clangd attempts to resolve declarations and +// types in uninstantiated code by using heuristics, understanding that the +// results may not be fully accurate but that this is better than nothing. +// +// At this time, the heuristic used is a simple but effective one: assume that +// template instantiations are based on the primary template definition and not +// not a specialization. More advanced heuristics may be added in the future. +class HeuristicResolver { +public: + HeuristicResolver(ASTContext &Ctx) : Ctx(Ctx) {} + + // Try to heuristically resolve certain types of expressions, declarations, or + // types to one or more likely-referenced declarations. + std::vector<const NamedDecl *> + resolveMemberExpr(const CXXDependentScopeMemberExpr *ME) const; + std::vector<const NamedDecl *> + resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) const; + std::vector<const NamedDecl *> + resolveTypeOfCallExpr(const CallExpr *CE) const; + std::vector<const NamedDecl *> + resolveCalleeOfCallExpr(const CallExpr *CE) const; + std::vector<const NamedDecl *> + resolveUsingValueDecl(const UnresolvedUsingValueDecl *UUVD) const; + std::vector<const NamedDecl *> + resolveDependentNameType(const DependentNameType *DNT) const; + std::vector<const NamedDecl *> resolveTemplateSpecializationType( + const DependentTemplateSpecializationType *DTST) const; + + // Try to heuristically resolve a dependent nested name specifier + // to the type it likely denotes. Note that *dependent* name specifiers always + // denote types, not namespaces. + const Type * + resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS) const; + + // Given the type T of a dependent expression that appears of the LHS of a + // "->", heuristically find a corresponding pointee type in whose scope we + // could look up the name appearing on the RHS. + const Type *getPointeeType(const Type *T) const; + +private: + ASTContext &Ctx; + + // Given a tag-decl type and a member name, heuristically resolve the + // name to one or more declarations. + // The current heuristic is simply to look up the name in the primary + // template. This is a heuristic because the template could potentially + // have specializations that declare different members. + // Multiple declarations could be returned if the name is overloaded + // (e.g. an overloaded method in the primary template). + // This heuristic will give the desired answer in many cases, e.g. + // for a call to vector<T>::size(). + std::vector<const NamedDecl *> resolveDependentMember( + const Type *T, DeclarationName Name, + llvm::function_ref<bool(const NamedDecl *ND)> Filter) const; + + // Try to heuristically resolve the type of a possibly-dependent expression + // `E`. + const Type *resolveExprToType(const Expr *E) const; + std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E) const; + + // Helper function for HeuristicResolver::resolveDependentMember() + // which takes a possibly-dependent type `T` and heuristically + // resolves it to a CXXRecordDecl in which we can try name lookup. + CXXRecordDecl *resolveTypeToRecordDecl(const Type *T) const; + + // This is a reimplementation of CXXRecordDecl::lookupDependentName() + // so that the implementation can call into other HeuristicResolver helpers. + // FIXME: Once HeuristicResolver is upstreamed to the clang libraries + // (https://github.com/clangd/clangd/discussions/1662), + // CXXRecordDecl::lookupDepenedentName() can be removed, and its call sites + // can be modified to benefit from the more comprehensive heuristics offered + // by HeuristicResolver instead. + std::vector<const NamedDecl *> lookupDependentName( + CXXRecordDecl *RD, DeclarationName Name, + llvm::function_ref<bool(const NamedDecl *ND)> Filter) const; + bool findOrdinaryMemberInDependentClasses(const CXXBaseSpecifier *Specifier, + CXXBasePath &Path, + DeclarationName Name) const; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/build/clang-plugin/mozsearch-plugin/from-clangd/README.md b/build/clang-plugin/mozsearch-plugin/from-clangd/README.md new file mode 100644 index 0000000000..9f39dab231 --- /dev/null +++ b/build/clang-plugin/mozsearch-plugin/from-clangd/README.md @@ -0,0 +1,10 @@ +The facilities in this subdirectory are copied over from clangd +(https://clangd.llvm.org/). + +The files here are currently copies of the following upstream files: +https://github.com/llvm/llvm-project/blob/2bcbcbefcd0f7432f99cc07bb47d1e1ecb579a3f/clang-tools-extra/clangd/HeuristicResolver.h +https://github.com/llvm/llvm-project/blob/2bcbcbefcd0f7432f99cc07bb47d1e1ecb579a3f/clang-tools-extra/clangd/HeuristicResolver.cpp + +If, in the future, these facilities are moved from clangd to +to libclangTooling and exposed in the clang API headers, we can +switch to consuming them from there directly. diff --git a/build/clang-plugin/plugin.h b/build/clang-plugin/plugin.h new file mode 100644 index 0000000000..15e7560455 --- /dev/null +++ b/build/clang-plugin/plugin.h @@ -0,0 +1,57 @@ +/* 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 plugin_h__ +#define plugin_h__ + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/CFG.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "clang/Sema/Sema.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include <iterator> +#include <memory> + +#define CLANG_VERSION_FULL (CLANG_VERSION_MAJOR * 100 + CLANG_VERSION_MINOR) + +using namespace llvm; +using namespace clang; +using namespace clang::ast_matchers; + +#if CLANG_VERSION_FULL >= 306 +typedef std::unique_ptr<ASTConsumer> ASTConsumerPtr; +#else +typedef ASTConsumer *ASTConsumerPtr; +#endif + +#if CLANG_VERSION_FULL < 800 +// Starting with Clang 8.0 some basic functions have been renamed +#define getBeginLoc getLocStart +#define getEndLoc getLocEnd +#endif + +// In order to support running our checks using clang-tidy, we implement a +// source compatible base check class called BaseCheck, and we use the +// preprocessor to decide which base class to pick. +#ifdef CLANG_TIDY +#if CLANG_VERSION_FULL >= 900 +#include "../ClangTidyCheck.h" +#else +#include "../ClangTidy.h" +#endif +typedef clang::tidy::ClangTidyCheck BaseCheck; +typedef clang::tidy::ClangTidyContext ContextType; +#else +#include "BaseCheck.h" +#endif + +#endif // plugin_h__ diff --git a/build/clang-plugin/tests/Makefile.in b/build/clang-plugin/tests/Makefile.in new file mode 100644 index 0000000000..318c9a0261 --- /dev/null +++ b/build/clang-plugin/tests/Makefile.in @@ -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/. + +include $(topsrcdir)/config/rules.mk + +$(OBJS): $(CLANG_PLUGIN) + +# Because building the objects doesn't actually build anything, create +# a stamp file to avoid re-running the tests unless the test files or +# the plugin changed. And since no objects are created, and not having +# the files around makes the rule always, we create dummy files here too. +tests-ok: $(OBJS) $(CSRCS) $(CPPSRCS) $(CLANG_PLUGIN) + touch $@ $(OBJS) + +target-objects: tests-ok + +# Don't actually build a library, since we don't actually build objects. +$(LIBRARY): EXPAND_LIBS_GEN=true diff --git a/build/clang-plugin/tests/NonParameterTestCases.h b/build/clang-plugin/tests/NonParameterTestCases.h new file mode 100644 index 0000000000..d38a14d944 --- /dev/null +++ b/build/clang-plugin/tests/NonParameterTestCases.h @@ -0,0 +1,61 @@ +MAYBE_STATIC void raw(Param x) {} + +MAYBE_STATIC void raw(NonParam x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void raw(NonParamUnion x) {} //expected-error {{Type 'NonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void raw(NonParamClass x) {} //expected-error {{Type 'NonParamClass' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void raw(NonParamEnum x) {} //expected-error {{Type 'NonParamEnum' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void raw(NonParamEnumClass x) {} //expected-error {{Type 'NonParamEnumClass' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void raw(HasNonParamStruct x) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void raw(HasNonParamUnion x) {} //expected-error {{Type 'HasNonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void raw(HasNonParamStructUnion x) {} //expected-error {{Type 'HasNonParamStructUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + +MAYBE_STATIC void const_(const NonParam x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void const_(const NonParamUnion x) {} //expected-error {{Type 'NonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void const_(const NonParamClass x) {} //expected-error {{Type 'NonParamClass' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void const_(const NonParamEnum x) {} //expected-error {{Type 'NonParamEnum' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void const_(const NonParamEnumClass x) {} //expected-error {{Type 'NonParamEnumClass' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void const_(const HasNonParamStruct x) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void const_(const HasNonParamUnion x) {} //expected-error {{Type 'HasNonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC void const_(const HasNonParamStructUnion x) {} //expected-error {{Type 'HasNonParamStructUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + +MAYBE_STATIC void array(NonParam x[]) {} +MAYBE_STATIC void array(NonParamUnion x[]) {} +MAYBE_STATIC void array(NonParamClass x[]) {} +MAYBE_STATIC void array(NonParamEnum x[]) {} +MAYBE_STATIC void array(NonParamEnumClass x[]) {} +MAYBE_STATIC void array(HasNonParamStruct x[]) {} +MAYBE_STATIC void array(HasNonParamUnion x[]) {} +MAYBE_STATIC void array(HasNonParamStructUnion x[]) {} + +MAYBE_STATIC void ptr(NonParam* x) {} +MAYBE_STATIC void ptr(NonParamUnion* x) {} +MAYBE_STATIC void ptr(NonParamClass* x) {} +MAYBE_STATIC void ptr(NonParamEnum* x) {} +MAYBE_STATIC void ptr(NonParamEnumClass* x) {} +MAYBE_STATIC void ptr(HasNonParamStruct* x) {} +MAYBE_STATIC void ptr(HasNonParamUnion* x) {} +MAYBE_STATIC void ptr(HasNonParamStructUnion* x) {} + +MAYBE_STATIC void ref(NonParam& x) {} +MAYBE_STATIC void ref(NonParamUnion& x) {} +MAYBE_STATIC void ref(NonParamClass& x) {} +MAYBE_STATIC void ref(NonParamEnum& x) {} +MAYBE_STATIC void ref(NonParamEnumClass& x) {} +MAYBE_STATIC void ref(HasNonParamStruct& x) {} +MAYBE_STATIC void ref(HasNonParamUnion& x) {} +MAYBE_STATIC void ref(HasNonParamStructUnion& x) {} + +MAYBE_STATIC void constRef(const NonParam& x) {} +MAYBE_STATIC void constRef(const NonParamUnion& x) {} +MAYBE_STATIC void constRef(const NonParamClass& x) {} +MAYBE_STATIC void constRef(const NonParamEnum& x) {} +MAYBE_STATIC void constRef(const NonParamEnumClass& x) {} +MAYBE_STATIC void constRef(const HasNonParamStruct& x) {} +MAYBE_STATIC void constRef(const HasNonParamUnion& x) {} +MAYBE_STATIC void constRef(const HasNonParamStructUnion& x) {} + +MAYBE_STATIC inline void inlineRaw(NonParam x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC inline void inlineRaw(NonParamUnion x) {} //expected-error {{Type 'NonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC inline void inlineRaw(NonParamClass x) {} //expected-error {{Type 'NonParamClass' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC inline void inlineRaw(NonParamEnum x) {} //expected-error {{Type 'NonParamEnum' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +MAYBE_STATIC inline void inlineRaw(NonParamEnumClass x) {} //expected-error {{Type 'NonParamEnumClass' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} diff --git a/build/clang-plugin/tests/TestAssertWithAssignment.cpp b/build/clang-plugin/tests/TestAssertWithAssignment.cpp new file mode 100644 index 0000000000..f0f049e4a3 --- /dev/null +++ b/build/clang-plugin/tests/TestAssertWithAssignment.cpp @@ -0,0 +1,68 @@ +#include "mozilla/MacroArgs.h" + +static __attribute__((always_inline)) bool MOZ_AssertAssignmentTest(bool expr) { + return expr; +} + +#define MOZ_UNLIKELY(x) (__builtin_expect(!!(x), 0)) +#define MOZ_CRASH() do { } while(0) +#define MOZ_CHECK_ASSERT_ASSIGNMENT(expr) MOZ_AssertAssignmentTest(!!(expr)) + +#define MOZ_ASSERT_HELPER1(expr) \ + do { \ + if (MOZ_UNLIKELY(!MOZ_CHECK_ASSERT_ASSIGNMENT(expr))) { \ + MOZ_CRASH();\ + } \ + } while(0) \ + +/* Now the two-argument form. */ +#define MOZ_ASSERT_HELPER2(expr, explain) \ + do { \ + if (MOZ_UNLIKELY(!MOZ_CHECK_ASSERT_ASSIGNMENT(expr))) { \ + MOZ_CRASH();\ + } \ + } while(0) \ + +#define MOZ_RELEASE_ASSERT_GLUE(a, b) a b +#define MOZ_RELEASE_ASSERT(...) \ + MOZ_RELEASE_ASSERT_GLUE( \ + MOZ_PASTE_PREFIX_AND_ARG_COUNT(MOZ_ASSERT_HELPER, __VA_ARGS__), \ + (__VA_ARGS__)) + +#define MOZ_ASSERT(...) MOZ_RELEASE_ASSERT(__VA_ARGS__) + +void FunctionTest(int p) { + MOZ_ASSERT(p = 1); // expected-error {{Forbidden assignment in assert expression}} +} + +void FunctionTest2(int p) { + MOZ_ASSERT(((p = 1))); // expected-error {{Forbidden assignment in assert expression}} +} + +void FunctionTest3(int p) { + MOZ_ASSERT(p != 3); +} + +class TestOverloading { + int value; +public: + explicit TestOverloading(int _val) : value(_val) {} + // different operators + explicit operator bool() const { return true; } + TestOverloading& operator=(const int _val) { value = _val; return *this; } + + int& GetInt() {return value;} +}; + +void TestOverloadingFunc() { + TestOverloading p(2); + int f; + + MOZ_ASSERT(p); + MOZ_ASSERT(p = 3); // expected-error {{Forbidden assignment in assert expression}} + MOZ_ASSERT(p, "p is not valid"); + MOZ_ASSERT(p = 3, "p different than 3"); // expected-error {{Forbidden assignment in assert expression}} + MOZ_ASSERT(p.GetInt() = 2); // expected-error {{Forbidden assignment in assert expression}} + MOZ_ASSERT(p.GetInt() == 2); + MOZ_ASSERT(p.GetInt() == 2, f = 3); +} diff --git a/build/clang-plugin/tests/TestBadImplicitConversionCtor.cpp b/build/clang-plugin/tests/TestBadImplicitConversionCtor.cpp new file mode 100644 index 0000000000..ca2472582e --- /dev/null +++ b/build/clang-plugin/tests/TestBadImplicitConversionCtor.cpp @@ -0,0 +1,50 @@ +#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) + +struct Foo { + Foo(int); // expected-error {{bad implicit conversion constructor for 'Foo'}} expected-note {{consider adding the explicit keyword to the constructor}} + Foo(int, char=0); // expected-error {{bad implicit conversion constructor for 'Foo'}} expected-note {{consider adding the explicit keyword to the constructor}} + Foo(...); // expected-error {{bad implicit conversion constructor for 'Foo'}} expected-note {{consider adding the explicit keyword to the constructor}} + template<class T> + Foo(float); // expected-error {{bad implicit conversion constructor for 'Foo'}} expected-note {{consider adding the explicit keyword to the constructor}} + Foo(int, unsigned); + Foo(Foo&); + Foo(const Foo&); + Foo(volatile Foo&); + Foo(const volatile Foo&); + Foo(Foo&&); + Foo(const Foo&&); + Foo(volatile Foo&&); + Foo(const volatile Foo&&); +}; + +struct Bar { + explicit Bar(int); + explicit Bar(int, char=0); + explicit Bar(...); +}; + +struct Baz { + MOZ_IMPLICIT Baz(int); + MOZ_IMPLICIT Baz(int, char=0); + MOZ_IMPLICIT Baz(...); +}; + +struct Barn { + Barn(int) = delete; + Barn(int, char=0) = delete; + Barn(...) = delete; +}; + +struct Abstract { + Abstract(int); + Abstract(int, char=0); + Abstract(...); + virtual void f() = 0; +}; + +template<class T> +struct Template { + Template(int); // expected-error {{bad implicit conversion constructor for 'Template'}} expected-note {{consider adding the explicit keyword to the constructor}} + template<class U> + Template(float); // expected-error {{bad implicit conversion constructor for 'Template'}} expected-note {{consider adding the explicit keyword to the constructor}} +}; diff --git a/build/clang-plugin/tests/TestCanRunScript.cpp b/build/clang-plugin/tests/TestCanRunScript.cpp new file mode 100644 index 0000000000..28795a31bf --- /dev/null +++ b/build/clang-plugin/tests/TestCanRunScript.cpp @@ -0,0 +1,881 @@ +#include <mozilla/RefPtr.h> +#include <mozilla/Maybe.h> + +#define MOZ_CAN_RUN_SCRIPT __attribute__((annotate("moz_can_run_script"))) +#define MOZ_CAN_RUN_SCRIPT_BOUNDARY __attribute__((annotate("moz_can_run_script_boundary"))) + +MOZ_CAN_RUN_SCRIPT void test() { + +} + +void test_parent() { // expected-note {{caller 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() { // expected-note {{caller 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 method_test4() { + method_test(); + } + + MOZ_CAN_RUN_SCRIPT void method_test5() { + this->method_test(); + } +}; + +MOZ_CAN_RUN_SCRIPT void testLambda() { + auto doIt = []() MOZ_CAN_RUN_SCRIPT { + test(); + }; + + auto doItWrong = []() { // expected-note {{caller 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}} + }; + + doIt(); + doItWrong(); +} + +void test2_parent() { // expected-note {{caller function declared here}} + test2(new RefCountedBase); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'new RefCountedBase' is neither.}} \ + // 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 caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'new RefCountedBase' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test2_parent3(RefCountedBase* param) { + test2(param); + + RefCountedBase*& paramRef = param; + test2(paramRef); +} + +MOZ_CAN_RUN_SCRIPT void test2_parent4() { + RefPtr<RefCountedBase> refptr = new RefCountedBase; + test2(refptr); + RefPtr<RefCountedBase>& refptrRef = refptr; + test2(refptrRef); + const RefPtr<RefCountedBase>& refptrConstRef = refptr; + test2(refptrConstRef); + + RefPtr<RefCountedBase> refptrOther = refptr; + RefPtr<RefCountedBase>& refptrRef2 = refptr ? refptr : refptrOther; + test2(refptrRef2); + + RefPtr<RefCountedBase>* refPtrInHeap = new RefPtr<RefCountedBase>; + RefPtr<RefCountedBase>& refptrRefUnsafe1 = *refPtrInHeap; + test2(refptrRefUnsafe1); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe1' is neither.}} + + RefPtr<RefCountedBase>& refptrRefUnsafe2 = refPtrInHeap ? *refPtrInHeap : refptr; + test2(refptrRefUnsafe2); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe2' is neither.}} + + RefPtr<RefCountedBase>& refptrRefUnsafe3 = refPtrInHeap ? refptr : *refPtrInHeap; + test2(refptrRefUnsafe3); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe3' is neither.}} +} + +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(); + RefPtr<RefCountedBase>& refptrRef = refptr; + refptrRef->method_test(); + refptrRef->method_test2(); + + RefPtr<RefCountedBase> refptrOther = refptr; + RefPtr<RefCountedBase>& refptrRef2 = refptr ? refptr : refptrOther; + refptrRef2->method_test(); + refptrRef2->method_test2(); + + RefPtr<RefCountedBase>* refPtrInHeap = new RefPtr<RefCountedBase>; + RefPtr<RefCountedBase>& refptrRefUnsafe1 = *refPtrInHeap; + refptrRefUnsafe1->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe1->' is neither.}} + refptrRefUnsafe1->method_test2(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe1->' is neither.}} + + RefPtr<RefCountedBase>& refptrRefUnsafe2 = refPtrInHeap ? *refPtrInHeap : refptr; + refptrRefUnsafe2->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe2->' is neither.}} + refptrRefUnsafe2->method_test2(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe2->' is neither.}} + + RefPtr<RefCountedBase>& refptrRefUnsafe3 = refPtrInHeap ? refptr : *refPtrInHeap; + refptrRefUnsafe3->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe3->' is neither.}} + refptrRefUnsafe3->method_test2(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refptrRefUnsafe3->' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test2_parent7() { + RefCountedBase* t = new RefCountedBase; + t->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 't' is neither.}} + t->method_test2(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 't' is neither.}} + + RefCountedBase*& tRef = t; + tRef->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'tRef' is neither.}} + tRef->method_test2(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'tRef' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test2_parent8() { + test2(nullptr); +} + +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; // expected-note {{overridden function declared here}} expected-note {{overridden function declared here}} expected-note {{caller function declared here}} +}; + +void RefCountedChild::method_test3() { + test(); // expected-error {{functions marked as MOZ_CAN_RUN_SCRIPT can only be called from functions also marked as MOZ_CAN_RUN_SCRIPT}} +} + +struct RefCountedSubChild : public RefCountedChild { + MOZ_CAN_RUN_SCRIPT void method_test3() override; // expected-error {{functions marked as MOZ_CAN_RUN_SCRIPT cannot override functions that are not marked MOZ_CAN_RUN_SCRIPT}} +}; + +void RefCountedSubChild::method_test3() { // expected-error {{functions marked as MOZ_CAN_RUN_SCRIPT cannot override functions that are not marked MOZ_CAN_RUN_SCRIPT}} + 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(); +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void test5() { + 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(); +} + +// We should be able to call test5 from a non-can_run_script function. +void test5_b() { + test5(); +} + +MOZ_CAN_RUN_SCRIPT void test6() { + void* x = new RefCountedBase(); + test2((RefCountedBase*)x); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'x' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_ref(const RefCountedBase&) { + +} + +MOZ_CAN_RUN_SCRIPT void test_ref_1() { + RefCountedBase* t = new RefCountedBase; + test_ref(*t); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*t' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_ref_2() { + RefCountedBase* t = new RefCountedBase; + (*t).method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*t' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_ref_3() { + RefCountedBase* t = new RefCountedBase; + auto& ref = *t; + test_ref(ref); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'ref' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_ref_4() { + RefCountedBase* t = new RefCountedBase; + auto& ref = *t; + ref.method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'ref' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_ref_5() { + RefPtr<RefCountedBase> t = new RefCountedBase; + test_ref(*t); +} + +MOZ_CAN_RUN_SCRIPT void test_ref_6() { + RefPtr<RefCountedBase> t = new RefCountedBase; + (*t).method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ref_7() { + RefPtr<RefCountedBase> t = new RefCountedBase; + auto& ref = *t; + MOZ_KnownLive(ref).method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ref_8() { + RefPtr<RefCountedBase> t = new RefCountedBase; + auto& ref = *t; + test_ref(MOZ_KnownLive(ref)); +} + +MOZ_CAN_RUN_SCRIPT void test_ref_9() { + void* x = new RefCountedBase(); + test_ref(*(RefCountedBase*)x); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*(RefCountedBase*)x' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_maybe() { + mozilla::Maybe<RefCountedBase*> unsafe; + unsafe.emplace(new RefCountedBase); + (*unsafe)->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*unsafe' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_maybe_2() { + // FIXME(bz): This should not generate an error! + mozilla::Maybe<RefPtr<RefCountedBase>> safe; + safe.emplace(new RefCountedBase); + (*safe)->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '(*safe){{(->)?}}' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_helper_1(RefCountedBase* arg = nullptr) { +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_1() { + test_defaults_helper_1(); +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_2() { + RefCountedBase* t = new RefCountedBase; + test_defaults_helper_1(t); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 't' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_3() { + RefPtr<RefCountedBase> t = new RefCountedBase; + test_defaults_helper_1(t); +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_helper_2(RefCountedBase* arg = new RefCountedBase()) { // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'new RefCountedBase()' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_4() { + test_defaults_helper_2(); +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_5() { + RefCountedBase* t = new RefCountedBase; + test_defaults_helper_2(t); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 't' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_defaults_6() { + RefPtr<RefCountedBase> t = new RefCountedBase; + test_defaults_helper_2(t); +} + +MOZ_CAN_RUN_SCRIPT void test_arg_deref_helper(RefCountedBase&) { +} + +MOZ_CAN_RUN_SCRIPT void test_arg_deref(RefCountedBase* arg) { + test_arg_deref_helper(*arg); +} + +struct RefCountedDerefTester : public RefCountedBase { + MOZ_CAN_RUN_SCRIPT void foo() { + test_arg_deref_helper(*this); + } +}; + +struct DisallowMemberArgs { + RefPtr<RefCountedBase> mRefCounted; + MOZ_CAN_RUN_SCRIPT void foo() { + mRefCounted->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mRefCounted{{(->)?}}' is neither.}} + + RefPtr<RefCountedBase>& unsafeMemberRef = mRefCounted; + unsafeMemberRef->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'unsafeMemberRef{{(->)?}}' is neither.}} + + RefPtr<RefCountedBase> safeRefCounted = mRefCounted; + RefPtr<RefCountedBase>& maybeUnsafeMemberRef1 = mRefCounted ? mRefCounted : safeRefCounted; + maybeUnsafeMemberRef1->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef1{{(->)?}}' is neither.}} + RefPtr<RefCountedBase>& maybeUnsafeMemberRef2 = safeRefCounted ? safeRefCounted : mRefCounted; + maybeUnsafeMemberRef2->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef2{{(->)?}}' is neither.}} + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mRefCounted); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mRefCounted' is neither.}} + + RefPtr<RefCountedBase>& unsafeMemberRef = mRefCounted; + test2(unsafeMemberRef); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'unsafeMemberRef' is neither.}} + + RefPtr<RefCountedBase> safeRefCounted = mRefCounted; + RefPtr<RefCountedBase>& maybeUnsafeMemberRef1 = mRefCounted ? mRefCounted : safeRefCounted; + test2(maybeUnsafeMemberRef1); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef1' is neither.}} + RefPtr<RefCountedBase>& maybeUnsafeMemberRef2 = safeRefCounted ? safeRefCounted : mRefCounted; + test2(maybeUnsafeMemberRef2); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef2' is neither.}} + } +}; + +struct DisallowMemberArgsWithGet { + RefPtr<RefCountedBase> mRefCounted; + MOZ_CAN_RUN_SCRIPT void foo() { + mRefCounted.get()->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mRefCounted.get()' is neither.}} + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mRefCounted.get()); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mRefCounted.get()' is neither.}} + } +}; + +struct AllowKnownLiveMemberArgs { + RefPtr<RefCountedBase> mRefCounted; + MOZ_CAN_RUN_SCRIPT void foo() { + MOZ_KnownLive(mRefCounted)->method_test(); + + RefPtr<RefCountedBase>& unsafeMemberRef = mRefCounted; + MOZ_KnownLive(unsafeMemberRef)->method_test(); + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(MOZ_KnownLive(mRefCounted)); + + RefPtr<RefCountedBase>& unsafeMemberRef = mRefCounted; + test2(MOZ_KnownLive(unsafeMemberRef)); + } +}; + +struct WeakPtrReturner : public RefCountedBase { + RefCountedBase* getWeakPtr() { return new RefCountedBase(); } +}; + +struct DisallowMemberCallsOnRandomKnownLive { + RefPtr<WeakPtrReturner> mWeakPtrReturner1; + WeakPtrReturner* mWeakPtrReturner2; + + MOZ_CAN_RUN_SCRIPT void test_refptr_method() { + MOZ_KnownLive(mWeakPtrReturner1)->getWeakPtr()->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'MOZ_KnownLive(mWeakPtrReturner1)->getWeakPtr()' is neither.}} + } + + MOZ_CAN_RUN_SCRIPT void test_refptr_function() { + test2(MOZ_KnownLive(mWeakPtrReturner1)->getWeakPtr()); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'MOZ_KnownLive(mWeakPtrReturner1)->getWeakPtr()' is neither.}} + } + + MOZ_CAN_RUN_SCRIPT void test_raw_method() { + MOZ_KnownLive(mWeakPtrReturner2)->getWeakPtr()->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'MOZ_KnownLive(mWeakPtrReturner2)->getWeakPtr()' is neither.}} + } + + MOZ_CAN_RUN_SCRIPT void test_raw_function() { + test2(MOZ_KnownLive(mWeakPtrReturner2)->getWeakPtr()); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'MOZ_KnownLive(mWeakPtrReturner2)->getWeakPtr()' is neither.}} + } +}; + +struct AllowConstMemberArgs { + const RefPtr<RefCountedBase> mRefCounted; + MOZ_CAN_RUN_SCRIPT void foo() { + mRefCounted->method_test(); + + const RefPtr<RefCountedBase>& safeMemberRef1 = mRefCounted; + safeMemberRef1->method_test(); + + const RefPtr<RefCountedBase> safeRefCounted = new RefCountedBase; + const RefPtr<RefCountedBase>& safeMemberRef2 = safeRefCounted ? safeRefCounted : mRefCounted; + safeMemberRef2->method_test(); + const RefPtr<RefCountedBase>& safeMemberRef3 = mRefCounted ? mRefCounted : safeRefCounted; + safeMemberRef3->method_test(); + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mRefCounted); + + const RefPtr<RefCountedBase>& safeMemberRef1 = mRefCounted; + test2(safeMemberRef1); + + const RefPtr<RefCountedBase> safeRefCounted = new RefCountedBase; + const RefPtr<RefCountedBase>& safeMemberRef2 = safeRefCounted ? safeRefCounted : mRefCounted; + test2(safeMemberRef2); + const RefPtr<RefCountedBase>& safeMemberRef3 = mRefCounted ? mRefCounted : safeRefCounted; + test2(safeMemberRef3); + } +}; + +struct AllowConstMemberArgsWithExplicitThis { + const RefPtr<RefCountedBase> mRefCounted; + MOZ_CAN_RUN_SCRIPT void foo() { + this->mRefCounted->method_test(); + + const RefPtr<RefCountedBase>& safeMemberRef1 = this->mRefCounted; + safeMemberRef1->method_test(); + + const RefPtr<RefCountedBase> safeRefCounted = new RefCountedBase; + const RefPtr<RefCountedBase>& safeMemberRef2 = safeRefCounted ? safeRefCounted : this->mRefCounted; + safeMemberRef2->method_test(); + const RefPtr<RefCountedBase>& safeMemberRef3 = this->mRefCounted ? this->mRefCounted : safeRefCounted; + safeMemberRef3->method_test(); + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(this->mRefCounted); + + const RefPtr<RefCountedBase>& safeMemberRef1 = this->mRefCounted; + test2(safeMemberRef1); + + const RefPtr<RefCountedBase> safeRefCounted = new RefCountedBase; + const RefPtr<RefCountedBase>& safeMemberRef2 = safeRefCounted ? safeRefCounted : this->mRefCounted; + test2(safeMemberRef2); + const RefPtr<RefCountedBase>& safeMemberRef3 = this->mRefCounted ? this->mRefCounted : safeRefCounted; + test2(safeMemberRef3); + } +}; + +struct DisallowConstMemberArgsOfMembers { + RefPtr<AllowConstMemberArgs> mMember; + MOZ_CAN_RUN_SCRIPT void foo() { + mMember->mRefCounted->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mMember->mRefCounted{{(->)?}}' is neither.}} + + const RefPtr<RefCountedBase>& unsafeMemberRef = mMember->mRefCounted; + unsafeMemberRef->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'unsafeMemberRef{{(->)?}}' is neither.}} + + const RefPtr<RefCountedBase> safeRefCounted = new RefCountedBase; + const RefPtr<RefCountedBase>& maybeUnsafeMemberRef1 = safeRefCounted ? safeRefCounted : mMember->mRefCounted; + maybeUnsafeMemberRef1->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef1{{(->)?}}' is neither.}} + const RefPtr<RefCountedBase>& maybeUnsafeMemberRef2 = mMember->mRefCounted ? mMember->mRefCounted : safeRefCounted; + maybeUnsafeMemberRef2->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef2{{(->)?}}' is neither.}} + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mMember->mRefCounted); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mMember->mRefCounted' is neither.}} + + const RefPtr<RefCountedBase>& unsafeMemberRef = mMember->mRefCounted; + test2(unsafeMemberRef); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'unsafeMemberRef' is neither.}} + + const RefPtr<RefCountedBase> safeRefCounted = new RefCountedBase; + const RefPtr<RefCountedBase>& maybeUnsafeMemberRef1 = safeRefCounted ? safeRefCounted : mMember->mRefCounted; + test2(maybeUnsafeMemberRef1); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef1' is neither.}} + const RefPtr<RefCountedBase>& maybeUnsafeMemberRef2 = mMember->mRefCounted ? mMember->mRefCounted : safeRefCounted; + test2(maybeUnsafeMemberRef2); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'maybeUnsafeMemberRef2' is neither.}} + } +}; + +struct DisallowConstNonRefPtrMemberArgs { + RefCountedBase* const mRefCounted; + MOZ_CAN_RUN_SCRIPT void foo() { + mRefCounted->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mRefCounted' is neither.}} + + RefCountedBase* const& unsafeMemberRefCounted = mRefCounted; + unsafeMemberRefCounted->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'unsafeMemberRefCounted' is neither.}} + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mRefCounted); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mRefCounted' is neither.}} + + RefCountedBase* const& unsafeMemberRefCounted = mRefCounted; + test2(unsafeMemberRefCounted); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'unsafeMemberRefCounted' is neither.}} + } +}; + +MOZ_CAN_RUN_SCRIPT void test_temporary_1() { +#ifdef MOZ_CLANG_PLUGIN_ALPHA + RefPtr<RefCountedBase>(new RefCountedBase())->method_test(); // expected-warning {{performance issue: temporary 'RefPtr<RefCountedBase>' is only dereferenced here once which involves short-lived AddRef/Release calls}} +#else + RefPtr<RefCountedBase>(new RefCountedBase())->method_test(); +#endif +} + +MOZ_CAN_RUN_SCRIPT void test_temporary_2() { + test_ref(*RefPtr<RefCountedBase>(new RefCountedBase())); +} + +struct WeakSmartPtr { + RefCountedBase* member; + + explicit WeakSmartPtr(RefCountedBase* arg) : member(arg) {} + + RefCountedBase* operator->() const { + return member; + } + + RefCountedBase& operator*() const { + return *member; + } + + operator RefCountedBase*() const { + return member; + } +}; + +MOZ_CAN_RUN_SCRIPT void test_temporary_3() { + WeakSmartPtr(new RefCountedBase())->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'WeakSmartPtr(new RefCountedBase()){{(->)?}}' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_temporary_4() { + test_ref(*WeakSmartPtr(new RefCountedBase())); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*WeakSmartPtr(new RefCountedBase())' is neither.}} +} + +MOZ_CAN_RUN_SCRIPT void test_temporary_5() { + test2(WeakSmartPtr(new RefCountedBase())); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'WeakSmartPtr(new RefCountedBase())' is neither.}} +} + + +template<typename T> +struct TArray { + TArray() { + mArray[0] = new RefCountedBase(); + } + T& operator[](unsigned int index) { return mArray[index]; } + T mArray[1]; +}; + +struct DisallowRawTArrayElement { + TArray<RefCountedBase*> mArray; + MOZ_CAN_RUN_SCRIPT void foo() { + mArray[0]->method_test(); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mArray[0]' is neither.}} + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mArray[0]); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mArray[0]' is neither.}} + } +}; + +struct DisallowRefPtrTArrayElement { + TArray<RefPtr<RefCountedBase>> mArray; + MOZ_CAN_RUN_SCRIPT void foo() { + mArray[0]->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mArray[0]{{(->)?}}' is neither.}} + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mArray[0]); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mArray[0]' is neither.}} + } +}; + +struct AllowConstexprMembers { + static constexpr RefCountedBase* mRefCounted = nullptr; + static constexpr RefCountedBase* mRefCounted2 = nullptr; + MOZ_CAN_RUN_SCRIPT void foo() { + mRefCounted->method_test(); + } + MOZ_CAN_RUN_SCRIPT void bar() { + test2(mRefCounted); + } + MOZ_CAN_RUN_SCRIPT void baz() { + test_ref(*mRefCounted); + } +}; + +MOZ_CAN_RUN_SCRIPT void test_constexpr_1() { + AllowConstexprMembers::mRefCounted->method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_constexpr_2() { + test2(AllowConstexprMembers::mRefCounted); +} + +MOZ_CAN_RUN_SCRIPT void test_constexpr_3() { + test_ref(*AllowConstexprMembers::mRefCounted); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_1(RefCountedBase* arg1, RefCountedBase* arg2) { + (arg1 ? arg1 : arg2)->method_test(); + RefCountedBase*& safeArg = arg1 ? arg1 : arg2; + safeArg->method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_2(RefCountedBase* arg1, RefCountedBase* arg2) { + test2(arg1 ? arg1 : arg2); + RefCountedBase*& safeArg = arg1 ? arg1 : arg2; + test2(safeArg); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_3(RefCountedBase* arg1, RefCountedBase& arg2) { + (arg1 ? *arg1 : arg2).method_test(); + RefCountedBase& safeArg = arg1 ? *arg1 : arg2; + safeArg.method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_4(RefCountedBase* arg1, RefCountedBase& arg2) { + test_ref(arg1 ? *arg1 : arg2); + RefCountedBase& safeArg = arg1 ? *arg1 : arg2; + test_ref(safeArg); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_5(RefCountedBase* arg) { + RefPtr<RefCountedBase> local = new RefCountedBase(); + (arg ? arg : local.get())->method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_6(RefCountedBase* arg) { + RefPtr<RefCountedBase> local = new RefCountedBase(); + test2(arg ? arg : local.get()); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_7(RefCountedBase* arg) { + RefPtr<RefCountedBase> local = new RefCountedBase(); + (arg ? *arg : *local).method_test(); + RefCountedBase& safeArgOrLocal = arg ? *arg : *local; + safeArgOrLocal.method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_8(RefCountedBase* arg) { + RefPtr<RefCountedBase> local = new RefCountedBase(); + test_ref(arg ? *arg : *local); + RefCountedBase& safeArgOrLocal = arg ? *arg : *local; + test_ref(safeArgOrLocal); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_9(RefCountedBase* arg) { + (arg ? arg : AllowConstexprMembers::mRefCounted)->method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_10(RefCountedBase* arg) { + test2(arg ? arg : AllowConstexprMembers::mRefCounted); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_11(RefCountedBase* arg) { + (arg ? *arg : *AllowConstexprMembers::mRefCounted).method_test(); + RefCountedBase& safeArgOrMember = arg ? *arg : *AllowConstexprMembers::mRefCounted; + safeArgOrMember.method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_12(RefCountedBase* arg) { + test_ref(arg ? *arg : *AllowConstexprMembers::mRefCounted); + RefCountedBase& safeArgOrMember = arg ? *arg : *AllowConstexprMembers::mRefCounted; + test_ref(safeArgOrMember); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_13(RefCountedBase* arg1, RefCountedBase& arg2) { + (arg1 ? arg1 : &arg2)->method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_44(RefCountedBase* arg1, RefCountedBase& arg2) { + test2(arg1 ? arg1 : &arg2); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_13(bool arg) { + (arg ? + AllowConstexprMembers::mRefCounted : + AllowConstexprMembers::mRefCounted2)->method_test(); + RefCountedBase* const& safeConstexprMember = arg ? AllowConstexprMembers::mRefCounted : AllowConstexprMembers::mRefCounted2; + safeConstexprMember->method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_14(bool arg) { + test2(arg ? + AllowConstexprMembers::mRefCounted : + AllowConstexprMembers::mRefCounted2); + RefCountedBase* const& safeConstexprMember = arg ? AllowConstexprMembers::mRefCounted : AllowConstexprMembers::mRefCounted2; + test2(safeConstexprMember); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_15(bool arg) { + (arg ? + *AllowConstexprMembers::mRefCounted : + *AllowConstexprMembers::mRefCounted2).method_test(); + RefCountedBase& safeConstexprMember = arg ? *AllowConstexprMembers::mRefCounted : *AllowConstexprMembers::mRefCounted2; + safeConstexprMember.method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_ternary_16(bool arg) { + test_ref(arg ? + *AllowConstexprMembers::mRefCounted : + *AllowConstexprMembers::mRefCounted2); + RefCountedBase& safeConstexprMember = arg ? *AllowConstexprMembers::mRefCounted : *AllowConstexprMembers::mRefCounted2; + test_ref(safeConstexprMember); +} + +MOZ_CAN_RUN_SCRIPT void test_pointer_to_ref_1(RefCountedBase& arg) { + (&arg)->method_test(); +} + +MOZ_CAN_RUN_SCRIPT void test_pointer_to_ref_2(RefCountedBase& arg) { + test2(&arg); +} + +struct DisallowMemberArgsViaReferenceAlias { + RefPtr<RefCountedBase> mRefCounted; + MOZ_CAN_RUN_SCRIPT void foo() { + RefPtr<RefCountedBase>& bogus = mRefCounted; + bogus->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'bogus{{(->)?}}' is neither.}} + } + MOZ_CAN_RUN_SCRIPT void bar() { + RefPtr<RefCountedBase>& bogus = mRefCounted; + test2(bogus); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'bogus' is neither.}} + } +}; + +struct DisallowMemberArgsViaReferenceAlias2 { + RefPtr<RefCountedBase> mRefCountedArr[2]; + MOZ_CAN_RUN_SCRIPT void foo1() { + for (RefPtr<RefCountedBase>& item : mRefCountedArr) { + item->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'item{{(->)?}}' is neither.}} + } + } + MOZ_CAN_RUN_SCRIPT void foo2() { + for (auto& item : mRefCountedArr) { + item->method_test(); // expected-error-re {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'item{{(->)?}}' is neither.}} + } + } + MOZ_CAN_RUN_SCRIPT void foo3() { + for (RefPtr<RefCountedBase> item : mRefCountedArr) { + item->method_test(); + } + } + MOZ_CAN_RUN_SCRIPT void foo4() { + for (auto item : mRefCountedArr) { + item->method_test(); + } + } + MOZ_CAN_RUN_SCRIPT void bar1() { + for (RefPtr<RefCountedBase>& item : mRefCountedArr) { + test2(item); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'item' is neither.}} + } + } + MOZ_CAN_RUN_SCRIPT void bar2() { + for (auto& item : mRefCountedArr) { + test2(item); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'item' is neither.}} + } + } + MOZ_CAN_RUN_SCRIPT void bar3() { + for (RefPtr<RefCountedBase> item : mRefCountedArr) { + test2(item); + } + } + MOZ_CAN_RUN_SCRIPT void bar4() { + for (auto item : mRefCountedArr) { + test2(item); + } + } +}; + +struct AllowMozKnownLiveMember { +public: + MOZ_KNOWN_LIVE RefCountedBase* mWhatever; + MOZ_KNOWN_LIVE RefPtr<RefCountedBase> mRefCountedWhatever; + MOZ_CAN_RUN_SCRIPT void fooPtr(RefCountedBase* aWhatever) {} + MOZ_CAN_RUN_SCRIPT void fooRef(RefCountedBase& aWhatever) {} + MOZ_CAN_RUN_SCRIPT void bar() { + fooPtr(mWhatever); + fooRef(*mWhatever); + fooPtr(mRefCountedWhatever); + fooRef(*mRefCountedWhatever); + + RefCountedBase*& whateverRef = mWhatever; + fooPtr(whateverRef); + fooRef(*whateverRef); + RefPtr<RefCountedBase>& refCountedWhateverRef = mRefCountedWhatever; + fooPtr(refCountedWhateverRef); + fooRef(*refCountedWhateverRef); + } +}; + +struct AllowMozKnownLiveMemberParent : AllowMozKnownLiveMember { + MOZ_CAN_RUN_SCRIPT void baz() { + fooPtr(mWhatever); + fooRef(*mWhatever); + fooPtr(mRefCountedWhatever); + fooRef(*mRefCountedWhatever); + + RefCountedBase*& whateverRef = mWhatever; + fooPtr(whateverRef); + fooRef(*whateverRef); + RefPtr<RefCountedBase>& refCountedWhateverRef = mRefCountedWhatever; + fooPtr(refCountedWhateverRef); + fooRef(*refCountedWhateverRef); + } +}; + +struct AllowMozKnownLiveParamMember { +public: + MOZ_CAN_RUN_SCRIPT void foo(AllowMozKnownLiveMember& aAllow) { + aAllow.fooPtr(aAllow.mWhatever); + aAllow.fooRef(*aAllow.mWhatever); + aAllow.fooPtr(aAllow.mRefCountedWhatever); + aAllow.fooRef(*aAllow.mRefCountedWhatever); + + RefCountedBase*& whateverRef = aAllow.mWhatever; + aAllow.fooPtr(whateverRef); + aAllow.fooRef(*whateverRef); + RefPtr<RefCountedBase>& refCountedWhateverRef = aAllow.mRefCountedWhatever; + aAllow.fooPtr(refCountedWhateverRef); + aAllow.fooRef(*refCountedWhateverRef); + } + MOZ_CAN_RUN_SCRIPT void bar(AllowMozKnownLiveMemberParent& aAllowParent) { + aAllowParent.fooPtr(aAllowParent.mWhatever); + aAllowParent.fooRef(*aAllowParent.mWhatever); + aAllowParent.fooPtr(aAllowParent.mRefCountedWhatever); + aAllowParent.fooRef(*aAllowParent.mRefCountedWhatever); + + RefCountedBase*& whateverRef = aAllowParent.mWhatever; + aAllowParent.fooPtr(whateverRef); + aAllowParent.fooRef(*whateverRef); + RefPtr<RefCountedBase>& refCountedWhateverRef = aAllowParent.mRefCountedWhatever; + aAllowParent.fooPtr(refCountedWhateverRef); + aAllowParent.fooRef(*refCountedWhateverRef); + } +}; + +MOZ_CAN_RUN_SCRIPT void AllowMozKnownLiveMemberInAutoStorage() { + AllowMozKnownLiveMember inStack; + AllowMozKnownLiveMember* inHeap = new AllowMozKnownLiveMember(); + inStack.fooPtr(inStack.mWhatever); + inStack.fooRef(*inStack.mWhatever); + inStack.fooPtr(inStack.mRefCountedWhatever); + inStack.fooRef(*inStack.mRefCountedWhatever); + RefCountedBase*& whateverRefInStack = inStack.mWhatever; + inStack.fooPtr(whateverRefInStack); + inStack.fooRef(*whateverRefInStack); + RefPtr<RefCountedBase>& refCountedWhateverRefInStack = inStack.mRefCountedWhatever; + inStack.fooPtr(refCountedWhateverRefInStack); + inStack.fooRef(*refCountedWhateverRefInStack); + + inStack.fooPtr(inHeap->mWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'inHeap->mWhatever' is neither.}} + inStack.fooRef(*inHeap->mWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*inHeap->mWhatever' is neither.}} + inStack.fooPtr(inHeap->mRefCountedWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'inHeap->mRefCountedWhatever' is neither.}} + inStack.fooRef(*inHeap->mRefCountedWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*inHeap->mRefCountedWhatever' is neither.}} + RefCountedBase*& whateverRefInHeap = inHeap->mWhatever; + inStack.fooPtr(whateverRefInHeap); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'whateverRefInHeap' is neither.}} + inStack.fooRef(*whateverRefInHeap); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*whateverRefInHeap' is neither.}} + RefPtr<RefCountedBase>& refCountedWhateverRefInHeap = inHeap->mRefCountedWhatever; + inStack.fooPtr(refCountedWhateverRefInHeap); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refCountedWhateverRefInHeap' is neither.}} + inStack.fooRef(*refCountedWhateverRefInHeap); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*refCountedWhateverRefInHeap' is neither.}} +} + +struct DisallowMozKnownLiveMemberNotFromKnownLive { + AllowMozKnownLiveMember* mMember; + MOZ_CAN_RUN_SCRIPT void fooPtr(RefCountedBase* aWhatever) {} + MOZ_CAN_RUN_SCRIPT void fooRef(RefCountedBase& aWhatever) {} + MOZ_CAN_RUN_SCRIPT void bar() { + fooPtr(mMember->mWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mMember->mWhatever' is neither.}} + fooRef(*mMember->mWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*mMember->mWhatever' is neither.}} + fooPtr(mMember->mRefCountedWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'mMember->mRefCountedWhatever' is neither.}} + fooRef(*mMember->mRefCountedWhatever); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*mMember->mRefCountedWhatever' is neither.}} + RefCountedBase*& whateverRef = mMember->mWhatever; + fooPtr(whateverRef); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'whateverRef' is neither.}} + fooRef(*whateverRef); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*whateverRef' is neither.}} + RefPtr<RefCountedBase>& refCountedWhateverRef = mMember->mRefCountedWhatever; + fooPtr(refCountedWhateverRef); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). 'refCountedWhateverRef' is neither.}} + fooRef(*refCountedWhateverRef); // expected-error {{arguments must all be strong refs or caller's parameters when calling a function marked as MOZ_CAN_RUN_SCRIPT (including the implicit object argument). '*refCountedWhateverRef' is neither.}} + } +}; + +void IncorrectlyUnmarkedEarlyDeclaration(); // expected-note {{The first declaration exists here}} + +MOZ_CAN_RUN_SCRIPT void IncorrectlyUnmarkedEarlyDeclaration() {}; // expected-error {{MOZ_CAN_RUN_SCRIPT must be put in front of the declaration, not the definition}} diff --git a/build/clang-plugin/tests/TestCustomHeap.cpp b/build/clang-plugin/tests/TestCustomHeap.cpp new file mode 100644 index 0000000000..c1e82f2fa7 --- /dev/null +++ b/build/clang-plugin/tests/TestCustomHeap.cpp @@ -0,0 +1,29 @@ +#define MOZ_NONHEAP_CLASS __attribute__((annotate("moz_nonheap_class"))) +#ifndef MOZ_HEAP_ALLOCATOR +#define MOZ_HEAP_ALLOCATOR \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((annotate("moz_heap_allocator"))) \ + _Pragma("GCC diagnostic pop") +#endif + +#include <stdlib.h> +#include <memory> + +struct MOZ_NONHEAP_CLASS X { +}; + +void *operator new(size_t x, int qual) MOZ_HEAP_ALLOCATOR { + return ::operator new(x); +} + +template <typename T> +T *customAlloc() MOZ_HEAP_ALLOCATOR { + T *arg = static_cast<T*>(malloc(sizeof(T))); + return new (arg) T(); +} + +void misuseX() { + X *foo = customAlloc<X>(); // expected-error {{variable of type 'X' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} + X *foo2 = new (100) X(); // expected-error {{variable of type 'X' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} +} diff --git a/build/clang-plugin/tests/TestDanglingOnTemporary.cpp b/build/clang-plugin/tests/TestDanglingOnTemporary.cpp new file mode 100644 index 0000000000..62a7755ece --- /dev/null +++ b/build/clang-plugin/tests/TestDanglingOnTemporary.cpp @@ -0,0 +1,45 @@ +#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; } + operator int*() + { + return get(); // This should be ignored because the call is implcitly on this + } +}; + +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/TestExplicitOperatorBool.cpp b/build/clang-plugin/tests/TestExplicitOperatorBool.cpp new file mode 100644 index 0000000000..bc4b43a7d0 --- /dev/null +++ b/build/clang-plugin/tests/TestExplicitOperatorBool.cpp @@ -0,0 +1,11 @@ +#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) + +struct Bad { + operator bool(); // expected-error {{bad implicit conversion operator for 'Bad'}} expected-note {{consider adding the explicit keyword to 'operator bool'}} +}; +struct Good { + explicit operator bool(); +}; +struct Okay { + MOZ_IMPLICIT operator bool(); +}; diff --git a/build/clang-plugin/tests/TestFopenUsage.cpp b/build/clang-plugin/tests/TestFopenUsage.cpp new file mode 100644 index 0000000000..19a89f88c9 --- /dev/null +++ b/build/clang-plugin/tests/TestFopenUsage.cpp @@ -0,0 +1,50 @@ +#include <stdio.h> +#include <io.h> +#include <fcntl.h> +#include <fstream> +#include <windows.h> + +void func_fopen() { + FILE *f1 = fopen("dummy.txt", "rt"); // expected-warning {{Usage of ASCII file functions (here fopen) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + FILE *f2; + fopen_s(&f2, "dummy.txt", "rt"); // expected-warning {{Usage of ASCII file functions (here fopen_s) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + + int fh1 = _open("dummy.txt", _O_RDONLY); // expected-warning {{Usage of ASCII file functions (here _open) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + int fh2 = open("dummy.txt", _O_RDONLY); // expected-warning {{Usage of ASCII file functions (here open) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + int fh3 = _sopen("dummy.txt", _O_RDONLY, _SH_DENYRW); // expected-warning {{Usage of ASCII file functions (here _sopen) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + int fd4; + errno_t err = _sopen_s(&fd4, "dummy.txt", _O_RDONLY, _SH_DENYRW, 0); // expected-warning {{Usage of ASCII file functions (here _sopen_s) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + + std::fstream fs1; + fs1.open("dummy.txt"); // expected-warning {{Usage of ASCII file functions (here open) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + std::ifstream ifs1; + ifs1.open("dummy.txt"); // expected-warning {{Usage of ASCII file functions (here open) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + std::ofstream ofs1; + ofs1.open("dummy.txt"); // expected-warning {{Usage of ASCII file functions (here open) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} +#ifdef _MSC_VER + std::fstream fs2; + fs2.open(L"dummy.txt"); + std::ifstream ifs2; + ifs2.open(L"dummy.txt"); + std::ofstream ofs2; + ofs2.open(L"dummy.txt"); +#endif + + LPOFSTRUCT buffer; + HFILE hFile1 = OpenFile("dummy.txt", buffer, OF_READ); // expected-warning {{Usage of ASCII file functions (here OpenFile) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + +#ifndef UNICODE + // CreateFile is just an alias of CreateFileA + LPCSTR buffer2; + HANDLE hFile2 = CreateFile(buffer2, GENERIC_WRITE, 0, NULL, CREATE_NEW, // expected-warning {{Usage of ASCII file functions (here CreateFileA) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + FILE_ATTRIBUTE_NORMAL, NULL); +#else + // CreateFile is just an alias of CreateFileW and should not be matched + LPCWSTR buffer2; + HANDLE hFile2 = CreateFile(buffer2, GENERIC_WRITE, 0, NULL, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, NULL); +#endif + LPCSTR buffer3; + HANDLE hFile3 = CreateFileA(buffer3, GENERIC_WRITE, 0, NULL, CREATE_NEW, // expected-warning {{Usage of ASCII file functions (here CreateFileA) is forbidden on Windows.}} expected-note {{On Windows executed functions: fopen, fopen_s, open, _open, _sopen, _sopen_s, OpenFile, CreateFileA should never be used due to lossy conversion from UTF8 to ANSI.}} + FILE_ATTRIBUTE_NORMAL, NULL); +} diff --git a/build/clang-plugin/tests/TestGlobalClass.cpp b/build/clang-plugin/tests/TestGlobalClass.cpp new file mode 100644 index 0000000000..7ce9c8d463 --- /dev/null +++ b/build/clang-plugin/tests/TestGlobalClass.cpp @@ -0,0 +1,52 @@ +#define MOZ_GLOBAL_CLASS __attribute__((annotate("moz_global_class"))) +#include <stddef.h> + +struct MOZ_GLOBAL_CLASS Global { + int i; + void *operator new(size_t x) throw() { return 0; } + void *operator new(size_t blah, char *buffer) { return buffer; } +}; + +template <class T> +struct MOZ_GLOBAL_CLASS TemplateClass { + T i; +}; + +void gobble(void *) { } + +void misuseGlobalClass(int len) { + Global notValid; // expected-error {{variable of type 'Global' only valid as global}} expected-note {{value incorrectly allocated in an automatic variable}} + Global alsoNotValid[2]; // expected-error-re {{variable of type 'Global{{ ?}}[2]' only valid as global}} expected-note-re {{'Global{{ ?}}[2]' is a global type because it is an array of global type 'Global'}} expected-note {{value incorrectly allocated in an automatic variable}} + static Global valid; + static Global alsoValid[2]; + + gobble(¬Valid); + gobble(&valid); + gobble(&alsoValid[0]); + + gobble(new Global); // expected-error {{variable of type 'Global' only valid as global}} expected-note {{value incorrectly allocated on the heap}} + gobble(new Global[10]); // expected-error {{variable of type 'Global' only valid as global}} expected-note {{value incorrectly allocated on the heap}} + gobble(new TemplateClass<int>); // expected-error {{variable of type 'TemplateClass<int>' only valid as global}} expected-note {{value incorrectly allocated on the heap}} + gobble(len <= 5 ? &valid : new Global); // expected-error {{variable of type 'Global' only valid as global}} expected-note {{value incorrectly allocated on the heap}} + + char buffer[sizeof(Global)]; + gobble(new (buffer) Global); +} + +Global valid; +struct RandomClass { + Global nonstaticMember; // expected-note {{'RandomClass' is a global type because member 'nonstaticMember' is a global type 'Global'}} + static Global staticMember; +}; +struct MOZ_GLOBAL_CLASS RandomGlobalClass { + Global nonstaticMember; + static Global staticMember; +}; + +struct BadInherit : Global {}; // expected-note {{'BadInherit' is a global type because it inherits from a global type 'Global'}} +struct MOZ_GLOBAL_CLASS GoodInherit : Global {}; + +void misuseGlobalClassEvenMore(int len) { + BadInherit moreInvalid; // expected-error {{variable of type 'BadInherit' only valid as global}} expected-note {{value incorrectly allocated in an automatic variable}} + RandomClass evenMoreInvalid; // expected-error {{variable of type 'RandomClass' only valid as global}} expected-note {{value incorrectly allocated in an automatic variable}} +} diff --git a/build/clang-plugin/tests/TestHeapClass.cpp b/build/clang-plugin/tests/TestHeapClass.cpp new file mode 100644 index 0000000000..0cdd939250 --- /dev/null +++ b/build/clang-plugin/tests/TestHeapClass.cpp @@ -0,0 +1,64 @@ +#define MOZ_HEAP_CLASS __attribute__((annotate("moz_heap_class"))) +#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) + +#include <stddef.h> + +struct MOZ_HEAP_CLASS Heap { + int i; + Heap() {} + MOZ_IMPLICIT Heap(int a) {} + Heap(int a, int b) {} + void *operator new(size_t x) throw() { return 0; } + void *operator new(size_t blah, char *buffer) { return buffer; } +}; + +template <class T> +struct MOZ_HEAP_CLASS TemplateClass { + T i; +}; + +void gobble(void *) { } + +void gobbleref(const Heap&) { } + +void misuseHeapClass(int len) { + Heap invalid; // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in an automatic variable}} + Heap alsoInvalid[2]; // expected-error-re {{variable of type 'Heap{{ ?}}[2]' only valid on the heap}} expected-note {{value incorrectly allocated in an automatic variable}} expected-note-re {{'Heap{{ ?}}[2]' is a heap type because it is an array of heap type 'Heap'}} + static Heap invalidStatic; // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a global variable}} + static Heap alsoInvalidStatic[2]; // expected-error-re {{variable of type 'Heap{{ ?}}[2]' only valid on the heap}} expected-note {{value incorrectly allocated in a global variable}} expected-note-re {{'Heap{{ ?}}[2]' is a heap type because it is an array of heap type 'Heap'}} + + gobble(&invalid); + gobble(&invalidStatic); + gobble(&alsoInvalid[0]); + + gobbleref(Heap()); // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a temporary}} + gobbleref(Heap(10, 20)); // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a temporary}} + gobbleref(Heap(10)); // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a temporary}} + gobbleref(10); // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a temporary}} + + gobble(new Heap); + gobble(new Heap[10]); + gobble(new TemplateClass<int>); + gobble(len <= 5 ? &invalid : new Heap); + + char buffer[sizeof(Heap)]; + gobble(new (buffer) Heap); +} + +Heap invalidStatic; // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a global variable}} +struct RandomClass { + Heap nonstaticMember; // expected-note {{'RandomClass' is a heap type because member 'nonstaticMember' is a heap type 'Heap'}} + static Heap staticMember; // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a global variable}} +}; +struct MOZ_HEAP_CLASS RandomHeapClass { + Heap nonstaticMember; + static Heap staticMember; // expected-error {{variable of type 'Heap' only valid on the heap}} expected-note {{value incorrectly allocated in a global variable}} +}; + +struct BadInherit : Heap {}; // expected-note {{'BadInherit' is a heap type because it inherits from a heap type 'Heap'}} +struct MOZ_HEAP_CLASS GoodInherit : Heap {}; + +void useStuffWrongly() { + BadInherit i; // expected-error {{variable of type 'BadInherit' only valid on the heap}} expected-note {{value incorrectly allocated in an automatic variable}} + RandomClass r; // expected-error {{variable of type 'RandomClass' only valid on the heap}} expected-note {{value incorrectly allocated in an automatic variable}} +} diff --git a/build/clang-plugin/tests/TestInheritTypeAnnotationsFromTemplateArgs.cpp b/build/clang-plugin/tests/TestInheritTypeAnnotationsFromTemplateArgs.cpp new file mode 100644 index 0000000000..0c04c3b2bd --- /dev/null +++ b/build/clang-plugin/tests/TestInheritTypeAnnotationsFromTemplateArgs.cpp @@ -0,0 +1,46 @@ +#define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS \ + __attribute__((annotate("moz_inherit_type_annotations_from_template_args"))) +#define MOZ_STACK_CLASS __attribute__((annotate("moz_stack_class"))) +#define MOZ_NON_MEMMOVABLE __attribute__((annotate("moz_non_memmovable"))) +#define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type"))) + +class Normal {}; +class MOZ_STACK_CLASS Stack {}; +class IndirectStack : Stack {}; // expected-note {{'IndirectStack' is a stack type because it inherits from a stack type 'Stack'}} +class ContainsStack { Stack m; }; // expected-note {{'ContainsStack' is a stack type because member 'm' is a stack type 'Stack'}} +class MOZ_NON_MEMMOVABLE Pointery {}; +class IndirectPointery : Pointery {}; // expected-note {{'IndirectPointery' is a non-memmove()able type because it inherits from a non-memmove()able type 'Pointery'}} +class ContainsPointery { Pointery m; }; // expected-note {{'ContainsPointery' is a non-memmove()able type because member 'm' is a non-memmove()able type 'Pointery'}} + +template<class T> +class MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Template {}; // expected-note-re 5 {{'Template<{{.*}}>' is a stack type because it has a template argument stack type '{{.*}}'}} expected-note-re 5 {{'Template<{{.*}}>' is a non-memmove()able type because it has a template argument non-memmove()able type '{{.*}}'}} +class IndirectTemplate : Template<Stack> {}; // expected-note {{'IndirectTemplate' is a stack type because it inherits from a stack type 'Template<Stack>'}} +class ContainsTemplate { Template<Stack> m; }; // expected-note {{'ContainsTemplate' is a stack type because member 'm' is a stack type 'Template<Stack>'}} + +static Template<Stack> a; // expected-error {{variable of type 'Template<Stack>' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +static Template<IndirectStack> b; // expected-error {{variable of type 'Template<IndirectStack>' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +static Template<ContainsStack> c; // expected-error {{variable of type 'Template<ContainsStack>' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +static IndirectTemplate d; // expected-error {{variable of type 'IndirectTemplate' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +static ContainsTemplate e; // expected-error {{variable of type 'ContainsTemplate' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +static Template<Normal> f; + +template<class T> +class MOZ_NEEDS_MEMMOVABLE_TYPE Mover { // expected-error-re 8 {{Cannot instantiate 'Mover<{{.*}}>' with non-memmovable template argument '{{.*}}'}} + char mForceInstantiation[sizeof(T)]; +}; +class IndirectTemplatePointery : Template<Pointery> {}; // expected-note {{'IndirectTemplatePointery' is a non-memmove()able type because it inherits from a non-memmove()able type 'Template<Pointery>'}} +class ContainsTemplatePointery { Template<Pointery> m; }; // expected-note {{'ContainsTemplatePointery' is a non-memmove()able type because member 'm' is a non-memmove()able type 'Template<Pointery>'}} + +static Mover<Template<Pointery>> n; // expected-note-re {{instantiation of 'Mover<Template<Pointery>{{ ?}}>' requested here}} +static Mover<Template<IndirectPointery>> o; // expected-note-re {{instantiation of 'Mover<Template<IndirectPointery>{{ ?}}>' requested here}} +static Mover<Template<ContainsPointery>> p; // expected-note-re {{instantiation of 'Mover<Template<ContainsPointery>{{ ?}}>' requested here}} +static Mover<IndirectTemplatePointery> q; // expected-note {{instantiation of 'Mover<IndirectTemplatePointery>' requested here}} +static Mover<ContainsTemplatePointery> r; // expected-note {{instantiation of 'Mover<ContainsTemplatePointery>' requested here}} +static Mover<Template<Normal>> s; + +template<class T, class... Ts> +class MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS ManyTs {}; // expected-note-re 3 {{'ManyTs<{{.*}}>' is a non-memmove()able type because it has a template argument non-memmove()able type '{{.*}}'}} + +static Mover<ManyTs<Pointery>> t; // expected-note-re {{instantiation of 'Mover<ManyTs<Pointery>{{ ?}}>' requested here}} +static Mover<ManyTs<Normal, Pointery>> u; // expected-note-re {{instantiation of 'Mover<ManyTs<Normal, Pointery>{{ ?}}>' requested here}} +static Mover<ManyTs<Normal, Normal, Pointery>> v; // expected-note-re {{instantiation of 'Mover<ManyTs<Normal, Normal, Pointery>{{ ?}}>' requested here}} diff --git a/build/clang-plugin/tests/TestJSHandleRootedTypedef.cpp b/build/clang-plugin/tests/TestJSHandleRootedTypedef.cpp new file mode 100644 index 0000000000..a0489e09ef --- /dev/null +++ b/build/clang-plugin/tests/TestJSHandleRootedTypedef.cpp @@ -0,0 +1,168 @@ +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "js/GCVector.h" +#include "js/Id.h" + +class Foo { + void HandleFunction(JS::HandleFunction){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleId(JS::HandleId){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleObject(JS::HandleObject){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleScript(JS::HandleScript){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleString(JS::HandleString){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleSymbol(JS::HandleSymbol){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleBigInt(JS::HandleBigInt){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleValue(JS::HandleValue){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleValueVector(JS::HandleValueVector){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleObjectVector(JS::HandleObjectVector){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void HandleIdVector(JS::HandleIdVector){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + void MutableHandleFunction(JS::MutableHandleFunction){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleId(JS::MutableHandleId){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleObject(JS::MutableHandleObject){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleScript(JS::MutableHandleScript){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleString(JS::MutableHandleString){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleSymbol(JS::MutableHandleSymbol){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleBigInt(JS::MutableHandleBigInt){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleValue(JS::MutableHandleValue){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleValueVector(JS::MutableHandleValueVector){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleObjectVector(JS::MutableHandleObjectVector){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + void MutableHandleIdVector(JS::MutableHandleIdVector){}; // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + // Examples of preferred forms + void FullHandleFunction(JS::Handle<JSFunction*>){}; + void FullHandleId(JS::Handle<JS::PropertyKey>){}; + void FullHandleObject(JS::Handle<JSObject*>){}; + void FullHandleScript(JS::Handle<JSScript*>){}; + void FullHandleString(JS::Handle<JSString*>){}; + void FullHandleSymbol(JS::Handle<JS::Symbol*>){}; + void FullHandleBigInt(JS::Handle<JS::BigInt*>){}; + void FullHandleValue(JS::Handle<JS::Value>){}; + void FullHandleValueVector(JS::Handle<JS::StackGCVector<JS::Value>>){}; + void FullHandleObjectVector(JS::Handle<JS::StackGCVector<JSObject*>>){}; + void FullHandleIdVector(JS::HandleVector<JS::PropertyKey>){}; + void FullHandleValueVectorShorter(JS::HandleVector<JS::Value>){}; + void FullHandleObjectVectorShorter(JS::HandleVector<JSObject*>){}; + void FullHandleIdVectorShorter(JS::HandleVector<JS::PropertyKey>){}; + + void FullMutableHandleFunction(JS::MutableHandle<JSFunction*>){}; + void FullMutableHandleId(JS::MutableHandle<JS::PropertyKey>){}; + void FullMutableHandleObject(JS::MutableHandle<JSObject*>){}; + void FullMutableHandleScript(JS::MutableHandle<JSScript*>){}; + void FullMutableHandleString(JS::MutableHandle<JSString*>){}; + void FullMutableHandleSymbol(JS::MutableHandle<JS::Symbol*>){}; + void FullMutableHandleBigInt(JS::MutableHandle<JS::BigInt*>){}; + void FullMutableHandleValue(JS::MutableHandle<JS::Value*>){}; + void FullMutableHandleValueVector(JS::MutableHandle<JS::StackGCVector<JS::Value>>){}; + void FullMutableHandleObjectVector(JS::MutableHandle<JS::StackGCVector<JSObject*>>){}; + void FullMutableHandleIdVector(JS::MutableHandle<JS::StackGCVector<JS::PropertyKey>>){}; + void FullMutableHandleValueVectorShorter(JS::MutableHandleVector<JS::Value>){}; + void FullMutableHandleObjectVectorShorter(JS::MutableHandleVector<JSObject*>){}; + void FullMutableHandleIdVectorShorter(JS::MutableHandleVector<JS::PropertyKey>){}; +}; + +static void Bar(JSContext *aCx) { + JS::RootedObject RootedObject(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedFunction RootedFunction(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedScript RootedScript(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedString RootedString(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedSymbol RootedSymbol(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedBigInt RootedBigInt(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedId RootedId(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedValue RootedValue(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + JS::RootedValueVector RootedValueVector(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedObjectVector RootedObjectVector(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::RootedIdVector RootedIdVector(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + JS::PersistentRootedFunction PersistentRootedFunction(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedId PersistentRootedId(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedObject PersistentRootedObject(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedScript PersistentRootedScript(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedString PersistentRootedString(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedSymbol PersistentRootedSymbol(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedBigInt PersistentRootedBigInt(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedValue PersistentRootedValue(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + JS::PersistentRootedIdVector PersistentRootedIdVector(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::PersistentRootedObjectVector PersistentRootedObjectVector(aCx); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + JS::HandleFunction HandleFunction(RootedFunction); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleId HandleId(RootedId); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleObject HandleObject(RootedObject); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleScript HandleScript(RootedScript); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleString HandleString(RootedString); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleSymbol HandleSymbol(RootedSymbol); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleBigInt HandleBigInt(RootedBigInt); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleValue HandleValue(RootedValue); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleValueVector HandleValueVector(RootedValueVector); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleObjectVector HandleObjectVector(RootedObjectVector); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::HandleIdVector HandleIdVector(RootedIdVector); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + JS::MutableHandleFunction MutableHandleFunction(&RootedFunction); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleId MutableHandleId(&RootedId); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleObject MutableHandleObject(&RootedObject); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleScript MutableHandleScript(&RootedScript); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleString MutableHandleString(&RootedString); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleSymbol MutableHandleSymbol(&RootedSymbol); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleBigInt MutableHandleBigInt(&RootedBigInt); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleValue MutableHandleValue(&RootedValue); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleValueVector MutableHandleValueVector(&RootedValueVector); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleObjectVector MutableHandleObjectVector(&RootedObjectVector); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + JS::MutableHandleIdVector MutableHandleIdVector(&RootedIdVector); // expected-error {{The fully qualified types are preferred over the shorthand typedefs for JS::Handle/JS::Rooted types outside SpiderMonkey.}} + + // Examples of preferred forms + JS::Rooted<JSObject*> FullRootedObject(aCx); + JS::Rooted<JSFunction*> FullRootedFunction(aCx); + JS::Rooted<JSScript*> FullRootedScript(aCx); + JS::Rooted<JSString*> FullRootedString(aCx); + JS::Rooted<JS::Symbol*> FullRootedSymbol(aCx); + JS::Rooted<JS::BigInt*> FullRootedBigInt(aCx); + JS::Rooted<JS::PropertyKey> FullRootedId(aCx); + JS::Rooted<JS::Value> FullRootedValue(aCx); + + JS::RootedVector<JS::Value> FullRootedValueVector(aCx); + JS::RootedVector<JSObject*> FullRootedObjectVector(aCx); + JS::RootedVector<JS::PropertyKey> FullRootedIdVector(aCx); + + JS::PersistentRooted<JSFunction*> FullPersistentRootedFunction(aCx); + JS::PersistentRooted<JS::PropertyKey> FullPersistentRootedId(aCx); + JS::PersistentRooted<JSObject*> FullPersistentRootedObject(aCx); + JS::PersistentRooted<JSScript*> FullPersistentRootedScript(aCx); + JS::PersistentRooted<JSString*> FullPersistentRootedString(aCx); + JS::PersistentRooted<JS::Symbol*> FullPersistentRootedSymbol(aCx); + JS::PersistentRooted<JS::BigInt*> FullPersistentRootedBigInt(aCx); + JS::PersistentRooted<JS::Value> FullPersistentRootedValue(aCx); + + JS::PersistentRootedVector<JS::PropertyKey> FullPersistentRootedIdVector(aCx); + JS::PersistentRootedVector<JSObject*> FullPersistentRootedObjectVector(aCx); + + JS::Handle<JSFunction*> FullHandleFunction(FullRootedFunction); + JS::Handle<JS::PropertyKey> FullHandleId(FullRootedId); + JS::Handle<JSObject*> FullHandleObject(FullRootedObject); + JS::Handle<JSScript*> FullHandleScript(FullRootedScript); + JS::Handle<JSString*> FullHandleString(FullRootedString); + JS::Handle<JS::Symbol*> FullHandleSymbol(FullRootedSymbol); + JS::Handle<JS::BigInt*> FullHandleBigInt(FullRootedBigInt); + JS::Handle<JS::Value> FullHandleValue(FullRootedValue); + JS::Handle<JS::StackGCVector<JS::Value>> FullHandleValueVector(FullRootedValueVector); + JS::Handle<JS::StackGCVector<JSObject*>> FullHandleObjectVector(FullRootedObjectVector); + JS::Handle<JS::StackGCVector<JS::PropertyKey>> FullHandleIdVector(FullRootedIdVector); + JS::HandleVector<JS::Value> FullHandleValueVectorShorter(FullRootedValueVector); + JS::HandleVector<JSObject*> FullHandleObjectVectorShorter(FullRootedObjectVector); + JS::HandleVector<JS::PropertyKey> FullHandleIdVectorShorter(FullRootedIdVector); + + JS::MutableHandle<JSFunction*> FullMutableHandleFunction(&FullRootedFunction); + JS::MutableHandle<JS::PropertyKey> FullMutableHandleId(&FullRootedId); + JS::MutableHandle<JSObject*> FullMutableHandleObject(&FullRootedObject); + JS::MutableHandle<JSScript*> FullMutableHandleScript(&FullRootedScript); + JS::MutableHandle<JSString*> FullMutableHandleString(&FullRootedString); + JS::MutableHandle<JS::Symbol*> FullMutableHandleSymbol(&FullRootedSymbol); + JS::MutableHandle<JS::BigInt*> FullMutableHandleBigInt(&FullRootedBigInt); + JS::MutableHandle<JS::Value> FullMutableHandleValue(&FullRootedValue); + JS::MutableHandle<JS::StackGCVector<JS::Value>> FullMutableHandleValueVector(&FullRootedValueVector); + JS::MutableHandle<JS::StackGCVector<JSObject*>> FullMutableHandleObjectVector(&FullRootedObjectVector); + JS::MutableHandle<JS::StackGCVector<JS::PropertyKey>> FullMutableHandleIdVector(&FullRootedIdVector); + JS::MutableHandleVector<JS::Value> FullMutableHandleValueVectorShorter(&FullRootedValueVector); + JS::MutableHandleVector<JSObject*> FullMutableHandleObjectVectorShorter(&FullRootedObjectVector); + JS::MutableHandleVector<JS::PropertyKey> FullMutableHandleIdVectorShorter(&FullRootedIdVector); +} diff --git a/build/clang-plugin/tests/TestKnownLive.cpp b/build/clang-plugin/tests/TestKnownLive.cpp new file mode 100644 index 0000000000..8d01160d1d --- /dev/null +++ b/build/clang-plugin/tests/TestKnownLive.cpp @@ -0,0 +1,33 @@ +#include <mozilla/RefPtr.h> + +#define MOZ_KNOWN_LIVE __attribute__((annotate("moz_known_live"))) + +class Foo { + // dummy refcounting +public: + uint32_t AddRef() { return 0; } + uint32_t Release() { return 0; } + +private: + ~Foo() = default; +}; + +class Bar { + MOZ_KNOWN_LIVE RefPtr<Foo> mFoo; + Bar() : mFoo(new Foo()) {} + ~Bar() { mFoo = nullptr; } + + void Baz() { + mFoo = nullptr; // expected-error {{MOZ_KNOWN_LIVE members can only be modified by constructors and destructors}} + } +}; + +class Bar2 { + MOZ_KNOWN_LIVE Foo *mFoo; + Bar2() : mFoo(new Foo()) {} + ~Bar2() { mFoo = nullptr; } + + void Baz() { + mFoo = nullptr; // expected-error {{MOZ_KNOWN_LIVE members can only be modified by constructors and destructors}} + } +}; diff --git a/build/clang-plugin/tests/TestKungFuDeathGrip.cpp b/build/clang-plugin/tests/TestKungFuDeathGrip.cpp new file mode 100644 index 0000000000..0218015807 --- /dev/null +++ b/build/clang-plugin/tests/TestKungFuDeathGrip.cpp @@ -0,0 +1,142 @@ +#include <utility> + +#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) + +template <typename T> +class already_AddRefed { +public: + already_AddRefed(); + T* mPtr; +}; + +template <typename T> +class RefPtr { +public: + RefPtr(); + MOZ_IMPLICIT RefPtr(T* aIn); + MOZ_IMPLICIT RefPtr(already_AddRefed<T> aIn); + + RefPtr(const RefPtr<T>& aOther) = default; + RefPtr& operator=(const RefPtr<T>&) = default; + + // We must define non-defaulted move operations as in the real RefPtr to make + // the type non-trivially-copyable. + RefPtr(RefPtr<T>&&); + RefPtr& operator=(RefPtr<T>&&); + + void swap(RefPtr<T>& aOther); + + ~RefPtr(); + T* mPtr; +}; + +template <typename T> +class nsCOMPtr { +public: + nsCOMPtr(); + MOZ_IMPLICIT nsCOMPtr(T* aIn); + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<T> aIn); + ~nsCOMPtr(); + T* mPtr; +}; + +class Type { +public: + static nsCOMPtr<Type> someStaticCOMPtr; + + void f(nsCOMPtr<Type> ignoredArgument, Type *param) { + nsCOMPtr<Type> never_referenced; + nsCOMPtr<Type> kfdg_t1(this); + nsCOMPtr<Type> kfdg_t2 = this; + nsCOMPtr<Type> kfdg_t3 = (this); + + nsCOMPtr<Type> kfdg_m1(p); // expected-error {{Unused "kungFuDeathGrip" 'nsCOMPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m1', or explicitly pass 'kfdg_m1' to `mozilla::Unused`}} + nsCOMPtr<Type> kfdg_m2 = p; // expected-error {{Unused "kungFuDeathGrip" 'nsCOMPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m2', or explicitly pass 'kfdg_m2' to `mozilla::Unused`}} + nsCOMPtr<Type> kfdg_m3(p); + kfdg_m3.mPtr->f(nullptr, nullptr); + nsCOMPtr<Type> kfdg_m4 = p; + kfdg_m4.mPtr->f(nullptr, nullptr); + + nsCOMPtr<Type> kfdg_a1((already_AddRefed<Type>())); + nsCOMPtr<Type> kfdg_a2 = already_AddRefed<Type>(); + + nsCOMPtr<Type> kfdg_p1(param); + nsCOMPtr<Type> kfdg_p2 = param; + + + RefPtr<Type> never_referenced2; + RefPtr<Type> kfdg_t4(this); + RefPtr<Type> kfdg_t5 = this; + RefPtr<Type> kfdg_t6 = (this); + + RefPtr<Type> kfdg_m5(p); // expected-error {{Unused "kungFuDeathGrip" 'RefPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m5', or explicitly pass 'kfdg_m5' to `mozilla::Unused`}} + RefPtr<Type> kfdg_m6 = p; // expected-error {{Unused "kungFuDeathGrip" 'RefPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m6', or explicitly pass 'kfdg_m6' to `mozilla::Unused`}} + RefPtr<Type> kfdg_m7(p); + kfdg_m7.mPtr->f(nullptr, nullptr); + RefPtr<Type> kfdg_m8 = p; + kfdg_m8.mPtr->f(nullptr, nullptr); + + RefPtr<Type> kfdg_a3((already_AddRefed<Type>())); + RefPtr<Type> kfdg_a4 = already_AddRefed<Type>(); + + RefPtr<Type> kfdg_p3(param); + RefPtr<Type> kfdg_p4 = param; + } + + Type *p; +}; + +struct Type2 { + void f() { + mWeakRef->f(nullptr, nullptr); + } + + void g() { + RefPtr<Type> kfdg; + kfdg.swap(mStrongRef); + f(); + } + + void h() { + RefPtr<Type> kfdg = std::move(mStrongRef); + f(); + } + + RefPtr<Type> mStrongRef; + Type* mWeakRef; +}; + +void f(nsCOMPtr<Type> ignoredArgument, Type *param) { + nsCOMPtr<Type> never_referenced; + Type t; + // Type *p = nullptr; + nsCOMPtr<Type> kfdg_m1(t.p); // expected-error {{Unused "kungFuDeathGrip" 'nsCOMPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m1', or explicitly pass 'kfdg_m1' to `mozilla::Unused`}} + nsCOMPtr<Type> kfdg_m2 = t.p; // expected-error {{Unused "kungFuDeathGrip" 'nsCOMPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m2', or explicitly pass 'kfdg_m2' to `mozilla::Unused`}} + nsCOMPtr<Type> kfdg_m3(t.p); + kfdg_m3.mPtr->f(nullptr, nullptr); + nsCOMPtr<Type> kfdg_m4 = t.p; + kfdg_m4.mPtr->f(nullptr, nullptr); + + nsCOMPtr<Type> kfdg_a1((already_AddRefed<Type>())); + nsCOMPtr<Type> kfdg_a2 = already_AddRefed<Type>(); + + nsCOMPtr<Type> kfdg_p1(param); + nsCOMPtr<Type> kfdg_p2 = param; + + + RefPtr<Type> never_referenced2; + RefPtr<Type> kfdg_m5(t.p); // expected-error {{Unused "kungFuDeathGrip" 'RefPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m5', or explicitly pass 'kfdg_m5' to `mozilla::Unused`}} + RefPtr<Type> kfdg_m6 = t.p; // expected-error {{Unused "kungFuDeathGrip" 'RefPtr<Type>' objects constructed from members are prohibited}} expected-note {{Please switch all accesses to this member to go through 'kfdg_m6', or explicitly pass 'kfdg_m6' to `mozilla::Unused`}} + RefPtr<Type> kfdg_m7(t.p); + kfdg_m7.mPtr->f(nullptr, nullptr); + RefPtr<Type> kfdg_m8 = t.p; + kfdg_m8.mPtr->f(nullptr, nullptr); + + RefPtr<Type> kfdg_a3((already_AddRefed<Type>())); + RefPtr<Type> kfdg_a4 = already_AddRefed<Type>(); + + RefPtr<Type> kfdg_p3(param); + RefPtr<Type> kfdg_p4 = param; +} + +nsCOMPtr<Type> Type::someStaticCOMPtr(nullptr); diff --git a/build/clang-plugin/tests/TestLoadLibraryUsage.cpp b/build/clang-plugin/tests/TestLoadLibraryUsage.cpp new file mode 100644 index 0000000000..319c9d6b2a --- /dev/null +++ b/build/clang-plugin/tests/TestLoadLibraryUsage.cpp @@ -0,0 +1,20 @@ +#include <windows.h> +#include "prlink.h" + +void Func() { + auto h1 = PR_LoadLibrary(nullptr); // expected-error {{Usage of ASCII file functions (such as PR_LoadLibrary) is forbidden.}} + auto h2 = PR_LoadLibrary("C:\\Some\\Path"); + auto h3 = LoadLibraryA(nullptr); // expected-error {{Usage of ASCII file functions (such as LoadLibraryA) is forbidden.}} + auto h4 = LoadLibraryA("C:\\Some\\Path"); + auto h5 = LoadLibraryExA(nullptr, nullptr, 0); // expected-error {{Usage of ASCII file functions (such as LoadLibraryExA) is forbidden.}} + auto h6 = LoadLibraryExA("C:\\Some\\Path", nullptr, 0); + +#ifndef UNICODE + // LoadLibrary is a defnine for LoadLibraryA + auto h7 = LoadLibrary(nullptr); // expected-error {{Usage of ASCII file functions (such as LoadLibraryA) is forbidden.}} + auto h8 = LoadLibrary("C:\\Some\\Path"); + // LoadLibraryEx is a define for LoadLibraryExA + auto h9 = LoadLibraryEx(nullptr, nullptr, 0); // expected-error {{Usage of ASCII file functions (such as LoadLibraryExA) is forbidden.}} + auto h10 = LoadLibraryEx("C:\\Some\\Path", nullptr, 0); +#endif +} diff --git a/build/clang-plugin/tests/TestMultipleAnnotations.cpp b/build/clang-plugin/tests/TestMultipleAnnotations.cpp new file mode 100644 index 0000000000..034367a58e --- /dev/null +++ b/build/clang-plugin/tests/TestMultipleAnnotations.cpp @@ -0,0 +1,19 @@ +#define MOZ_NON_TEMPORARY_CLASS __attribute__((annotate("moz_non_temporary_class"))) +#define MOZ_STACK_CLASS __attribute__((annotate("moz_stack_class"))) + +class MOZ_NON_TEMPORARY_CLASS MOZ_STACK_CLASS TestClass {}; + +TestClass foo; // expected-error {{variable of type 'TestClass' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} + +TestClass f() +{ + TestClass bar; + return bar; +} + +void gobbleref(const TestClass&) { } + +void g() +{ + gobbleref(f()); // expected-error {{variable of type 'TestClass' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} +} diff --git a/build/clang-plugin/tests/TestMustOverride.cpp b/build/clang-plugin/tests/TestMustOverride.cpp new file mode 100644 index 0000000000..8e053f6c23 --- /dev/null +++ b/build/clang-plugin/tests/TestMustOverride.cpp @@ -0,0 +1,63 @@ +#define MOZ_MUST_OVERRIDE __attribute__((annotate("moz_must_override"))) +// Ignore warnings not related to static analysis here +#pragma GCC diagnostic ignored "-Woverloaded-virtual" + +struct S { + virtual void f() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} + virtual void g() MOZ_MUST_OVERRIDE; + virtual void h() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} +}; +struct C : S { // expected-error {{'C' must override 'f'}} expected-error {{'C' must override 'h'}} + virtual void g() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} + virtual void h(int); + void q() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} +}; +struct D : C { // expected-error {{'D' must override 'g'}} expected-error {{'D' must override 'q'}} + virtual void f(); +}; + +struct Base { + virtual void VirtMethod() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} + void NonVirtMethod() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} + static void StaticMethod() MOZ_MUST_OVERRIDE; +}; + +struct DoesNotPropagate : Base { + virtual void VirtMethod(); + void NonVirtMethod(); + static void StaticMethod(); +}; + +struct Final : DoesNotPropagate { }; + +struct Propagates : Base { + virtual void VirtMethod() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} + void NonVirtMethod() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} + static void StaticMethod() MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} +}; + +struct FailsFinal : Propagates { }; // expected-error {{'FailsFinal' must override 'VirtMethod'}} expected-error {{'FailsFinal' must override 'NonVirtMethod'}} expected-error {{'FailsFinal' must override 'StaticMethod'}} + +struct WrongOverload : Base { // expected-error {{'WrongOverload' must override 'VirtMethod'}} expected-error {{'WrongOverload' must override 'NonVirtMethod'}} + virtual void VirtMethod() const; + void NonVirtMethod(int param); + static void StaticMethod(); +}; + +namespace A { namespace B { namespace C { + struct Param {}; + struct Base { + void f(Param p) MOZ_MUST_OVERRIDE; // expected-note {{function to override is here}} + }; +}}} + +struct Param {}; + +struct Derived : A::B::C::Base { + typedef A::B::C::Param Typedef; + void f(Typedef t); +}; + +struct BadDerived : A::B::C::Base { // expected-error {{'BadDerived' must override 'f'}} + void f(Param p); +}; diff --git a/build/clang-plugin/tests/TestMustReturnFromCaller.cpp b/build/clang-plugin/tests/TestMustReturnFromCaller.cpp new file mode 100644 index 0000000000..c935be3cf8 --- /dev/null +++ b/build/clang-plugin/tests/TestMustReturnFromCaller.cpp @@ -0,0 +1,270 @@ +#include <cstddef> +#include <utility> + +#define MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG __attribute__((annotate("moz_must_return_from_caller_if_this_is_arg"))) +#define MOZ_MAY_CALL_AFTER_MUST_RETURN __attribute__((annotate("moz_may_call_after_must_return"))) + +struct Thrower { + void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw() {} +}; + +void DoAnythingElse(); +int MakeAnInt(); +int MOZ_MAY_CALL_AFTER_MUST_RETURN SafeMakeInt(); +bool Condition(); + +// It might be nicer to #include "mozilla/ScopeExit.h" and use that here -- but +// doing so also will #define the two attribute-macros defined above, running a +// risk of redefinition errors. Just stick to the normal clang-plugin test +// style and use as little external code as possible. + +template<typename Func> +class ScopeExit { + Func exitFunction; + bool callOnDestruction; +public: + explicit ScopeExit(Func&& func) + : exitFunction(std::move(func)) + , callOnDestruction(true) + {} + + ~ScopeExit() { + if (callOnDestruction) { + exitFunction(); + } + } + + void release() { callOnDestruction = false; } +}; + +template<typename ExitFunction> +ScopeExit<ExitFunction> +MakeScopeExit(ExitFunction&& func) +{ + return ScopeExit<ExitFunction>(std::move(func)); +} + +class Foo { +public: + __attribute__((annotate("moz_implicit"))) Foo(std::nullptr_t); + Foo(); +}; + +void a1(Thrower& thrower) { + thrower.Throw(); +} + +int a2(Thrower& thrower) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + return MakeAnInt(); +} + +int a3(Thrower& thrower) { + // RAII operations happening after a must-immediately-return are fine. + auto atExit = MakeScopeExit([] { DoAnythingElse(); }); + thrower.Throw(); + return 5; +} + +int a4(Thrower& thrower) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + return Condition() ? MakeAnInt() : MakeAnInt(); +} + +void a5(Thrower& thrower) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + DoAnythingElse(); +} + +int a6(Thrower& thrower) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + DoAnythingElse(); + return MakeAnInt(); +} + +int a7(Thrower& thrower) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + DoAnythingElse(); + return Condition() ? MakeAnInt() : MakeAnInt(); +} + +int a8(Thrower& thrower) { + thrower.Throw(); + return SafeMakeInt(); +} + +int a9(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return SafeMakeInt(); +} + +int a10(Thrower& thrower) { + auto atExit = MakeScopeExit([] { DoAnythingElse(); }); + + if (Condition()) { + thrower.Throw(); + return SafeMakeInt(); + } + + atExit.release(); + DoAnythingElse(); + return 5; +} + +void b1(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } +} + +int b2(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + } + return MakeAnInt(); +} + +int b3(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return 5; +} + +// Explicit test in orer to also verify the `UnaryOperator` node in the `CFG` +int b3a(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return -1; +} + +float b3b(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return 1.0f; +} + +bool b3c(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return false; +} + +int b4(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + } + return Condition() ? MakeAnInt() : MakeAnInt(); +} + +void b5(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + } + DoAnythingElse(); +} + +void b6(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + DoAnythingElse(); + } +} + +void b7(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + return; + } + DoAnythingElse(); +} + +void b8(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + DoAnythingElse(); + return; + } + DoAnythingElse(); +} + +void b9(Thrower& thrower) { + while (Condition()) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + } +} + +void b10(Thrower& thrower) { + while (Condition()) { + thrower.Throw(); + return; + } +} + +void b11(Thrower& thrower) { + thrower.Throw(); // expected-error {{You must immediately return after calling this function}} + if (Condition()) { + return; + } else { + return; + } +} + +void b12(Thrower& thrower) { + switch (MakeAnInt()) { + case 1: + break; + default: + thrower.Throw(); + return; + } +} + +void b13(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return; +} + +Foo b14(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + return nullptr; + } + return nullptr; +} + +Foo b15(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return nullptr; +} + +Foo b16(Thrower& thrower) { + if (Condition()) { + thrower.Throw(); + } + return Foo(); +} + +void c1() { + Thrower thrower; + thrower.Throw(); + DoAnythingElse(); // Should be allowed, since our thrower is not an arg +} + +class TestRet { + TestRet *b13(Thrower &thrower) { + if (Condition()) { + thrower.Throw(); + } + return this; + } +}; diff --git a/build/clang-plugin/tests/TestNANTestingExpr.cpp b/build/clang-plugin/tests/TestNANTestingExpr.cpp new file mode 100644 index 0000000000..9aac46d036 --- /dev/null +++ b/build/clang-plugin/tests/TestNANTestingExpr.cpp @@ -0,0 +1,24 @@ +#line 1 "tests/SkScalar.h" +// This checks that the whitelist accounts for #line directives and such. If you +// remove SkScalar from the whitelist, please change the filename here instead +// of adding expected diagnostics. +inline int headerSays(double x) { + return x != x; +} +#line 9 "TestNANTestingExpr.cpp" +void test(bool x); +void foo() { + float f, f2; + typedef double mydouble; + mydouble d; + double d2; + test(f == f); // expected-error{{comparing a floating point value to itself for NaN checking can lead to incorrect results}} expected-note{{consider using std::isnan instead}} + test(d == d); // expected-error{{comparing a floating point value to itself for NaN checking can lead to incorrect results}} expected-note{{consider using std::isnan instead}} + test(f != f); // expected-error{{comparing a floating point value to itself for NaN checking can lead to incorrect results}} expected-note{{consider using std::isnan instead}} + test(d != d); // expected-error{{comparing a floating point value to itself for NaN checking can lead to incorrect results}} expected-note{{consider using std::isnan instead}} + test(f != d); + test(d == (d - f)); + test(f == f2); + test(d == d2); + test(d + 1 == d); +} diff --git a/build/clang-plugin/tests/TestNANTestingExprC.c b/build/clang-plugin/tests/TestNANTestingExprC.c new file mode 100644 index 0000000000..ab2fead22a --- /dev/null +++ b/build/clang-plugin/tests/TestNANTestingExprC.c @@ -0,0 +1,17 @@ +/* expected-no-diagnostics */ +void test(int x); +void foo() { + float f, f2; + typedef double mydouble; + mydouble d; + double d2; + test(f == f); + test(d == d); + test(f != f); + test(d != d); + test(f != d); + test(d == (d - f)); + test(f == f2); + test(d == d2); + test(d + 1 == d); +} diff --git a/build/clang-plugin/tests/TestNeedsNoVTableType.cpp b/build/clang-plugin/tests/TestNeedsNoVTableType.cpp new file mode 100644 index 0000000000..9b7c405d80 --- /dev/null +++ b/build/clang-plugin/tests/TestNeedsNoVTableType.cpp @@ -0,0 +1,94 @@ +#define MOZ_NEEDS_NO_VTABLE_TYPE __attribute__((annotate("moz_needs_no_vtable_type"))) + +template <class T> +struct MOZ_NEEDS_NO_VTABLE_TYPE PickyConsumer { // expected-error {{'PickyConsumer<B>' cannot be instantiated because 'B' has a VTable}} expected-error {{'PickyConsumer<E>' cannot be instantiated because 'E' has a VTable}} expected-error {{'PickyConsumer<F>' cannot be instantiated because 'F' has a VTable}} expected-error {{'PickyConsumer<G>' cannot be instantiated because 'G' has a VTable}} + T *m; +}; + +template <class T> +struct MOZ_NEEDS_NO_VTABLE_TYPE PickyConsumer_A { // expected-error {{'PickyConsumer_A<B>' cannot be instantiated because 'B' has a VTable}} expected-error {{'PickyConsumer_A<E>' cannot be instantiated because 'E' has a VTable}} expected-error {{'PickyConsumer_A<F>' cannot be instantiated because 'F' has a VTable}} expected-error {{'PickyConsumer_A<G>' cannot be instantiated because 'G' has a VTable}} + T *m; +}; +template <class T> +struct PickyConsumerWrapper { + PickyConsumer_A<T> m; // expected-note {{bad instantiation of 'PickyConsumer_A<B>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_A<E>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_A<F>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_A<G>' requested here}} +}; + +template <class T> +struct MOZ_NEEDS_NO_VTABLE_TYPE PickyConsumer_B { // expected-error {{'PickyConsumer_B<B>' cannot be instantiated because 'B' has a VTable}} expected-error {{'PickyConsumer_B<E>' cannot be instantiated because 'E' has a VTable}} expected-error {{'PickyConsumer_B<F>' cannot be instantiated because 'F' has a VTable}} expected-error {{'PickyConsumer_B<G>' cannot be instantiated because 'G' has a VTable}} + T *m; +}; +template <class T> +struct PickyConsumerSubclass : PickyConsumer_B<T> {}; // expected-note {{bad instantiation of 'PickyConsumer_B<B>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_B<E>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_B<F>' requested here}} expected-note {{bad instantiation of 'PickyConsumer_B<G>' requested here}} + +template <class T> +struct NonPickyConsumer { + T *m; +}; + +struct A {}; +struct B : virtual A {}; +struct C : A {}; +struct D { + void d(); +}; +struct E { + virtual void e(); +}; +struct F : E { + void e() final; +}; +struct G { + virtual void e() = 0; +}; + +void f() { + { + PickyConsumer<A> a1; + PickyConsumerWrapper<A> a2; + PickyConsumerSubclass<A> a3; + NonPickyConsumer<A> a4; + } + + { + PickyConsumer<B> a1; // expected-note {{bad instantiation of 'PickyConsumer<B>' requested here}} + PickyConsumerWrapper<B> a2; + PickyConsumerSubclass<B> a3; + NonPickyConsumer<B> a4; + } + + { + PickyConsumer<C> a1; + PickyConsumerWrapper<C> a2; + PickyConsumerSubclass<C> a3; + NonPickyConsumer<C> a4; + } + + { + PickyConsumer<D> a1; + PickyConsumerWrapper<D> a2; + PickyConsumerSubclass<D> a3; + NonPickyConsumer<D> a4; + } + + { + PickyConsumer<E> a1; // expected-note {{bad instantiation of 'PickyConsumer<E>' requested here}} + PickyConsumerWrapper<E> a2; + PickyConsumerSubclass<E> a3; + NonPickyConsumer<E> a4; + } + + { + PickyConsumer<F> a1; // expected-note {{bad instantiation of 'PickyConsumer<F>' requested here}} + PickyConsumerWrapper<F> a2; + PickyConsumerSubclass<F> a3; + NonPickyConsumer<F> a4; + } + + { + PickyConsumer<G> a1; // expected-note {{bad instantiation of 'PickyConsumer<G>' requested here}} + PickyConsumerWrapper<G> a2; + PickyConsumerSubclass<G> a3; + NonPickyConsumer<G> a4; + } +} diff --git a/build/clang-plugin/tests/TestNoAddRefReleaseOnReturn.cpp b/build/clang-plugin/tests/TestNoAddRefReleaseOnReturn.cpp new file mode 100644 index 0000000000..5db514f7ab --- /dev/null +++ b/build/clang-plugin/tests/TestNoAddRefReleaseOnReturn.cpp @@ -0,0 +1,110 @@ +#define MOZ_NO_ADDREF_RELEASE_ON_RETURN __attribute__((annotate("moz_no_addref_release_on_return"))) + +struct Test { + void AddRef(); + void Release(); + void foo(); +}; + +struct TestD : Test {}; + +struct S { + Test* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + Test& g() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + Test h() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +}; + +struct SD { + TestD* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + TestD& g() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + TestD h() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +}; + +template<class T> +struct X { + T* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + T& g() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + T h() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +}; + +template<class T> +struct SP { + T* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +}; + +Test* f() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +Test& g() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +Test h() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + +TestD* fd() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +TestD& gd() MOZ_NO_ADDREF_RELEASE_ON_RETURN; +TestD hd() MOZ_NO_ADDREF_RELEASE_ON_RETURN; + +void test() { + S s; + s.f()->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'S::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + s.f()->Release(); // expected-error{{'Release' must not be called on the return value of 'S::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + s.f()->foo(); + s.g().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'S::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + s.g().Release(); // expected-error{{'Release' must not be called on the return value of 'S::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + s.g().foo(); + s.h().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'S::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + s.h().Release(); // expected-error{{'Release' must not be called on the return value of 'S::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + s.h().foo(); + SD sd; + sd.f()->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'SD::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sd.f()->Release(); // expected-error{{'Release' must not be called on the return value of 'SD::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sd.f()->foo(); + sd.g().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'SD::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sd.g().Release(); // expected-error{{'Release' must not be called on the return value of 'SD::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sd.g().foo(); + sd.h().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'SD::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sd.h().Release(); // expected-error{{'Release' must not be called on the return value of 'SD::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sd.h().foo(); + X<Test> x; + x.f()->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'X<Test>::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + x.f()->Release(); // expected-error{{'Release' must not be called on the return value of 'X<Test>::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + x.f()->foo(); + x.g().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'X<Test>::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + x.g().Release(); // expected-error{{'Release' must not be called on the return value of 'X<Test>::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + x.g().foo(); + x.h().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'X<Test>::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + x.h().Release(); // expected-error{{'Release' must not be called on the return value of 'X<Test>::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + x.h().foo(); + X<TestD> xd; + xd.f()->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'X<TestD>::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + xd.f()->Release(); // expected-error{{'Release' must not be called on the return value of 'X<TestD>::f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + xd.f()->foo(); + xd.g().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'X<TestD>::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + xd.g().Release(); // expected-error{{'Release' must not be called on the return value of 'X<TestD>::g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + xd.g().foo(); + xd.h().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'X<TestD>::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + xd.h().Release(); // expected-error{{'Release' must not be called on the return value of 'X<TestD>::h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + xd.h().foo(); + SP<Test> sp; + sp->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'SP<Test>::operator->' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sp->Release(); // expected-error{{'Release' must not be called on the return value of 'SP<Test>::operator->' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + sp->foo(); + SP<TestD> spd; + spd->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'SP<TestD>::operator->' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + spd->Release(); // expected-error{{'Release' must not be called on the return value of 'SP<TestD>::operator->' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + spd->foo(); + f()->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + f()->Release(); // expected-error{{'Release' must not be called on the return value of 'f' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + f()->foo(); + g().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + g().Release(); // expected-error{{'Release' must not be called on the return value of 'g' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + g().foo(); + h().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + h().Release(); // expected-error{{'Release' must not be called on the return value of 'h' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + h().foo(); + fd()->AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'fd' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + fd()->Release(); // expected-error{{'Release' must not be called on the return value of 'fd' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + fd()->foo(); + gd().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'gd' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + gd().Release(); // expected-error{{'Release' must not be called on the return value of 'gd' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + gd().foo(); + hd().AddRef(); // expected-error{{'AddRef' must not be called on the return value of 'hd' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + hd().Release(); // expected-error{{'Release' must not be called on the return value of 'hd' which is marked with MOZ_NO_ADDREF_RELEASE_ON_RETURN}} + hd().foo(); +} diff --git a/build/clang-plugin/tests/TestNoArithmeticExprInArgument.cpp b/build/clang-plugin/tests/TestNoArithmeticExprInArgument.cpp new file mode 100644 index 0000000000..d147b17012 --- /dev/null +++ b/build/clang-plugin/tests/TestNoArithmeticExprInArgument.cpp @@ -0,0 +1,32 @@ +#define MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT __attribute__((annotate("moz_no_arith_expr_in_arg"))) + +struct X { + explicit X(int) MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT; + void baz(int) MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT; +}; + +int operator+(int, X); +int operator+(X, int); +int operator++(X); + +void badArithmeticsInArgs() { + int a = 1; + typedef int myint; + myint b = 2; + X goodObj1(a); + goodObj1.baz(b); + X badObj1(a + b); // expected-error{{cannot pass an arithmetic expression of built-in types to 'X'}} + X badObj2 = X(a ? 0 : ++a); // expected-error{{cannot pass an arithmetic expression of built-in types to 'X'}} + X badObj3(~a); // expected-error{{cannot pass an arithmetic expression of built-in types to 'X'}} + badObj1.baz(a - 1 - b); // expected-error{{cannot pass an arithmetic expression of built-in types to 'baz'}} + badObj1.baz(++a); // expected-error{{cannot pass an arithmetic expression of built-in types to 'baz'}} + badObj1.baz(a++); // expected-error{{cannot pass an arithmetic expression of built-in types to 'baz'}} + badObj1.baz(a || b); + badObj1.baz(a + goodObj1); + badObj1.baz(goodObj1 + a); + badObj1.baz(++goodObj1); + badObj1.baz(-1); + badObj1.baz(-1.0); + badObj1.baz(1 + 2); + badObj1.baz(1 << (sizeof(int)/2)); +} diff --git a/build/clang-plugin/tests/TestNoAutoType.cpp b/build/clang-plugin/tests/TestNoAutoType.cpp new file mode 100644 index 0000000000..6c6e65f243 --- /dev/null +++ b/build/clang-plugin/tests/TestNoAutoType.cpp @@ -0,0 +1,41 @@ +#define MOZ_NON_AUTOABLE __attribute__((annotate("moz_non_autoable"))) + +template<class T> +struct MOZ_NON_AUTOABLE ExplicitTypeTemplate {}; +struct MOZ_NON_AUTOABLE ExplicitType {}; +struct NonExplicitType {}; + +void f() { + { + ExplicitType a; + auto b = a; // expected-error {{Cannot use auto to declare a variable of type 'ExplicitType'}} expected-note {{Please write out this type explicitly}} + auto &br = a; // expected-error {{Cannot use auto to declare a variable of type 'ExplicitType &'}} expected-note {{Please write out this type explicitly}} + const auto &brc = a; // expected-error {{Cannot use auto to declare a variable of type 'const ExplicitType &'}} expected-note {{Please write out this type explicitly}} + auto *bp = &a; // expected-error {{Cannot use auto to declare a variable of type 'ExplicitType *'}} expected-note {{Please write out this type explicitly}} + const auto *bpc = &a; // expected-error {{Cannot use auto to declare a variable of type 'const ExplicitType *'}} expected-note {{Please write out this type explicitly}} + } + + { + ExplicitTypeTemplate<int> a; + auto b = a; // expected-error {{Cannot use auto to declare a variable of type 'ExplicitTypeTemplate<int>'}} expected-note {{Please write out this type explicitly}} + auto &br = a; // expected-error {{Cannot use auto to declare a variable of type 'ExplicitTypeTemplate<int> &'}} expected-note {{Please write out this type explicitly}} + const auto &brc = a; // expected-error {{Cannot use auto to declare a variable of type 'const ExplicitTypeTemplate<int> &'}} expected-note {{Please write out this type explicitly}} + auto *bp = &a; // expected-error {{Cannot use auto to declare a variable of type 'ExplicitTypeTemplate<int> *'}} expected-note {{Please write out this type explicitly}} + const auto *bpc = &a; // expected-error {{Cannot use auto to declare a variable of type 'const ExplicitTypeTemplate<int> *'}} expected-note {{Please write out this type explicitly}} + } + + { + NonExplicitType c; + auto d = c; + auto &dr = c; + const auto &drc = c; + auto *dp = &c; + const auto *dpc = &c; + } +} + +ExplicitType A; +auto B = A; // expected-error {{Cannot use auto to declare a variable of type 'ExplicitType'}} expected-note {{Please write out this type explicitly}} + +NonExplicitType C; +auto D = C; diff --git a/build/clang-plugin/tests/TestNoDuplicateRefCntMember.cpp b/build/clang-plugin/tests/TestNoDuplicateRefCntMember.cpp new file mode 100644 index 0000000000..ff68e4fc7c --- /dev/null +++ b/build/clang-plugin/tests/TestNoDuplicateRefCntMember.cpp @@ -0,0 +1,49 @@ +class C1 {}; + +class RC1 { +public: + virtual void AddRef(); + virtual void Release(); + +private: + int mRefCnt; // expected-note 2 {{Superclass 'RC1' also has an mRefCnt member}} expected-note 3 {{Superclass 'RC1' has an mRefCnt member}} +}; + +class RC2 : public RC1 { // expected-error {{Refcounted record 'RC2' has multiple mRefCnt members}} +public: + virtual void AddRef(); + virtual void Release(); + +private: + int mRefCnt; // expected-note {{Consider using the _INHERITED macros for AddRef and Release here}} +}; + +class C2 : public RC1 {}; + +class RC3 : public RC1 {}; + +class RC4 : public RC3, public C2 {}; // expected-error {{Refcounted record 'RC4' has multiple superclasses with mRefCnt members}} + +class RC5 : public RC1 {}; + +class RC6 : public C1, public RC5 { // expected-error {{Refcounted record 'RC6' has multiple mRefCnt members}} +public: + virtual void AddRef(); + virtual void Release(); + +private: + int mRefCnt; // expected-note {{Consider using the _INHERITED macros for AddRef and Release here}} +}; + +class Predecl; + +class OtherRC { +public: + virtual void AddRef(); + virtual void Release(); + +private: + int mRefCnt; // expected-note {{Superclass 'OtherRC' has an mRefCnt member}} +}; + +class MultRCSuper : public RC1, public OtherRC {}; // expected-error {{Refcounted record 'MultRCSuper' has multiple superclasses with mRefCnt members}} diff --git a/build/clang-plugin/tests/TestNoExplicitMoveConstructor.cpp b/build/clang-plugin/tests/TestNoExplicitMoveConstructor.cpp new file mode 100644 index 0000000000..5aea6b1a7f --- /dev/null +++ b/build/clang-plugin/tests/TestNoExplicitMoveConstructor.cpp @@ -0,0 +1,25 @@ +class Foo { + Foo(Foo&& f); +}; + +class Bar { + explicit Bar(Bar&& f); // expected-error {{Move constructors may not be marked explicit}} +}; + +class Baz { + template<typename T> + explicit Baz(T&& f) {}; +}; + +class Quxx { + Quxx(); + Quxx(Quxx& q) = delete; + template<typename T> + explicit Quxx(T&& f) {}; +}; + +void f() { + // Move a quxx into a quxx! (This speciailizes Quxx's constructor to look like + // a move constructor - to make sure it doesn't trigger) + Quxx(Quxx()); +} diff --git a/build/clang-plugin/tests/TestNoNewThreadsChecker.cpp b/build/clang-plugin/tests/TestNoNewThreadsChecker.cpp new file mode 100644 index 0000000000..c10277c1c4 --- /dev/null +++ b/build/clang-plugin/tests/TestNoNewThreadsChecker.cpp @@ -0,0 +1,9 @@ +// Dummy NS_NewNamedThread. +void NS_NewNamedThread(const char *aName) {} + +void func_threads() { + // Test to see if the checker recognizes a bad name, and if it recognizes a + // name from the ThreadAllows.txt. + NS_NewNamedThread("A bad name"); // expected-error {{Thread name not recognized. Please use the background thread pool.}} expected-note {{NS_NewNamedThread has been deprecated in favor of background task dispatch via NS_DispatchBackgroundTask and NS_CreateBackgroundTaskQueue. If you must create a new ad-hoc thread, have your thread name added to ThreadAllows.txt.}} + NS_NewNamedThread("Checker Test"); +} diff --git a/build/clang-plugin/tests/TestNoPrincipalGetUri.cpp b/build/clang-plugin/tests/TestNoPrincipalGetUri.cpp new file mode 100644 index 0000000000..c94a2add8a --- /dev/null +++ b/build/clang-plugin/tests/TestNoPrincipalGetUri.cpp @@ -0,0 +1,31 @@ +class nsIPrincipal { +public: + void GetURI(int foo){}; +}; + +class SomePrincipal : public nsIPrincipal { +public: + void GetURI(int foo) {} +}; + +class NullPrincipal : public SomePrincipal {}; + +class SomeURI { +public: + void GetURI(int foo) {} +}; + +void f() { + nsIPrincipal *a = new SomePrincipal(); + a->GetURI(0); // expected-error {{Principal->GetURI is deprecated and will be removed soon. Please consider using the new helper functions of nsIPrincipal}} + + ::nsIPrincipal *b = new NullPrincipal(); + b->GetURI(0); // expected-error {{Principal->GetURI is deprecated and will be removed soon. Please consider using the new helper functions of nsIPrincipal}} + + SomeURI *c = new SomeURI(); + c->GetURI(0); + + SomePrincipal *d = new SomePrincipal(); + d->GetURI(0); + +} diff --git a/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp b/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp new file mode 100644 index 0000000000..aedc5af096 --- /dev/null +++ b/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp @@ -0,0 +1,682 @@ +#include <mozilla/StaticAnalysisFunctions.h> + +#include <functional> +#define MOZ_STRONG_REF +#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) + +// Ensure that warnings about returning stack addresses of local variables are +// errors, so our `expected-error` annotations below work correctly. +#pragma GCC diagnostic error "-Wreturn-stack-address" + +struct RefCountedBase { + void AddRef(); + void Release(); +}; + +template <class T> +struct SmartPtr { + SmartPtr(); + MOZ_IMPLICIT SmartPtr(T*); + T* MOZ_STRONG_REF t; + T* operator->() const; +}; + +struct R : RefCountedBase { + void method(); +private: + void privateMethod(); +}; + +void take(...); +void foo() { + R* ptr; + SmartPtr<R> sp; + take([&](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + take([&](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + take([&](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + take([&](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + take([=](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + take([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + take([=](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + take([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + take([ptr](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + take([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + take([ptr](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + take([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + take([&ptr](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + take([&sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + take([&ptr](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + take([&sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +} + +void b() { + R* ptr; + SmartPtr<R> sp; + std::function<void(R*)>([&](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + std::function<void(SmartPtr<R>)>([&](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + std::function<void(R*)>([&](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + std::function<void(SmartPtr<R>)>([&](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + std::function<void(R*)>([=](R* argptr) { + R* localptr; + ptr->method(); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + argptr->method(); + localptr->method(); + }); + std::function<void(SmartPtr<R>)>([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + std::function<void(R*)>([=](R* argptr) { + R* localptr; + take(ptr); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + take(argptr); + take(localptr); + }); + std::function<void(SmartPtr<R>)>([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + std::function<void(R*)>([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + std::function<void(SmartPtr<R>)>([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + std::function<void(R*)>([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + std::function<void(SmartPtr<R>)>([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + std::function<void(R*)>([&ptr](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + std::function<void(SmartPtr<R>)>([&sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + std::function<void(R*)>([&ptr](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + std::function<void(SmartPtr<R>)>([&sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +} + +// These tests would check c++14 deduced return types, if they were supported in +// our codebase. They are being kept here for convenience in the future if we do +// add support for c++14 deduced return types +#if 0 +auto d1() { + R* ptr; + SmartPtr<R> sp; + return ([&](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); +} +auto d2() { + R* ptr; + SmartPtr<R> sp; + return ([&](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); +} +auto d3() { + R* ptr; + SmartPtr<R> sp; + return ([&](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); +} +auto d4() { + R* ptr; + SmartPtr<R> sp; + return ([&](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +} +auto d5() { + R* ptr; + SmartPtr<R> sp; + return ([=](R* argptr) { + R* localptr; + ptr->method(); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + argptr->method(); + localptr->method(); + }); +} +auto d6() { + R* ptr; + SmartPtr<R> sp; + return ([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); +} +auto d8() { + R* ptr; + SmartPtr<R> sp; + return ([=](R* argptr) { + R* localptr; + take(ptr); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + take(argptr); + take(localptr); + }); +} +auto d9() { + R* ptr; + SmartPtr<R> sp; + return ([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +} +auto d10() { + R* ptr; + SmartPtr<R> sp; + return ([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); +} +auto d11() { + R* ptr; + SmartPtr<R> sp; + return ([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); +} +auto d12() { + R* ptr; + SmartPtr<R> sp; + return ([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); +} +auto d13() { + R* ptr; + SmartPtr<R> sp; + return ([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +} +auto d14() { + R* ptr; + SmartPtr<R> sp; + return ([&ptr](R* argptr) { + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); +} +auto d15() { + R* ptr; + SmartPtr<R> sp; + return ([&sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); +} +auto d16() { + R* ptr; + SmartPtr<R> sp; + return ([&ptr](R* argptr) { + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); +} +auto d17() { + R* ptr; + SmartPtr<R> sp; + return ([&sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +} +#endif + +void e() { + auto e1 = []() { + R* ptr; + SmartPtr<R> sp; + return ([&](R* argptr) { // expected-error{{address of stack memory associated with local variable 'ptr' returned}} + R* localptr; +#if __clang_major__ >= 12 + ptr->method(); // expected-note{{implicitly captured by reference due to use here}} +#else + ptr->method(); +#endif + argptr->method(); + localptr->method(); + }); + }; + auto e2 = []() { + R* ptr; + SmartPtr<R> sp; + return ([&](SmartPtr<R> argsp) { // expected-error{{address of stack memory associated with local variable 'sp' returned}} + SmartPtr<R> localsp; +#if __clang_major__ >= 12 + sp->method(); // expected-note{{implicitly captured by reference due to use here}} +#else + sp->method(); +#endif + argsp->method(); + localsp->method(); + }); + }; + auto e3 = []() { + R* ptr; + SmartPtr<R> sp; + return ([&](R* argptr) { // expected-error{{address of stack memory associated with local variable 'ptr' returned}} + R* localptr; +#if __clang_major__ >= 12 + take(ptr); // expected-note{{implicitly captured by reference due to use here}} +#else + take(ptr); +#endif + take(argptr); + take(localptr); + }); + }; + auto e4 = []() { + R* ptr; + SmartPtr<R> sp; + return ([&](SmartPtr<R> argsp) { // expected-error{{address of stack memory associated with local variable 'sp' returned}} + SmartPtr<R> localsp; +#if __clang_major__ >= 12 + take(sp); // expected-note{{implicitly captured by reference due to use here}} +#else + take(sp); +#endif + take(argsp); + take(localsp); + }); + }; + auto e5 = []() { + R* ptr; + SmartPtr<R> sp; + return ([=](R* argptr) { + R* localptr; + ptr->method(); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + argptr->method(); + localptr->method(); + }); + }; + auto e6 = []() { + R* ptr; + SmartPtr<R> sp; + return ([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + }; + auto e8 = []() { + R* ptr; + SmartPtr<R> sp; + return ([=](R* argptr) { + R* localptr; + take(ptr); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + take(argptr); + take(localptr); + }); + }; + auto e9 = []() { + R* ptr; + SmartPtr<R> sp; + return ([=](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + }; + auto e10 = []() { + R* ptr; + SmartPtr<R> sp; + return ([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); + }; + auto e11 = []() { + R* ptr; + SmartPtr<R> sp; + return ([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); + }; + auto e12 = []() { + R* ptr; + SmartPtr<R> sp; + return ([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); + }; + auto e13 = []() { + R* ptr; + SmartPtr<R> sp; + return ([sp](SmartPtr<R> argsp) { + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); + }; + auto e14 = []() { + R* ptr; + SmartPtr<R> sp; +#if __clang_major__ >= 12 + return ([&ptr](R* argptr) { // expected-error{{address of stack memory associated with local variable 'ptr' returned}} expected-note{{captured by reference here}} + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); +#else + return ([&ptr](R* argptr) { // expected-error{{address of stack memory associated with local variable 'ptr' returned}} + R* localptr; + ptr->method(); + argptr->method(); + localptr->method(); + }); +#endif + }; + auto e15 = []() { + R* ptr; + SmartPtr<R> sp; +#if __clang_major__ >= 12 + return ([&sp](SmartPtr<R> argsp) { // expected-error{{address of stack memory associated with local variable 'sp' returned}} expected-note{{captured by reference here}} + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); +#else + return ([&sp](SmartPtr<R> argsp) { // expected-error{{address of stack memory associated with local variable 'sp' returned}} + SmartPtr<R> localsp; + sp->method(); + argsp->method(); + localsp->method(); + }); +#endif + }; + auto e16 = []() { + R* ptr; + SmartPtr<R> sp; +#if __clang_major__ >= 12 + return ([&ptr](R* argptr) { // expected-error{{address of stack memory associated with local variable 'ptr' returned}} expected-note{{captured by reference here}} + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); +#else + return ([&ptr](R* argptr) { // expected-error{{address of stack memory associated with local variable 'ptr' returned}} + R* localptr; + take(ptr); + take(argptr); + take(localptr); + }); +#endif + }; + auto e17 = []() { + R* ptr; + SmartPtr<R> sp; +#if __clang_major__ >= 12 + return ([&sp](SmartPtr<R> argsp) { // expected-error{{address of stack memory associated with local variable 'sp' returned}} expected-note{{captured by reference here}} + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +#else + return ([&sp](SmartPtr<R> argsp) { // expected-error{{address of stack memory associated with local variable 'sp' returned}} + SmartPtr<R> localsp; + take(sp); + take(argsp); + take(localsp); + }); +#endif + }; +} + +void +R::privateMethod() { + SmartPtr<R> self = this; + std::function<void()>([&]() { + self->method(); + }); + std::function<void()>([&]() { + self->privateMethod(); + }); + std::function<void()>([&]() { + this->method(); + }); + std::function<void()>([&]() { + this->privateMethod(); + }); + std::function<void()>([=]() { + self->method(); + }); + std::function<void()>([=]() { + self->privateMethod(); + }); + std::function<void()>([=]() { + this->method(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([=]() { + this->privateMethod(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([self]() { + self->method(); + }); + std::function<void()>([self]() { + self->privateMethod(); + }); + std::function<void()>([this]() { + this->method(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([this]() { + this->privateMethod(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([this]() { + method(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([this]() { + privateMethod(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([=]() { + method(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([=]() { + privateMethod(); // expected-error{{Refcounted variable 'this' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + }); + std::function<void()>([&]() { + method(); + }); + std::function<void()>([&]() { + privateMethod(); + }); + + std::function<void()>( + [instance = MOZ_KnownLive(this)]() { instance->privateMethod(); }); + + // It should be OK to go through `this` if we have captured a reference to it. + std::function<void()>([this, self]() { + this->method(); + this->privateMethod(); + method(); + privateMethod(); + }); +} diff --git a/build/clang-plugin/tests/TestNoUsingNamespaceMozillaJava.cpp b/build/clang-plugin/tests/TestNoUsingNamespaceMozillaJava.cpp new file mode 100644 index 0000000000..70cfbe1827 --- /dev/null +++ b/build/clang-plugin/tests/TestNoUsingNamespaceMozillaJava.cpp @@ -0,0 +1,29 @@ +namespace mozilla { +namespace java { +namespace sdk { +} // namespace sdk + +namespace future { +} // namespace future +} // namespace java +} // namespace mozilla + +namespace mozilla { + using namespace java; // expected-error{{using namespace mozilla::java is forbidden}} + using namespace java::future; // expected-error{{using namespace mozilla::java::future is forbidden}} +} + +using namespace mozilla::java::sdk; // expected-error{{using namespace mozilla::java::sdk is forbidden}} + +namespace shouldPass { + namespace java { + } + + using namespace java; +} + +using namespace shouldPass::java; + + +void test() { +} diff --git a/build/clang-plugin/tests/TestNonHeapClass.cpp b/build/clang-plugin/tests/TestNonHeapClass.cpp new file mode 100644 index 0000000000..26fe6404e0 --- /dev/null +++ b/build/clang-plugin/tests/TestNonHeapClass.cpp @@ -0,0 +1,62 @@ +#define MOZ_NONHEAP_CLASS __attribute__((annotate("moz_nonheap_class"))) +#define MOZ_STACK_CLASS __attribute__((annotate("moz_stack_class"))) +#include <stddef.h> + +struct MOZ_NONHEAP_CLASS NonHeap { + int i; + void *operator new(size_t x) throw() { return 0; } + void *operator new(size_t blah, char *buffer) { return buffer; } +}; + +template <class T> +struct MOZ_NONHEAP_CLASS TemplateClass { + T i; +}; + +void gobble(void *) { } + +void misuseNonHeapClass(int len) { + NonHeap valid; + NonHeap alsoValid[2]; + static NonHeap validStatic; + static NonHeap alsoValidStatic[2]; + + gobble(&valid); + gobble(&validStatic); + gobble(&alsoValid[0]); + + gobble(new NonHeap); // expected-error {{variable of type 'NonHeap' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} + gobble(new NonHeap[10]); // expected-error {{variable of type 'NonHeap' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} + gobble(new TemplateClass<int>); // expected-error {{variable of type 'TemplateClass<int>' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} + gobble(len <= 5 ? &valid : new NonHeap); // expected-error {{variable of type 'NonHeap' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} + + char buffer[sizeof(NonHeap)]; + gobble(new (buffer) NonHeap); +} + +NonHeap validStatic; +struct RandomClass { + NonHeap nonstaticMember; // expected-note {{'RandomClass' is a non-heap type because member 'nonstaticMember' is a non-heap type 'NonHeap'}} + static NonHeap staticMember; +}; +struct MOZ_NONHEAP_CLASS RandomNonHeapClass { + NonHeap nonstaticMember; + static NonHeap staticMember; +}; + +struct BadInherit : NonHeap {}; // expected-note {{'BadInherit' is a non-heap type because it inherits from a non-heap type 'NonHeap'}} +struct MOZ_NONHEAP_CLASS GoodInherit : NonHeap {}; + +void useStuffWrongly() { + gobble(new BadInherit); // expected-error {{variable of type 'BadInherit' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} + gobble(new RandomClass); // expected-error {{variable of type 'RandomClass' is not valid on the heap}} expected-note {{value incorrectly allocated on the heap}} +} + +// Stack class overrides non-heap typees. +struct MOZ_STACK_CLASS StackClass {}; +struct MOZ_NONHEAP_CLASS InferredStackClass : GoodInherit { + NonHeap nonstaticMember; + StackClass stackClass; // expected-note {{'InferredStackClass' is a stack type because member 'stackClass' is a stack type 'StackClass'}} +}; + +InferredStackClass global; // expected-error {{variable of type 'InferredStackClass' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} diff --git a/build/clang-plugin/tests/TestNonMemMovable.cpp b/build/clang-plugin/tests/TestNonMemMovable.cpp new file mode 100644 index 0000000000..dfbb5a6c65 --- /dev/null +++ b/build/clang-plugin/tests/TestNonMemMovable.cpp @@ -0,0 +1,830 @@ +#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"))) + +/* + These are a bunch of structs with variable levels of memmovability. + They will be used as template parameters to the various NeedyTemplates +*/ +struct MOZ_NON_MEMMOVABLE NonMovable {}; +struct Movable {}; + +// Subclasses +struct S_NonMovable : NonMovable {}; // expected-note 51 {{'S_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'NonMovable'}} +struct S_Movable : Movable {}; + +// Members +struct W_NonMovable { + NonMovable m; // expected-note 34 {{'W_NonMovable' is a non-memmove()able type because member 'm' is a non-memmove()able type 'NonMovable'}} +}; +struct W_Movable { + Movable m; +}; + +// Wrapped Subclasses +struct WS_NonMovable { + S_NonMovable m; // expected-note 34 {{'WS_NonMovable' is a non-memmove()able type because member 'm' is a non-memmove()able type 'S_NonMovable'}} +}; +struct WS_Movable { + S_Movable m; +}; + +// Combinations of the above +struct SW_NonMovable : W_NonMovable {}; // expected-note 17 {{'SW_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'W_NonMovable'}} +struct SW_Movable : W_Movable {}; + +struct SWS_NonMovable : WS_NonMovable {}; // expected-note 17 {{'SWS_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'WS_NonMovable'}} +struct SWS_Movable : WS_Movable {}; + +// Basic templated wrapper +template <class T> +struct Template_Inline { + T m; // expected-note-re 56 {{'Template_Inline<{{.*}}>' is a non-memmove()able type because member 'm' is a non-memmove()able type '{{.*}}'}} +}; + +template <class T> +struct Template_Ref { + T* m; +}; + +template <class T> +struct Template_Unused {}; + +template <class T> +struct MOZ_NON_MEMMOVABLE Template_NonMovable {}; + +/* + These tests take the following form: + DECLARATIONS => Declarations of the templates which are either marked with MOZ_NEEDS_MEMMOVABLE_TYPE + or which instantiate a MOZ_NEEDS_MEMMOVABLE_TYPE through some mechanism. + BAD N => Instantiations of the wrapper template with each of the non-memmovable types. + The prefix S_ means subclass, W_ means wrapped. Each of these rows should produce an error + on the NeedyTemplate in question, and a note at the instantiation location of that template. + Unfortunately, on every case more complicated than bad1, the instantiation location is + within another template. Thus, the notes are expected on the template in question which + actually instantiates the MOZ_NEEDS_MEMMOVABLE_TYPE template. + GOOD N => Instantiations of the wrapper template with each of the memmovable types. + This is meant as a sanity check to ensure that we don't reject valid instantiations of + templates. + + + Note 1: Each set uses it's own types to ensure that they don't re-use each-other's template specializations. + If they did, then some of the error messages would not be emitted (as error messages are emitted for template + specializations, rather than for variable declarations) + + Note 2: Every instance of NeedyTemplate contains a member of type T. This is to ensure that T is actually + instantiated (if T is a template) by clang. If T isn't instantiated, then we can't actually tell if it is + NON_MEMMOVABLE. (This is OK in practice, as you cannot memmove a type which you don't know the size of). + + Note 3: There are a set of tests for specializations of NeedyTemplate at the bottom. For each set of tests, + these tests contribute two expected errors to the templates. +*/ + +// +// 1 - Unwrapped MOZ_NEEDS_MEMMOVABLE_TYPE +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate1 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate1<{{.*}}>' with non-memmovable template argument '{{.*}}'}} + +void bad1() { + NeedyTemplate1<NonMovable> a1; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<S_NonMovable> a2; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<W_NonMovable> a3; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<WS_NonMovable> a4; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<SW_NonMovable> a5; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<SWS_NonMovable> a6; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + + NeedyTemplate1<Template_Inline<NonMovable> > b1; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_Inline<S_NonMovable> > b2; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_Inline<W_NonMovable> > b3; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_Inline<WS_NonMovable> > b4; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_Inline<SW_NonMovable> > b5; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_Inline<SWS_NonMovable> > b6; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + + NeedyTemplate1<Template_NonMovable<NonMovable> > c1; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<S_NonMovable> > c2; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<W_NonMovable> > c3; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<WS_NonMovable> > c4; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<SW_NonMovable> > c5; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<SWS_NonMovable> > c6; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<Movable> > c7; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<S_Movable> > c8; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<W_Movable> > c9; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<WS_Movable> > c10; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<SW_Movable> > c11; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} + NeedyTemplate1<Template_NonMovable<SWS_Movable> > c12; // expected-note-re {{instantiation of 'NeedyTemplate1<{{.*}}>' requested here}} +} + +void good1() { + NeedyTemplate1<Movable> a1; + NeedyTemplate1<S_Movable> a2; + NeedyTemplate1<W_Movable> a3; + NeedyTemplate1<WS_Movable> a4; + NeedyTemplate1<SW_Movable> a5; + NeedyTemplate1<SWS_Movable> a6; + + NeedyTemplate1<Template_Inline<Movable> > b1; + NeedyTemplate1<Template_Inline<S_Movable> > b2; + NeedyTemplate1<Template_Inline<W_Movable> > b3; + NeedyTemplate1<Template_Inline<WS_Movable> > b4; + NeedyTemplate1<Template_Inline<SW_Movable> > b5; + NeedyTemplate1<Template_Inline<SWS_Movable> > b6; + + NeedyTemplate1<Template_Unused<Movable> > c1; + NeedyTemplate1<Template_Unused<S_Movable> > c2; + NeedyTemplate1<Template_Unused<W_Movable> > c3; + NeedyTemplate1<Template_Unused<WS_Movable> > c4; + NeedyTemplate1<Template_Unused<SW_Movable> > c5; + NeedyTemplate1<Template_Unused<SWS_Movable> > c6; + NeedyTemplate1<Template_Unused<NonMovable> > c7; + NeedyTemplate1<Template_Unused<S_NonMovable> > c8; + NeedyTemplate1<Template_Unused<W_NonMovable> > c9; + NeedyTemplate1<Template_Unused<WS_NonMovable> > c10; + NeedyTemplate1<Template_Unused<SW_NonMovable> > c11; + NeedyTemplate1<Template_Unused<SWS_NonMovable> > c12; + + NeedyTemplate1<Template_Ref<Movable> > d1; + NeedyTemplate1<Template_Ref<S_Movable> > d2; + NeedyTemplate1<Template_Ref<W_Movable> > d3; + NeedyTemplate1<Template_Ref<WS_Movable> > d4; + NeedyTemplate1<Template_Ref<SW_Movable> > d5; + NeedyTemplate1<Template_Ref<SWS_Movable> > d6; + NeedyTemplate1<Template_Ref<NonMovable> > d7; + NeedyTemplate1<Template_Ref<S_NonMovable> > d8; + NeedyTemplate1<Template_Ref<W_NonMovable> > d9; + NeedyTemplate1<Template_Ref<WS_NonMovable> > d10; + NeedyTemplate1<Template_Ref<SW_NonMovable> > d11; + NeedyTemplate1<Template_Ref<SWS_NonMovable> > d12; +} + +// +// 2 - Subclassed MOZ_NEEDS_MEMMOVABLE_TYPE +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate2 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate2<{{.*}}>' with non-memmovable template argument '{{.*}}'}} +template <class T> +struct S_NeedyTemplate2 : NeedyTemplate2<T> {}; // expected-note-re 26 {{instantiation of 'NeedyTemplate2<{{.*}}>' requested here}} + +void bad2() { + S_NeedyTemplate2<NonMovable> a1; + S_NeedyTemplate2<S_NonMovable> a2; + S_NeedyTemplate2<W_NonMovable> a3; + S_NeedyTemplate2<WS_NonMovable> a4; + S_NeedyTemplate2<SW_NonMovable> a5; + S_NeedyTemplate2<SWS_NonMovable> a6; + + S_NeedyTemplate2<Template_Inline<NonMovable> > b1; + S_NeedyTemplate2<Template_Inline<S_NonMovable> > b2; + S_NeedyTemplate2<Template_Inline<W_NonMovable> > b3; + S_NeedyTemplate2<Template_Inline<WS_NonMovable> > b4; + S_NeedyTemplate2<Template_Inline<SW_NonMovable> > b5; + S_NeedyTemplate2<Template_Inline<SWS_NonMovable> > b6; + + S_NeedyTemplate2<Template_NonMovable<NonMovable> > c1; + S_NeedyTemplate2<Template_NonMovable<S_NonMovable> > c2; + S_NeedyTemplate2<Template_NonMovable<W_NonMovable> > c3; + S_NeedyTemplate2<Template_NonMovable<WS_NonMovable> > c4; + S_NeedyTemplate2<Template_NonMovable<SW_NonMovable> > c5; + S_NeedyTemplate2<Template_NonMovable<SWS_NonMovable> > c6; + S_NeedyTemplate2<Template_NonMovable<Movable> > c7; + S_NeedyTemplate2<Template_NonMovable<S_Movable> > c8; + S_NeedyTemplate2<Template_NonMovable<W_Movable> > c9; + S_NeedyTemplate2<Template_NonMovable<WS_Movable> > c10; + S_NeedyTemplate2<Template_NonMovable<SW_Movable> > c11; + S_NeedyTemplate2<Template_NonMovable<SWS_Movable> > c12; +} + +void good2() { + S_NeedyTemplate2<Movable> a1; + S_NeedyTemplate2<S_Movable> a2; + S_NeedyTemplate2<W_Movable> a3; + S_NeedyTemplate2<WS_Movable> a4; + S_NeedyTemplate2<SW_Movable> a5; + S_NeedyTemplate2<SWS_Movable> a6; + + S_NeedyTemplate2<Template_Inline<Movable> > b1; + S_NeedyTemplate2<Template_Inline<S_Movable> > b2; + S_NeedyTemplate2<Template_Inline<W_Movable> > b3; + S_NeedyTemplate2<Template_Inline<WS_Movable> > b4; + S_NeedyTemplate2<Template_Inline<SW_Movable> > b5; + S_NeedyTemplate2<Template_Inline<SWS_Movable> > b6; + + S_NeedyTemplate2<Template_Unused<Movable> > c1; + S_NeedyTemplate2<Template_Unused<S_Movable> > c2; + S_NeedyTemplate2<Template_Unused<W_Movable> > c3; + S_NeedyTemplate2<Template_Unused<WS_Movable> > c4; + S_NeedyTemplate2<Template_Unused<SW_Movable> > c5; + S_NeedyTemplate2<Template_Unused<SWS_Movable> > c6; + S_NeedyTemplate2<Template_Unused<NonMovable> > c7; + S_NeedyTemplate2<Template_Unused<S_NonMovable> > c8; + S_NeedyTemplate2<Template_Unused<W_NonMovable> > c9; + S_NeedyTemplate2<Template_Unused<WS_NonMovable> > c10; + S_NeedyTemplate2<Template_Unused<SW_NonMovable> > c11; + S_NeedyTemplate2<Template_Unused<SWS_NonMovable> > c12; + + S_NeedyTemplate2<Template_Ref<Movable> > d1; + S_NeedyTemplate2<Template_Ref<S_Movable> > d2; + S_NeedyTemplate2<Template_Ref<W_Movable> > d3; + S_NeedyTemplate2<Template_Ref<WS_Movable> > d4; + S_NeedyTemplate2<Template_Ref<SW_Movable> > d5; + S_NeedyTemplate2<Template_Ref<SWS_Movable> > d6; + S_NeedyTemplate2<Template_Ref<NonMovable> > d7; + S_NeedyTemplate2<Template_Ref<S_NonMovable> > d8; + S_NeedyTemplate2<Template_Ref<W_NonMovable> > d9; + S_NeedyTemplate2<Template_Ref<WS_NonMovable> > d10; + S_NeedyTemplate2<Template_Ref<SW_NonMovable> > d11; + S_NeedyTemplate2<Template_Ref<SWS_NonMovable> > d12; +} + +// +// 3 - Wrapped MOZ_NEEDS_MEMMOVABLE_TYPE +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate3 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate3<{{.*}}>' with non-memmovable template argument '{{.*}}'}} +template <class T> +struct W_NeedyTemplate3 { + NeedyTemplate3<T> m; // expected-note-re 26 {{instantiation of 'NeedyTemplate3<{{.*}}>' requested here}} +}; +void bad3() { + W_NeedyTemplate3<NonMovable> a1; + W_NeedyTemplate3<S_NonMovable> a2; + W_NeedyTemplate3<W_NonMovable> a3; + W_NeedyTemplate3<WS_NonMovable> a4; + W_NeedyTemplate3<SW_NonMovable> a5; + W_NeedyTemplate3<SWS_NonMovable> a6; + + W_NeedyTemplate3<Template_Inline<NonMovable> > b1; + W_NeedyTemplate3<Template_Inline<S_NonMovable> > b2; + W_NeedyTemplate3<Template_Inline<W_NonMovable> > b3; + W_NeedyTemplate3<Template_Inline<WS_NonMovable> > b4; + W_NeedyTemplate3<Template_Inline<SW_NonMovable> > b5; + W_NeedyTemplate3<Template_Inline<SWS_NonMovable> > b6; + + W_NeedyTemplate3<Template_NonMovable<NonMovable> > c1; + W_NeedyTemplate3<Template_NonMovable<S_NonMovable> > c2; + W_NeedyTemplate3<Template_NonMovable<W_NonMovable> > c3; + W_NeedyTemplate3<Template_NonMovable<WS_NonMovable> > c4; + W_NeedyTemplate3<Template_NonMovable<SW_NonMovable> > c5; + W_NeedyTemplate3<Template_NonMovable<SWS_NonMovable> > c6; + W_NeedyTemplate3<Template_NonMovable<Movable> > c7; + W_NeedyTemplate3<Template_NonMovable<S_Movable> > c8; + W_NeedyTemplate3<Template_NonMovable<W_Movable> > c9; + W_NeedyTemplate3<Template_NonMovable<WS_Movable> > c10; + W_NeedyTemplate3<Template_NonMovable<SW_Movable> > c11; + W_NeedyTemplate3<Template_NonMovable<SWS_Movable> > c12; +} + +void good3() { + W_NeedyTemplate3<Movable> a1; + W_NeedyTemplate3<S_Movable> a2; + W_NeedyTemplate3<W_Movable> a3; + W_NeedyTemplate3<WS_Movable> a4; + W_NeedyTemplate3<SW_Movable> a5; + W_NeedyTemplate3<SWS_Movable> a6; + + W_NeedyTemplate3<Template_Inline<Movable> > b1; + W_NeedyTemplate3<Template_Inline<S_Movable> > b2; + W_NeedyTemplate3<Template_Inline<W_Movable> > b3; + W_NeedyTemplate3<Template_Inline<WS_Movable> > b4; + W_NeedyTemplate3<Template_Inline<SW_Movable> > b5; + W_NeedyTemplate3<Template_Inline<SWS_Movable> > b6; + + W_NeedyTemplate3<Template_Unused<Movable> > c1; + W_NeedyTemplate3<Template_Unused<S_Movable> > c2; + W_NeedyTemplate3<Template_Unused<W_Movable> > c3; + W_NeedyTemplate3<Template_Unused<WS_Movable> > c4; + W_NeedyTemplate3<Template_Unused<SW_Movable> > c5; + W_NeedyTemplate3<Template_Unused<SWS_Movable> > c6; + W_NeedyTemplate3<Template_Unused<NonMovable> > c7; + W_NeedyTemplate3<Template_Unused<S_NonMovable> > c8; + W_NeedyTemplate3<Template_Unused<W_NonMovable> > c9; + W_NeedyTemplate3<Template_Unused<WS_NonMovable> > c10; + W_NeedyTemplate3<Template_Unused<SW_NonMovable> > c11; + W_NeedyTemplate3<Template_Unused<SWS_NonMovable> > c12; + + W_NeedyTemplate3<Template_Ref<Movable> > d1; + W_NeedyTemplate3<Template_Ref<S_Movable> > d2; + W_NeedyTemplate3<Template_Ref<W_Movable> > d3; + W_NeedyTemplate3<Template_Ref<WS_Movable> > d4; + W_NeedyTemplate3<Template_Ref<SW_Movable> > d5; + W_NeedyTemplate3<Template_Ref<SWS_Movable> > d6; + W_NeedyTemplate3<Template_Ref<NonMovable> > d7; + W_NeedyTemplate3<Template_Ref<S_NonMovable> > d8; + W_NeedyTemplate3<Template_Ref<W_NonMovable> > d9; + W_NeedyTemplate3<Template_Ref<WS_NonMovable> > d10; + W_NeedyTemplate3<Template_Ref<SW_NonMovable> > d11; + W_NeedyTemplate3<Template_Ref<SWS_NonMovable> > d12; +} + +// +// 4 - Wrapped Subclassed MOZ_NEEDS_MEMMOVABLE_TYPE +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate4 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate4<{{.*}}>' with non-memmovable template argument '{{.*}}'}} +template <class T> +struct S_NeedyTemplate4 : NeedyTemplate4<T> {}; // expected-note-re 26 {{instantiation of 'NeedyTemplate4<{{.*}}>' requested here}} +template <class T> +struct WS_NeedyTemplate4 { + S_NeedyTemplate4<T> m; +}; +void bad4() { + WS_NeedyTemplate4<NonMovable> a1; + WS_NeedyTemplate4<S_NonMovable> a2; + WS_NeedyTemplate4<W_NonMovable> a3; + WS_NeedyTemplate4<WS_NonMovable> a4; + WS_NeedyTemplate4<SW_NonMovable> a5; + WS_NeedyTemplate4<SWS_NonMovable> a6; + + WS_NeedyTemplate4<Template_Inline<NonMovable> > b1; + WS_NeedyTemplate4<Template_Inline<S_NonMovable> > b2; + WS_NeedyTemplate4<Template_Inline<W_NonMovable> > b3; + WS_NeedyTemplate4<Template_Inline<WS_NonMovable> > b4; + WS_NeedyTemplate4<Template_Inline<SW_NonMovable> > b5; + WS_NeedyTemplate4<Template_Inline<SWS_NonMovable> > b6; + + WS_NeedyTemplate4<Template_NonMovable<NonMovable> > c1; + WS_NeedyTemplate4<Template_NonMovable<S_NonMovable> > c2; + WS_NeedyTemplate4<Template_NonMovable<W_NonMovable> > c3; + WS_NeedyTemplate4<Template_NonMovable<WS_NonMovable> > c4; + WS_NeedyTemplate4<Template_NonMovable<SW_NonMovable> > c5; + WS_NeedyTemplate4<Template_NonMovable<SWS_NonMovable> > c6; + WS_NeedyTemplate4<Template_NonMovable<Movable> > c7; + WS_NeedyTemplate4<Template_NonMovable<S_Movable> > c8; + WS_NeedyTemplate4<Template_NonMovable<W_Movable> > c9; + WS_NeedyTemplate4<Template_NonMovable<WS_Movable> > c10; + WS_NeedyTemplate4<Template_NonMovable<SW_Movable> > c11; + WS_NeedyTemplate4<Template_NonMovable<SWS_Movable> > c12; +} + +void good4() { + WS_NeedyTemplate4<Movable> a1; + WS_NeedyTemplate4<S_Movable> a2; + WS_NeedyTemplate4<W_Movable> a3; + WS_NeedyTemplate4<WS_Movable> a4; + WS_NeedyTemplate4<SW_Movable> a5; + WS_NeedyTemplate4<SWS_Movable> a6; + + WS_NeedyTemplate4<Template_Inline<Movable> > b1; + WS_NeedyTemplate4<Template_Inline<S_Movable> > b2; + WS_NeedyTemplate4<Template_Inline<W_Movable> > b3; + WS_NeedyTemplate4<Template_Inline<WS_Movable> > b4; + WS_NeedyTemplate4<Template_Inline<SW_Movable> > b5; + WS_NeedyTemplate4<Template_Inline<SWS_Movable> > b6; + + WS_NeedyTemplate4<Template_Unused<Movable> > c1; + WS_NeedyTemplate4<Template_Unused<S_Movable> > c2; + WS_NeedyTemplate4<Template_Unused<W_Movable> > c3; + WS_NeedyTemplate4<Template_Unused<WS_Movable> > c4; + WS_NeedyTemplate4<Template_Unused<SW_Movable> > c5; + WS_NeedyTemplate4<Template_Unused<SWS_Movable> > c6; + WS_NeedyTemplate4<Template_Unused<NonMovable> > c7; + WS_NeedyTemplate4<Template_Unused<S_NonMovable> > c8; + WS_NeedyTemplate4<Template_Unused<W_NonMovable> > c9; + WS_NeedyTemplate4<Template_Unused<WS_NonMovable> > c10; + WS_NeedyTemplate4<Template_Unused<SW_NonMovable> > c11; + WS_NeedyTemplate4<Template_Unused<SWS_NonMovable> > c12; + + WS_NeedyTemplate4<Template_Ref<Movable> > d1; + WS_NeedyTemplate4<Template_Ref<S_Movable> > d2; + WS_NeedyTemplate4<Template_Ref<W_Movable> > d3; + WS_NeedyTemplate4<Template_Ref<WS_Movable> > d4; + WS_NeedyTemplate4<Template_Ref<SW_Movable> > d5; + WS_NeedyTemplate4<Template_Ref<SWS_Movable> > d6; + WS_NeedyTemplate4<Template_Ref<NonMovable> > d7; + WS_NeedyTemplate4<Template_Ref<S_NonMovable> > d8; + WS_NeedyTemplate4<Template_Ref<W_NonMovable> > d9; + WS_NeedyTemplate4<Template_Ref<WS_NonMovable> > d10; + WS_NeedyTemplate4<Template_Ref<SW_NonMovable> > d11; + WS_NeedyTemplate4<Template_Ref<SWS_NonMovable> > d12; +} + +// +// 5 - Subclassed Wrapped MOZ_NEEDS_MEMMOVABLE_TYPE +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate5 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate5<{{.*}}>' with non-memmovable template argument '{{.*}}'}} +template <class T> +struct W_NeedyTemplate5 { + NeedyTemplate5<T> m; // expected-note-re 26 {{instantiation of 'NeedyTemplate5<{{.*}}>' requested here}} +}; +template <class T> +struct SW_NeedyTemplate5 : W_NeedyTemplate5<T> {}; +void bad5() { + SW_NeedyTemplate5<NonMovable> a1; + SW_NeedyTemplate5<S_NonMovable> a2; + SW_NeedyTemplate5<W_NonMovable> a3; + SW_NeedyTemplate5<WS_NonMovable> a4; + SW_NeedyTemplate5<SW_NonMovable> a5; + SW_NeedyTemplate5<SWS_NonMovable> a6; + + SW_NeedyTemplate5<Template_Inline<NonMovable> > b1; + SW_NeedyTemplate5<Template_Inline<S_NonMovable> > b2; + SW_NeedyTemplate5<Template_Inline<W_NonMovable> > b3; + SW_NeedyTemplate5<Template_Inline<WS_NonMovable> > b4; + SW_NeedyTemplate5<Template_Inline<SW_NonMovable> > b5; + SW_NeedyTemplate5<Template_Inline<SWS_NonMovable> > b6; + + SW_NeedyTemplate5<Template_NonMovable<NonMovable> > c1; + SW_NeedyTemplate5<Template_NonMovable<S_NonMovable> > c2; + SW_NeedyTemplate5<Template_NonMovable<W_NonMovable> > c3; + SW_NeedyTemplate5<Template_NonMovable<WS_NonMovable> > c4; + SW_NeedyTemplate5<Template_NonMovable<SW_NonMovable> > c5; + SW_NeedyTemplate5<Template_NonMovable<SWS_NonMovable> > c6; + SW_NeedyTemplate5<Template_NonMovable<Movable> > c7; + SW_NeedyTemplate5<Template_NonMovable<S_Movable> > c8; + SW_NeedyTemplate5<Template_NonMovable<W_Movable> > c9; + SW_NeedyTemplate5<Template_NonMovable<WS_Movable> > c10; + SW_NeedyTemplate5<Template_NonMovable<SW_Movable> > c11; + SW_NeedyTemplate5<Template_NonMovable<SWS_Movable> > c12; +} + +void good5() { + SW_NeedyTemplate5<Movable> a1; + SW_NeedyTemplate5<S_Movable> a2; + SW_NeedyTemplate5<W_Movable> a3; + SW_NeedyTemplate5<WS_Movable> a4; + SW_NeedyTemplate5<SW_Movable> a5; + SW_NeedyTemplate5<SWS_Movable> a6; + + SW_NeedyTemplate5<Template_Inline<Movable> > b1; + SW_NeedyTemplate5<Template_Inline<S_Movable> > b2; + SW_NeedyTemplate5<Template_Inline<W_Movable> > b3; + SW_NeedyTemplate5<Template_Inline<WS_Movable> > b4; + SW_NeedyTemplate5<Template_Inline<SW_Movable> > b5; + SW_NeedyTemplate5<Template_Inline<SWS_Movable> > b6; + + SW_NeedyTemplate5<Template_Unused<Movable> > c1; + SW_NeedyTemplate5<Template_Unused<S_Movable> > c2; + SW_NeedyTemplate5<Template_Unused<W_Movable> > c3; + SW_NeedyTemplate5<Template_Unused<WS_Movable> > c4; + SW_NeedyTemplate5<Template_Unused<SW_Movable> > c5; + SW_NeedyTemplate5<Template_Unused<SWS_Movable> > c6; + SW_NeedyTemplate5<Template_Unused<NonMovable> > c7; + SW_NeedyTemplate5<Template_Unused<S_NonMovable> > c8; + SW_NeedyTemplate5<Template_Unused<W_NonMovable> > c9; + SW_NeedyTemplate5<Template_Unused<WS_NonMovable> > c10; + SW_NeedyTemplate5<Template_Unused<SW_NonMovable> > c11; + SW_NeedyTemplate5<Template_Unused<SWS_NonMovable> > c12; + + SW_NeedyTemplate5<Template_Ref<Movable> > d1; + SW_NeedyTemplate5<Template_Ref<S_Movable> > d2; + SW_NeedyTemplate5<Template_Ref<W_Movable> > d3; + SW_NeedyTemplate5<Template_Ref<WS_Movable> > d4; + SW_NeedyTemplate5<Template_Ref<SW_Movable> > d5; + SW_NeedyTemplate5<Template_Ref<SWS_Movable> > d6; + SW_NeedyTemplate5<Template_Ref<NonMovable> > d7; + SW_NeedyTemplate5<Template_Ref<S_NonMovable> > d8; + SW_NeedyTemplate5<Template_Ref<W_NonMovable> > d9; + SW_NeedyTemplate5<Template_Ref<WS_NonMovable> > d10; + SW_NeedyTemplate5<Template_Ref<SW_NonMovable> > d11; + SW_NeedyTemplate5<Template_Ref<SWS_NonMovable> > d12; +} + +// +// 6 - MOZ_NEEDS_MEMMOVABLE_TYPE instantiated with default template argument +// +// Note: This has an extra error, because it also includes a test with the default template argument. +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate6 {T m;}; // expected-error-re 27 {{Cannot instantiate 'NeedyTemplate6<{{.*}}>' with non-memmovable template argument '{{.*}}'}} +template <class T> +struct W_NeedyTemplate6 { + NeedyTemplate6<T> m; // expected-note-re 27 {{instantiation of 'NeedyTemplate6<{{.*}}>' requested here}} +}; +template <class T> +struct SW_NeedyTemplate6 : W_NeedyTemplate6<T> {}; +// We create a different NonMovable type here, as NeedyTemplate6 will already be instantiated with NonMovable +struct MOZ_NON_MEMMOVABLE NonMovable2 {}; +template <class T = NonMovable2> +struct Defaulted_SW_NeedyTemplate6 { + SW_NeedyTemplate6<T> m; +}; +void bad6() { + Defaulted_SW_NeedyTemplate6<NonMovable> a1; + Defaulted_SW_NeedyTemplate6<S_NonMovable> a2; + Defaulted_SW_NeedyTemplate6<W_NonMovable> a3; + Defaulted_SW_NeedyTemplate6<WS_NonMovable> a4; + Defaulted_SW_NeedyTemplate6<SW_NonMovable> a5; + Defaulted_SW_NeedyTemplate6<SWS_NonMovable> a6; + + Defaulted_SW_NeedyTemplate6<Template_Inline<NonMovable> > b1; + Defaulted_SW_NeedyTemplate6<Template_Inline<S_NonMovable> > b2; + Defaulted_SW_NeedyTemplate6<Template_Inline<W_NonMovable> > b3; + Defaulted_SW_NeedyTemplate6<Template_Inline<WS_NonMovable> > b4; + Defaulted_SW_NeedyTemplate6<Template_Inline<SW_NonMovable> > b5; + Defaulted_SW_NeedyTemplate6<Template_Inline<SWS_NonMovable> > b6; + + Defaulted_SW_NeedyTemplate6<Template_NonMovable<NonMovable> > c1; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<S_NonMovable> > c2; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<W_NonMovable> > c3; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<WS_NonMovable> > c4; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<SW_NonMovable> > c5; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<SWS_NonMovable> > c6; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<Movable> > c7; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<S_Movable> > c8; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<W_Movable> > c9; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<WS_Movable> > c10; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<SW_Movable> > c11; + Defaulted_SW_NeedyTemplate6<Template_NonMovable<SWS_Movable> > c12; + + Defaulted_SW_NeedyTemplate6<> c13; +} + +void good6() { + Defaulted_SW_NeedyTemplate6<Movable> a1; + Defaulted_SW_NeedyTemplate6<S_Movable> a2; + Defaulted_SW_NeedyTemplate6<W_Movable> a3; + Defaulted_SW_NeedyTemplate6<WS_Movable> a4; + Defaulted_SW_NeedyTemplate6<SW_Movable> a5; + Defaulted_SW_NeedyTemplate6<SWS_Movable> a6; + + Defaulted_SW_NeedyTemplate6<Template_Inline<Movable> > b1; + Defaulted_SW_NeedyTemplate6<Template_Inline<S_Movable> > b2; + Defaulted_SW_NeedyTemplate6<Template_Inline<W_Movable> > b3; + Defaulted_SW_NeedyTemplate6<Template_Inline<WS_Movable> > b4; + Defaulted_SW_NeedyTemplate6<Template_Inline<SW_Movable> > b5; + Defaulted_SW_NeedyTemplate6<Template_Inline<SWS_Movable> > b6; + + Defaulted_SW_NeedyTemplate6<Template_Unused<Movable> > c1; + Defaulted_SW_NeedyTemplate6<Template_Unused<S_Movable> > c2; + Defaulted_SW_NeedyTemplate6<Template_Unused<W_Movable> > c3; + Defaulted_SW_NeedyTemplate6<Template_Unused<WS_Movable> > c4; + Defaulted_SW_NeedyTemplate6<Template_Unused<SW_Movable> > c5; + Defaulted_SW_NeedyTemplate6<Template_Unused<SWS_Movable> > c6; + Defaulted_SW_NeedyTemplate6<Template_Unused<NonMovable> > c7; + Defaulted_SW_NeedyTemplate6<Template_Unused<S_NonMovable> > c8; + Defaulted_SW_NeedyTemplate6<Template_Unused<W_NonMovable> > c9; + Defaulted_SW_NeedyTemplate6<Template_Unused<WS_NonMovable> > c10; + Defaulted_SW_NeedyTemplate6<Template_Unused<SW_NonMovable> > c11; + Defaulted_SW_NeedyTemplate6<Template_Unused<SWS_NonMovable> > c12; + + Defaulted_SW_NeedyTemplate6<Template_Ref<Movable> > d1; + Defaulted_SW_NeedyTemplate6<Template_Ref<S_Movable> > d2; + Defaulted_SW_NeedyTemplate6<Template_Ref<W_Movable> > d3; + Defaulted_SW_NeedyTemplate6<Template_Ref<WS_Movable> > d4; + Defaulted_SW_NeedyTemplate6<Template_Ref<SW_Movable> > d5; + Defaulted_SW_NeedyTemplate6<Template_Ref<SWS_Movable> > d6; + Defaulted_SW_NeedyTemplate6<Template_Ref<NonMovable> > d7; + Defaulted_SW_NeedyTemplate6<Template_Ref<S_NonMovable> > d8; + Defaulted_SW_NeedyTemplate6<Template_Ref<W_NonMovable> > d9; + Defaulted_SW_NeedyTemplate6<Template_Ref<WS_NonMovable> > d10; + Defaulted_SW_NeedyTemplate6<Template_Ref<SW_NonMovable> > d11; + Defaulted_SW_NeedyTemplate6<Template_Ref<SWS_NonMovable> > d12; +} + +// +// 7 - MOZ_NEEDS_MEMMOVABLE_TYPE instantiated as default template argument +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate7 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate7<{{.*}}>' with non-memmovable template argument '{{.*}}'}} +template <class T, class Q = NeedyTemplate7<T> > +struct Defaulted_Templated_NeedyTemplate7 {Q m;}; // expected-note-re 26 {{instantiation of 'NeedyTemplate7<{{.*}}>' requested here}} +void bad7() { + Defaulted_Templated_NeedyTemplate7<NonMovable> a1; + Defaulted_Templated_NeedyTemplate7<S_NonMovable> a2; + Defaulted_Templated_NeedyTemplate7<W_NonMovable> a3; + Defaulted_Templated_NeedyTemplate7<WS_NonMovable> a4; + Defaulted_Templated_NeedyTemplate7<SW_NonMovable> a5; + Defaulted_Templated_NeedyTemplate7<SWS_NonMovable> a6; + + Defaulted_Templated_NeedyTemplate7<Template_Inline<NonMovable> > b1; + Defaulted_Templated_NeedyTemplate7<Template_Inline<S_NonMovable> > b2; + Defaulted_Templated_NeedyTemplate7<Template_Inline<W_NonMovable> > b3; + Defaulted_Templated_NeedyTemplate7<Template_Inline<WS_NonMovable> > b4; + Defaulted_Templated_NeedyTemplate7<Template_Inline<SW_NonMovable> > b5; + Defaulted_Templated_NeedyTemplate7<Template_Inline<SWS_NonMovable> > b6; + + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<NonMovable> > c1; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<S_NonMovable> > c2; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<W_NonMovable> > c3; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<WS_NonMovable> > c4; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SW_NonMovable> > c5; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SWS_NonMovable> > c6; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<Movable> > c7; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<S_Movable> > c8; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<W_Movable> > c9; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<WS_Movable> > c10; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SW_Movable> > c11; + Defaulted_Templated_NeedyTemplate7<Template_NonMovable<SWS_Movable> > c12; +} + +void good7() { + Defaulted_Templated_NeedyTemplate7<Movable> a1; + Defaulted_Templated_NeedyTemplate7<S_Movable> a2; + Defaulted_Templated_NeedyTemplate7<W_Movable> a3; + Defaulted_Templated_NeedyTemplate7<WS_Movable> a4; + Defaulted_Templated_NeedyTemplate7<SW_Movable> a5; + Defaulted_Templated_NeedyTemplate7<SWS_Movable> a6; + + Defaulted_Templated_NeedyTemplate7<Template_Inline<Movable> > b1; + Defaulted_Templated_NeedyTemplate7<Template_Inline<S_Movable> > b2; + Defaulted_Templated_NeedyTemplate7<Template_Inline<W_Movable> > b3; + Defaulted_Templated_NeedyTemplate7<Template_Inline<WS_Movable> > b4; + Defaulted_Templated_NeedyTemplate7<Template_Inline<SW_Movable> > b5; + Defaulted_Templated_NeedyTemplate7<Template_Inline<SWS_Movable> > b6; + + Defaulted_Templated_NeedyTemplate7<Template_Unused<Movable> > c1; + Defaulted_Templated_NeedyTemplate7<Template_Unused<S_Movable> > c2; + Defaulted_Templated_NeedyTemplate7<Template_Unused<W_Movable> > c3; + Defaulted_Templated_NeedyTemplate7<Template_Unused<WS_Movable> > c4; + Defaulted_Templated_NeedyTemplate7<Template_Unused<SW_Movable> > c5; + Defaulted_Templated_NeedyTemplate7<Template_Unused<SWS_Movable> > c6; + Defaulted_Templated_NeedyTemplate7<Template_Unused<NonMovable> > c7; + Defaulted_Templated_NeedyTemplate7<Template_Unused<S_NonMovable> > c8; + Defaulted_Templated_NeedyTemplate7<Template_Unused<W_NonMovable> > c9; + Defaulted_Templated_NeedyTemplate7<Template_Unused<WS_NonMovable> > c10; + Defaulted_Templated_NeedyTemplate7<Template_Unused<SW_NonMovable> > c11; + Defaulted_Templated_NeedyTemplate7<Template_Unused<SWS_NonMovable> > c12; + + Defaulted_Templated_NeedyTemplate7<Template_Ref<Movable> > d1; + Defaulted_Templated_NeedyTemplate7<Template_Ref<S_Movable> > d2; + Defaulted_Templated_NeedyTemplate7<Template_Ref<W_Movable> > d3; + Defaulted_Templated_NeedyTemplate7<Template_Ref<WS_Movable> > d4; + Defaulted_Templated_NeedyTemplate7<Template_Ref<SW_Movable> > d5; + Defaulted_Templated_NeedyTemplate7<Template_Ref<SWS_Movable> > d6; + Defaulted_Templated_NeedyTemplate7<Template_Ref<NonMovable> > d7; + Defaulted_Templated_NeedyTemplate7<Template_Ref<S_NonMovable> > d8; + Defaulted_Templated_NeedyTemplate7<Template_Ref<W_NonMovable> > d9; + Defaulted_Templated_NeedyTemplate7<Template_Ref<WS_NonMovable> > d10; + Defaulted_Templated_NeedyTemplate7<Template_Ref<SW_NonMovable> > d11; + Defaulted_Templated_NeedyTemplate7<Template_Ref<SWS_NonMovable> > d12; +} + +// +// 8 - Wrapped MOZ_NEEDS_MEMMOVABLE_TYPE instantiated as default template argument +// + +template <class T> +struct MOZ_NEEDS_MEMMOVABLE_TYPE NeedyTemplate8 {T m;}; // expected-error-re 26 {{Cannot instantiate 'NeedyTemplate8<{{.*}}>' with non-memmovable template argument '{{.*}}'}} +template <class T, class Q = NeedyTemplate8<T> > +struct Defaulted_Templated_NeedyTemplate8 {Q m;}; // expected-note-re 26 {{instantiation of 'NeedyTemplate8<{{.*}}>' requested here}} +template <class T> +struct W_Defaulted_Templated_NeedyTemplate8 { + Defaulted_Templated_NeedyTemplate8<T> m; +}; +void bad8() { + W_Defaulted_Templated_NeedyTemplate8<NonMovable> a1; + W_Defaulted_Templated_NeedyTemplate8<S_NonMovable> a2; + W_Defaulted_Templated_NeedyTemplate8<W_NonMovable> a3; + W_Defaulted_Templated_NeedyTemplate8<WS_NonMovable> a4; + W_Defaulted_Templated_NeedyTemplate8<SW_NonMovable> a5; + W_Defaulted_Templated_NeedyTemplate8<SWS_NonMovable> a6; + + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<NonMovable> > b1; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<S_NonMovable> > b2; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<W_NonMovable> > b3; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<WS_NonMovable> > b4; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SW_NonMovable> > b5; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SWS_NonMovable> > b6; + + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<NonMovable> > c1; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<S_NonMovable> > c2; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<W_NonMovable> > c3; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<WS_NonMovable> > c4; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SW_NonMovable> > c5; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SWS_NonMovable> > c6; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<Movable> > c7; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<S_Movable> > c8; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<W_Movable> > c9; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<WS_Movable> > c10; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SW_Movable> > c11; + W_Defaulted_Templated_NeedyTemplate8<Template_NonMovable<SWS_Movable> > c12; +} + +void good8() { + W_Defaulted_Templated_NeedyTemplate8<Movable> a1; + W_Defaulted_Templated_NeedyTemplate8<S_Movable> a2; + W_Defaulted_Templated_NeedyTemplate8<W_Movable> a3; + W_Defaulted_Templated_NeedyTemplate8<WS_Movable> a4; + W_Defaulted_Templated_NeedyTemplate8<SW_Movable> a5; + W_Defaulted_Templated_NeedyTemplate8<SWS_Movable> a6; + + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<Movable> > b1; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<S_Movable> > b2; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<W_Movable> > b3; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<WS_Movable> > b4; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SW_Movable> > b5; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SWS_Movable> > b6; + + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<Movable> > c1; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<S_Movable> > c2; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<W_Movable> > c3; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<WS_Movable> > c4; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SW_Movable> > c5; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SWS_Movable> > c6; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<NonMovable> > c7; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<S_NonMovable> > c8; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<W_NonMovable> > c9; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<WS_NonMovable> > c10; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SW_NonMovable> > c11; + W_Defaulted_Templated_NeedyTemplate8<Template_Unused<SWS_NonMovable> > c12; + + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<Movable> > d1; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<S_Movable> > d2; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<W_Movable> > d3; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<WS_Movable> > d4; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SW_Movable> > d5; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SWS_Movable> > d6; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<NonMovable> > d7; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<S_NonMovable> > d8; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<W_NonMovable> > d9; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<WS_NonMovable> > d10; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SW_NonMovable> > d11; + W_Defaulted_Templated_NeedyTemplate8<Template_Ref<SWS_NonMovable> > d12; +} + +/* + SpecializedNonMovable is a non-movable class which has an explicit specialization of NeedyTemplate + for it. Instantiations of NeedyTemplateN<SpecializedNonMovable> should be legal as the explicit + specialization isn't annotated with MOZ_NEEDS_MEMMOVABLE_TYPE. + + However, as it is MOZ_NON_MEMMOVABLE, derived classes and members shouldn't be able to be used to + instantiate NeedyTemplate. +*/ + +struct MOZ_NON_MEMMOVABLE SpecializedNonMovable {}; +struct S_SpecializedNonMovable : SpecializedNonMovable {}; // expected-note 8 {{'S_SpecializedNonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'SpecializedNonMovable'}} + +// Specialize all of the NeedyTemplates with SpecializedNonMovable. +template <> +struct NeedyTemplate1<SpecializedNonMovable> {}; +template <> +struct NeedyTemplate2<SpecializedNonMovable> {}; +template <> +struct NeedyTemplate3<SpecializedNonMovable> {}; +template <> +struct NeedyTemplate4<SpecializedNonMovable> {}; +template <> +struct NeedyTemplate5<SpecializedNonMovable> {}; +template <> +struct NeedyTemplate6<SpecializedNonMovable> {}; +template <> +struct NeedyTemplate7<SpecializedNonMovable> {}; +template <> +struct NeedyTemplate8<SpecializedNonMovable> {}; + +void specialization() { + /* + SpecializedNonMovable has a specialization for every variant of NeedyTemplate, + so these templates are valid, even though SpecializedNonMovable isn't + memmovable + */ + NeedyTemplate1<SpecializedNonMovable> a1; + S_NeedyTemplate2<SpecializedNonMovable> a2; + W_NeedyTemplate3<SpecializedNonMovable> a3; + WS_NeedyTemplate4<SpecializedNonMovable> a4; + SW_NeedyTemplate5<SpecializedNonMovable> a5; + Defaulted_SW_NeedyTemplate6<SpecializedNonMovable> a6; + Defaulted_Templated_NeedyTemplate7<SpecializedNonMovable> a7; + W_Defaulted_Templated_NeedyTemplate8<SpecializedNonMovable> a8; + + /* + These entries contain an element which is SpecializedNonMovable, and are non-movable + as there is no valid specialization, and their member is non-memmovable + */ + NeedyTemplate1<Template_Inline<SpecializedNonMovable> > b1; // expected-note-re {{instantiation of 'NeedyTemplate1<Template_Inline<SpecializedNonMovable>{{ ?}}>' requested here}} + S_NeedyTemplate2<Template_Inline<SpecializedNonMovable> > b2; + W_NeedyTemplate3<Template_Inline<SpecializedNonMovable> > b3; + WS_NeedyTemplate4<Template_Inline<SpecializedNonMovable> > b4; + SW_NeedyTemplate5<Template_Inline<SpecializedNonMovable> > b5; + Defaulted_SW_NeedyTemplate6<Template_Inline<SpecializedNonMovable> > b6; + Defaulted_Templated_NeedyTemplate7<Template_Inline<SpecializedNonMovable> > b7; + W_Defaulted_Templated_NeedyTemplate8<Template_Inline<SpecializedNonMovable> > b8; + + /* + The subclass of SpecializedNonMovable, is also non-memmovable, + as there is no valid specialization. + */ + NeedyTemplate1<S_SpecializedNonMovable> c1; // expected-note {{instantiation of 'NeedyTemplate1<S_SpecializedNonMovable>' requested here}} + S_NeedyTemplate2<S_SpecializedNonMovable> c2; + W_NeedyTemplate3<S_SpecializedNonMovable> c3; + WS_NeedyTemplate4<S_SpecializedNonMovable> c4; + SW_NeedyTemplate5<S_SpecializedNonMovable> c5; + Defaulted_SW_NeedyTemplate6<S_SpecializedNonMovable> c6; + Defaulted_Templated_NeedyTemplate7<S_SpecializedNonMovable> c7; + W_Defaulted_Templated_NeedyTemplate8<S_SpecializedNonMovable> c8; +} + +class MOZ_NEEDS_MEMMOVABLE_MEMBERS NeedsMemMovableMembers { + Movable m1; + NonMovable m2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'm2' of type 'NonMovable'}} + S_Movable sm1; + S_NonMovable sm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'sm2' of type 'S_NonMovable'}} + W_Movable wm1; + W_NonMovable wm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'wm2' of type 'W_NonMovable'}} + SW_Movable swm1; + SW_NonMovable swm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'swm2' of type 'SW_NonMovable'}} + WS_Movable wsm1; + WS_NonMovable wsm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'wsm2' of type 'WS_NonMovable'}} + SWS_Movable swsm1; + SWS_NonMovable swsm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'swsm2' of type 'SWS_NonMovable'}} +}; + +class NeedsMemMovableMembersDerived : public NeedsMemMovableMembers {}; diff --git a/build/clang-plugin/tests/TestNonMemMovableStd.cpp b/build/clang-plugin/tests/TestNonMemMovableStd.cpp new file mode 100644 index 0000000000..b380d07186 --- /dev/null +++ b/build/clang-plugin/tests/TestNonMemMovableStd.cpp @@ -0,0 +1,70 @@ +#define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type"))) + +template<class T> +class MOZ_NEEDS_MEMMOVABLE_TYPE Mover { T mForceInst; }; // expected-error-re 9 {{Cannot instantiate 'Mover<{{.*}}>' with non-memmovable template argument '{{.*}}'}} + +namespace std { +// In theory defining things in std:: like this invokes undefined +// behavior, but in practice it's good enough for this test case. +template<class C> class basic_string { // expected-note 2 {{'std::basic_string<char>' is a non-memmove()able type because it is an stl-provided type not guaranteed to be memmove-able}} expected-note {{'std::string' (aka 'basic_string<char>') is a non-memmove()able type because it is an stl-provided type not guaranteed to be memmove-able}} + public: + basic_string(); + basic_string(const basic_string&); + basic_string(basic_string&&); + basic_string& operator=(const basic_string&); + basic_string& operator=(basic_string&&); + ~basic_string(); +}; +typedef basic_string<char> string; +template<class T, class U> class pair { T mT; U mU; }; // expected-note-re 4 {{'std::pair<bool, {{.*}}>' is a non-memmove()able type because it has a template argument non-memmove()able type '{{.*}}'}} + +struct has_nontrivial_dtor { // expected-note 2 {{'std::has_nontrivial_dtor' is a non-memmove()able type because it is an stl-provided type not guaranteed to be memmove-able}} + has_nontrivial_dtor() = default; + ~has_nontrivial_dtor(); +}; +struct has_nontrivial_copy { // expected-note 2 {{'std::has_nontrivial_copy' is a non-memmove()able type because it is an stl-provided type not guaranteed to be memmove-able}} + has_nontrivial_copy() = default; + has_nontrivial_copy(const has_nontrivial_copy&); + has_nontrivial_copy& operator=(const has_nontrivial_copy&); +}; +struct has_nontrivial_move { // expected-note 2 {{'std::has_nontrivial_move' is a non-memmove()able type because it is an stl-provided type not guaranteed to be memmove-able}} + has_nontrivial_move() = default; + has_nontrivial_move(const has_nontrivial_move&); + has_nontrivial_move& operator=(const has_nontrivial_move&); +}; +struct has_trivial_dtor { + has_trivial_dtor() = default; + ~has_trivial_dtor() = default; +}; +struct has_trivial_copy { + has_trivial_copy() = default; + has_trivial_copy(const has_trivial_copy&) = default; + has_trivial_copy& operator=(const has_trivial_copy&) = default; +}; +struct has_trivial_move { + has_trivial_move() = default; + has_trivial_move(const has_trivial_move&) = default; + has_trivial_move& operator=(const has_trivial_move&) = default; +}; +} + +class HasString { std::string m; }; // expected-note {{'HasString' is a non-memmove()able type because member 'm' is a non-memmove()able type 'std::string' (aka 'basic_string<char>')}} + +static Mover<std::string> bad; // expected-note-re {{instantiation of 'Mover<std::basic_string<char>{{ ?}}>' requested here}} +static Mover<HasString> bad_mem; // expected-note {{instantiation of 'Mover<HasString>' requested here}} +static Mover<std::pair<bool, int>> good; +static Mover<std::pair<bool, std::string>> not_good; // expected-note-re {{instantiation of 'Mover<std::pair<bool, std::basic_string<char>{{ ?}}>{{ ?}}>' requested here}} + +static Mover<std::has_nontrivial_dtor> nontrivial_dtor; // expected-note {{instantiation of 'Mover<std::has_nontrivial_dtor>' requested here}} +static Mover<std::has_nontrivial_copy> nontrivial_copy; // expected-note {{instantiation of 'Mover<std::has_nontrivial_copy>' requested here}} +static Mover<std::has_nontrivial_move> nontrivial_move; // expected-note {{instantiation of 'Mover<std::has_nontrivial_move>' requested here}} +static Mover<std::has_trivial_dtor> trivial_dtor; +static Mover<std::has_trivial_copy> trivial_copy; +static Mover<std::has_trivial_move> trivial_move; + +static Mover<std::pair<bool, std::has_nontrivial_dtor>> pair_nontrivial_dtor; // expected-note {{instantiation of 'Mover<std::pair<bool, std::has_nontrivial_dtor>>' requested here}} +static Mover<std::pair<bool, std::has_nontrivial_copy>> pair_nontrivial_copy; // expected-note {{instantiation of 'Mover<std::pair<bool, std::has_nontrivial_copy>>' requested here}} +static Mover<std::pair<bool, std::has_nontrivial_move>> pair_nontrivial_move; // expected-note {{instantiation of 'Mover<std::pair<bool, std::has_nontrivial_move>>' requested here}} +static Mover<std::pair<bool, std::has_trivial_dtor>> pair_trivial_dtor; +static Mover<std::pair<bool, std::has_trivial_copy>> pair_trivial_copy; +static Mover<std::pair<bool, std::has_trivial_move>> pair_trivial_move; diff --git a/build/clang-plugin/tests/TestNonMemMovableStdAtomic.cpp b/build/clang-plugin/tests/TestNonMemMovableStdAtomic.cpp new file mode 100644 index 0000000000..b8aef2eacd --- /dev/null +++ b/build/clang-plugin/tests/TestNonMemMovableStdAtomic.cpp @@ -0,0 +1,30 @@ +// expected-no-diagnostics + +#define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type"))) + +template<class T> +class MOZ_NEEDS_MEMMOVABLE_TYPE Mover { T mForceInst; }; + +#include <atomic> +#include <cstdint> +struct CustomType{}; +static struct { + Mover<std::atomic<CustomType>> m1; + Mover<std::atomic<bool>> m2; + Mover<std::atomic<char>> m3; + Mover<std::atomic<signed char>> m4; + Mover<std::atomic<unsigned char>> m5; + Mover<std::atomic<char16_t>> m6; + Mover<std::atomic<char32_t>> m7; + Mover<std::atomic<wchar_t>> m8; + Mover<std::atomic<short>> m9; + Mover<std::atomic<unsigned short>> m10; + Mover<std::atomic<int>> m11; + Mover<std::atomic<unsigned int>> m12; + Mover<std::atomic<long>> m13; + Mover<std::atomic<unsigned long>> m14; + Mover<std::atomic<long long>> m15; + Mover<std::atomic<unsigned long long>> m16; + Mover<std::atomic<void*>> m17; + Mover<std::atomic<CustomType*>> m18; +} good; diff --git a/build/clang-plugin/tests/TestNonParameterChecker.cpp b/build/clang-plugin/tests/TestNonParameterChecker.cpp new file mode 100644 index 0000000000..291ef949f6 --- /dev/null +++ b/build/clang-plugin/tests/TestNonParameterChecker.cpp @@ -0,0 +1,187 @@ +#define MOZ_NON_PARAM __attribute__((annotate("moz_non_param"))) + +struct Param {}; +struct MOZ_NON_PARAM NonParam {}; +union MOZ_NON_PARAM NonParamUnion {}; +class MOZ_NON_PARAM NonParamClass {}; +enum MOZ_NON_PARAM NonParamEnum { X, Y, Z }; +enum class MOZ_NON_PARAM NonParamEnumClass { X, Y, Z }; + +struct HasNonParamStruct { NonParam x; int y; }; // expected-note 14 {{'HasNonParamStruct' is a non-param type because member 'x' is a non-param type 'NonParam'}} +union HasNonParamUnion { NonParam x; int y; }; // expected-note 18 {{'HasNonParamUnion' is a non-param type because member 'x' is a non-param type 'NonParam'}} +struct HasNonParamStructUnion { HasNonParamUnion z; }; // expected-note 9 {{'HasNonParamStructUnion' is a non-param type because member 'z' is a non-param type 'HasNonParamUnion'}} + +#define MAYBE_STATIC +#include "NonParameterTestCases.h" +#undef MAYBE_STATIC + +// Do not check typedef and using. +typedef void (*funcTypeParam)(Param x); +typedef void (*funcTypeNonParam)(NonParam x); + +using usingFuncTypeParam = void (*)(Param x); +using usingFuncTypeNonParam = void (*)(NonParam x); + +class class_ +{ + explicit class_(Param x) {} + explicit class_(NonParam x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + explicit class_(HasNonParamStruct x) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + explicit class_(HasNonParamUnion x) {} //expected-error {{Type 'HasNonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + explicit class_(HasNonParamStructUnion x) {} //expected-error {{Type 'HasNonParamStructUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + +#define MAYBE_STATIC +#include "NonParameterTestCases.h" +#undef MAYBE_STATIC +}; + +class classWithStatic +{ +#define MAYBE_STATIC static +#include "NonParameterTestCases.h" +#undef MAYBE_STATIC +}; + +template <typename T> +class tmplClassForParam +{ +public: + void raw(T x) {} + void rawDefault(T x = T()) {} + void const_(const T x) {} + void ptr(T* x) {} + void ref(T& x) {} + void constRef(const T& x) {} + + void notCalled(T x) {} +}; + +template <typename T> +class tmplClassForNonParam +{ +public: + void raw(T x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + void rawDefault(T x = T()) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + void const_(const T x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + void ptr(T* x) {} + void ref(T& x) {} + void constRef(const T& x) {} + + void notCalled(T x) {} +}; + +template <typename T> +class tmplClassForHasNonParamStruct +{ +public: + void raw(T x) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + void rawDefault(T x = T()) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + void const_(const T x) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + void ptr(T* x) {} + void ref(T& x) {} + void constRef(const T& x) {} + + void notCalled(T x) {} +}; + +void testTemplateClass() +{ + tmplClassForParam<Param> paramClass; + Param param; + paramClass.raw(param); + paramClass.rawDefault(); + paramClass.const_(param); + paramClass.ptr(¶m); + paramClass.ref(param); + paramClass.constRef(param); + + tmplClassForNonParam<NonParam> nonParamClass; //expected-note 3 {{The bad argument was passed to 'tmplClassForNonParam' here}} + NonParam nonParam; + nonParamClass.raw(nonParam); + nonParamClass.rawDefault(); + nonParamClass.const_(nonParam); + nonParamClass.ptr(&nonParam); + nonParamClass.ref(nonParam); + nonParamClass.constRef(nonParam); + + tmplClassForHasNonParamStruct<HasNonParamStruct> hasNonParamStructClass;//expected-note 3 {{The bad argument was passed to 'tmplClassForHasNonParamStruct' here}} + HasNonParamStruct hasNonParamStruct; + hasNonParamStructClass.raw(hasNonParamStruct); + hasNonParamStructClass.rawDefault(); + hasNonParamStructClass.const_(hasNonParamStruct); + hasNonParamStructClass.ptr(&hasNonParamStruct); + hasNonParamStructClass.ref(hasNonParamStruct); + hasNonParamStructClass.constRef(hasNonParamStruct); +} + +template <typename T> +class NestedTemplateInner +{ +public: + void raw(T x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +}; + +template <typename T> +class nestedTemplateOuter +{ +public: + void constRef(const T& x) { + NestedTemplateInner<T> inner; //expected-note {{The bad argument was passed to 'NestedTemplateInner' here}} + inner.raw(x); + } +}; + +void testNestedTemplateClass() +{ + nestedTemplateOuter<NonParam> outer; + NonParam nonParam; + outer.constRef(nonParam); // FIXME: this line needs note "The bad argument was passed to 'constRef' here" +} + +template <typename T> +void tmplFuncForParam(T x) {} +template <typename T> +void tmplFuncForNonParam(T x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +template <typename T> +void tmplFuncForNonParamImplicit(T x) {} //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +template <typename T> +void tmplFuncForHasNonParamStruct(T x) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +template <typename T> +void tmplFuncForHasNonParamStructImplicit(T x) {} //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + +void testTemplateFunc() +{ + Param param; + tmplFuncForParam<Param>(param); + + NonParam nonParam; + tmplFuncForNonParam<NonParam>(nonParam); // FIXME: this line needs note "The bad argument was passed to 'tmplFuncForNonParam' here" + tmplFuncForNonParamImplicit(nonParam); // FIXME: this line needs note "The bad argument was passed to 'tmplFuncForNonParamImplicit' here" + + HasNonParamStruct hasNonParamStruct; + tmplFuncForHasNonParamStruct<HasNonParamStruct>(hasNonParamStruct); // FIXME: this line needs note "The bad argument was passed to 'tmplFuncForHasNonParamStruct' here" + tmplFuncForHasNonParamStructImplicit(hasNonParamStruct); // FIXME: this line needs note "The bad argument was passed to 'tmplFuncForHasNonParamStructImplicit' here" +} + +void testLambda() +{ + auto paramLambda = [](Param x) -> void {}; + auto nonParamLambda = [](NonParam x) -> void {}; //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + auto nonParamStructLambda = [](HasNonParamStruct x) -> void {}; //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + auto nonParamUnionLambda = [](HasNonParamUnion x) -> void {}; //expected-error {{Type 'HasNonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + auto nonParamStructUnionLambda = [](HasNonParamStructUnion x) -> void {}; //expected-error {{Type 'HasNonParamStructUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + + (void)[](Param x) -> void {}; + (void)[](NonParam x) -> void {}; //expected-error {{Type 'NonParam' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + (void)[](HasNonParamStruct x) -> void {}; //expected-error {{Type 'HasNonParamStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + (void)[](HasNonParamUnion x) -> void {}; //expected-error {{Type 'HasNonParamUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} + (void)[](HasNonParamStructUnion x) -> void {}; //expected-error {{Type 'HasNonParamStructUnion' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +} + +struct alignas(16) AlignasStruct { char a; ~AlignasStruct(); }; // expected-note {{'AlignasStruct' is a non-param type because it has an explicit alignment of '16'}} +void takesAlignasStruct(AlignasStruct x) { } // expected-error {{Type 'AlignasStruct' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +void takesAlignasStructByRef(const AlignasStruct& x) { } + +struct AlignasMember { alignas(16) char a; ~AlignasMember(); }; // expected-note {{'AlignasMember' is a non-param type because member 'a' has an explicit alignment of '16'}} +void takesAlignasMember(AlignasMember x) { } // expected-error {{Type 'AlignasMember' must not be used as parameter}} expected-note {{Please consider passing a const reference instead}} +void takesAlignasMemberByRef(const AlignasMember& x) { } diff --git a/build/clang-plugin/tests/TestNonTemporaryClass.cpp b/build/clang-plugin/tests/TestNonTemporaryClass.cpp new file mode 100644 index 0000000000..682c8ad530 --- /dev/null +++ b/build/clang-plugin/tests/TestNonTemporaryClass.cpp @@ -0,0 +1,70 @@ +#define MOZ_NON_TEMPORARY_CLASS __attribute__((annotate("moz_non_temporary_class"))) +#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) + +#include <stddef.h> + +struct MOZ_NON_TEMPORARY_CLASS NonTemporary { + int i; + NonTemporary() {} + MOZ_IMPLICIT NonTemporary(int a) {} + NonTemporary(int a, int b) {} + void *operator new(size_t x) throw() { return 0; } + void *operator new(size_t blah, char *buffer) { return buffer; } +}; + +template <class T> +struct MOZ_NON_TEMPORARY_CLASS TemplateClass { + T i; +}; + +void gobble(void *) { } + +void gobbleref(const NonTemporary&) { } + +template <class T> +void gobbleanyref(const T&) { } + +void misuseNonTemporaryClass(int len) { + NonTemporary invalid; + NonTemporary alsoInvalid[2]; + static NonTemporary invalidStatic; + static NonTemporary alsoInvalidStatic[2]; + + gobble(&invalid); + gobble(&invalidStatic); + gobble(&alsoInvalid[0]); + + gobbleref(NonTemporary()); // expected-error {{variable of type 'NonTemporary' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} + gobbleref(NonTemporary(10, 20)); // expected-error {{variable of type 'NonTemporary' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} + gobbleref(NonTemporary(10)); // expected-error {{variable of type 'NonTemporary' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} + gobbleref(10); // expected-error {{variable of type 'NonTemporary' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} + gobbleanyref(TemplateClass<int>()); // expected-error {{variable of type 'TemplateClass<int>' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} + + gobble(new NonTemporary); + gobble(new NonTemporary[10]); + gobble(new TemplateClass<int>); + gobble(len <= 5 ? &invalid : new NonTemporary); + + char buffer[sizeof(NonTemporary)]; + gobble(new (buffer) NonTemporary); +} + +void defaultArg(const NonTemporary& arg = NonTemporary()) { +} + +NonTemporary invalidStatic; +struct RandomClass { + NonTemporary nonstaticMember; // expected-note {{'RandomClass' is a non-temporary type because member 'nonstaticMember' is a non-temporary type 'NonTemporary'}} + static NonTemporary staticMember; +}; +struct MOZ_NON_TEMPORARY_CLASS RandomNonTemporaryClass { + NonTemporary nonstaticMember; + static NonTemporary staticMember; +}; + +struct BadInherit : NonTemporary {}; // expected-note {{'BadInherit' is a non-temporary type because it inherits from a non-temporary type 'NonTemporary'}} + +void useStuffWrongly() { + gobbleanyref(BadInherit()); // expected-error {{variable of type 'BadInherit' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} + gobbleanyref(RandomClass()); // expected-error {{variable of type 'RandomClass' is not valid in a temporary}} expected-note {{value incorrectly allocated in a temporary}} +} diff --git a/build/clang-plugin/tests/TestNonTrivialTypeInFfi.cpp b/build/clang-plugin/tests/TestNonTrivialTypeInFfi.cpp new file mode 100644 index 0000000000..e491122b99 --- /dev/null +++ b/build/clang-plugin/tests/TestNonTrivialTypeInFfi.cpp @@ -0,0 +1,65 @@ +// clang warns for some of these on its own, but we're not testing that, plus +// some of them (TrivialT<int>) is a false positive (clang doesn't realize the +// type is fully specialized below). +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage" + +struct Opaque; +struct Trivial { + int foo; + char bar; + Opaque* baz; +}; + +template <typename T> +struct TrivialT { + int foo; + char bar; + T* baz; +}; + +struct NonTrivial { + ~NonTrivial() { + } + + Opaque* ptr; +}; + +template <typename T> +struct NonTrivialT { + ~NonTrivialT() { + delete ptr; + } + + T* ptr; +}; + +struct TransitivelyNonTrivial { + NonTrivial nontrivial; +}; + +extern "C" void Foo(); +extern "C" Trivial Foo1(); +extern "C" NonTrivial Foo2(); // expected-error {{Type 'NonTrivial' must not be used as return type of extern "C" function}} expected-note {{Please consider using a pointer or reference instead}} +extern "C" NonTrivialT<int> Foo3(); // expected-error {{Type 'NonTrivialT<int>' must not be used as return type of extern "C" function}} expected-note {{Please consider using a pointer or reference, or explicitly instantiating the template instead}} +extern "C" NonTrivialT<float> Foo4(); // expected-error {{Type 'NonTrivialT<float>' must not be used as return type of extern "C" function}} expected-note {{Please consider using a pointer or reference, or explicitly instantiating the template instead}} + +extern "C" NonTrivial* Foo5(); + +extern "C" TrivialT<int> Foo6(); +extern "C" TrivialT<float> Foo7(); // expected-error {{Type 'TrivialT<float>' must not be used as return type of extern "C" function}} expected-note {{Please consider using a pointer or reference, or explicitly instantiating the template instead}} +extern "C" Trivial* Foo8(); + +extern "C" void Foo9(Trivial); +extern "C" void Foo10(NonTrivial); // expected-error {{Type 'NonTrivial' must not be used as parameter to extern "C" function}} expected-note {{Please consider using a pointer or reference instead}} +extern "C" void Foo11(NonTrivial*); +extern "C" void Foo12(NonTrivialT<int>); // expected-error {{Type 'NonTrivialT<int>' must not be used as parameter to extern "C" function}} expected-note {{Please consider using a pointer or reference, or explicitly instantiating the template instead}} +extern "C" void Foo13(TrivialT<int>); +extern "C" void Foo14(TrivialT<float>); // expected-error {{Type 'TrivialT<float>' must not be used as parameter to extern "C" function}} expected-note {{Please consider using a pointer or reference, or explicitly instantiating the template instead}} + +extern "C" TransitivelyNonTrivial Foo15(); // expected-error {{Type 'TransitivelyNonTrivial' must not be used as return type of extern "C" function}} expected-note {{Please consider using a pointer or reference instead}} +extern "C" void Foo16(TransitivelyNonTrivial); // expected-error {{Type 'TransitivelyNonTrivial' must not be used as parameter to extern "C" function}} expected-note {{Please consider using a pointer or reference instead}} + +template struct TrivialT<int>; + +#pragma GCC diagnostic pop diff --git a/build/clang-plugin/tests/TestOverrideBaseCall.cpp b/build/clang-plugin/tests/TestOverrideBaseCall.cpp new file mode 100644 index 0000000000..6fdaaad04e --- /dev/null +++ b/build/clang-plugin/tests/TestOverrideBaseCall.cpp @@ -0,0 +1,175 @@ +#define MOZ_REQUIRED_BASE_METHOD __attribute__((annotate("moz_required_base_method"))) + +class Base { +public: + virtual void fo() MOZ_REQUIRED_BASE_METHOD { + } + + virtual int foRet() MOZ_REQUIRED_BASE_METHOD { + return 0; + } +}; + +class BaseOne : public Base { +public: + virtual void fo() MOZ_REQUIRED_BASE_METHOD { + Base::fo(); + } +}; + +class BaseSecond : public Base { +public: + virtual void fo() MOZ_REQUIRED_BASE_METHOD { + Base::fo(); + } +}; + +class Deriv : public BaseOne, public BaseSecond { +public: + void func() { + } + + void fo() { + func(); + BaseSecond::fo(); + BaseOne::fo(); + } +}; + +class DerivSimple : public Base { +public: + void fo() { // expected-error {{Method Base::fo must be called in all overrides, but is not called in this override defined for class DerivSimple}} + } +}; + +class BaseVirtualOne : public virtual Base { +}; + +class BaseVirtualSecond: public virtual Base { +}; + +class DerivVirtual : public BaseVirtualOne, public BaseVirtualSecond { +public: + void fo() { + Base::fo(); + } +}; + +class DerivIf : public Base { +public: + void fo() { + if (true) { + Base::fo(); + } + } +}; + +class DerivIfElse : public Base { +public: + void fo() { + if (true) { + Base::fo(); + } else { + Base::fo(); + } + } +}; + +class DerivFor : public Base { +public: + void fo() { + for (int i = 0; i < 10; i++) { + Base::fo(); + } + } +}; + +class DerivDoWhile : public Base { +public: + void fo() { + do { + Base::fo(); + } while(false); + } +}; + +class DerivWhile : public Base { +public: + void fo() { + while (true) { + Base::fo(); + break; + } + } +}; + +class DerivAssignment : public Base { +public: + int foRet() { + return foRet(); + } +}; + +class BaseOperator { +private: + int value; +public: + BaseOperator() : value(0) { + } + virtual BaseOperator& operator++() MOZ_REQUIRED_BASE_METHOD { + value++; + return *this; + } +}; + +class DerivOperatorErr : public BaseOperator { +private: + int value; +public: + DerivOperatorErr() : value(0) { + } + DerivOperatorErr& operator++() { // expected-error {{Method BaseOperator::operator++ must be called in all overrides, but is not called in this override defined for class DerivOperatorErr}} + value++; + return *this; + } +}; + +class DerivOperator : public BaseOperator { +private: + int value; +public: + DerivOperator() : value(0) { + } + DerivOperator& operator++() { + BaseOperator::operator++(); + value++; + return *this; + } +}; + +class DerivPrime : public Base { +public: + void fo() { + Base::fo(); + } +}; + +class DerivSecondErr : public DerivPrime { +public: + void fo() { // expected-error {{Method Base::fo must be called in all overrides, but is not called in this override defined for class DerivSecondErr}} + } +}; + +class DerivSecond : public DerivPrime { +public: + void fo() { + Base::fo(); + } +}; + +class DerivSecondIndirect : public DerivPrime { +public: + void fo() { + DerivPrime::fo(); + } +}; diff --git a/build/clang-plugin/tests/TestOverrideBaseCallAnnotation.cpp b/build/clang-plugin/tests/TestOverrideBaseCallAnnotation.cpp new file mode 100644 index 0000000000..e268122c69 --- /dev/null +++ b/build/clang-plugin/tests/TestOverrideBaseCallAnnotation.cpp @@ -0,0 +1,47 @@ +#define MOZ_REQUIRED_BASE_METHOD __attribute__((annotate("moz_required_base_method"))) + +class Base { +public: + virtual void fo() MOZ_REQUIRED_BASE_METHOD { + } +}; + +class BaseNonVirtual { +public: + void fo() MOZ_REQUIRED_BASE_METHOD { // expected-error {{MOZ_REQUIRED_BASE_METHOD can be used only on virtual methods}} + } +}; + +class Deriv : public BaseNonVirtual { +public: + virtual void fo() MOZ_REQUIRED_BASE_METHOD { + } +}; + +class DerivVirtual : public Base { +public: + void fo() MOZ_REQUIRED_BASE_METHOD { + Base::fo(); + } +}; + +class BaseOperator { +public: + BaseOperator& operator++() MOZ_REQUIRED_BASE_METHOD { // expected-error {{MOZ_REQUIRED_BASE_METHOD can be used only on virtual methods}} + return *this; + } +}; + +class DerivOperator : public BaseOperator { +public: + virtual DerivOperator& operator++() { + return *this; + } +}; + +class DerivPrimeOperator : public DerivOperator { +public: + DerivPrimeOperator& operator++() { + return *this; + } +}; diff --git a/build/clang-plugin/tests/TestParamTraitsEnum.cpp b/build/clang-plugin/tests/TestParamTraitsEnum.cpp new file mode 100644 index 0000000000..a250250bfe --- /dev/null +++ b/build/clang-plugin/tests/TestParamTraitsEnum.cpp @@ -0,0 +1,94 @@ +typedef enum { + BadFirst, + BadSecond, + BadThird +} BadEnum; + +typedef enum { + NestedFirst, + NestedSecond +} NestedBadEnum; + +typedef enum { + GoodFirst, + GoodSecond, + GoodLast +} GoodEnum; + +enum RawEnum { + RawFirst, + RawLast +}; + +enum class ClassEnum { + ClassFirst, + ClassLast +}; + +template <class P> struct ParamTraits; + +// Simplified EnumSerializer etc. from IPCMessageUtils.h +template <typename E, typename EnumValidator> +struct EnumSerializer { + typedef E paramType; +}; + +template <typename E, + E MinLegal, + E HighBound> +class ContiguousEnumValidator +{}; + +template <typename E, + E MinLegal, + E HighBound> +struct ContiguousEnumSerializer + : EnumSerializer<E, + ContiguousEnumValidator<E, MinLegal, HighBound>> +{}; + +// Typical ParamTraits implementation that should be avoided +template<> +struct ParamTraits<ClassEnum> // expected-error {{Custom ParamTraits implementation for an enum type}} expected-note {{Please use a helper class for example ContiguousEnumSerializer}} +{ + typedef ClassEnum paramType; +}; + +template<> +struct ParamTraits<enum RawEnum> // expected-error {{Custom ParamTraits implementation for an enum type}} expected-note {{Please use a helper class for example ContiguousEnumSerializer}} +{ + typedef enum RawEnum paramType; +}; + +template<> +struct ParamTraits<BadEnum> // expected-error {{Custom ParamTraits implementation for an enum type}} expected-note {{Please use a helper class for example ContiguousEnumSerializer}} +{ + typedef BadEnum paramType; +}; + +// Make sure the analysis catches nested typedefs +typedef NestedBadEnum NestedDefLevel1; +typedef NestedDefLevel1 NestedDefLevel2; + +template<> +struct ParamTraits<NestedDefLevel2> // expected-error {{Custom ParamTraits implementation for an enum type}} expected-note {{Please use a helper class for example ContiguousEnumSerializer}} +{ + typedef NestedDefLevel2 paramType; +}; + +// Make sure a non enum typedef is not accidentally flagged +typedef int IntTypedef; + +template<> +struct ParamTraits<IntTypedef> +{ + typedef IntTypedef paramType; +}; + +// Make sure ParamTraits using helper classes are not flagged +template<> +struct ParamTraits<GoodEnum> +: public ContiguousEnumSerializer<GoodEnum, + GoodEnum::GoodFirst, + GoodEnum::GoodLast> +{}; diff --git a/build/clang-plugin/tests/TestRefCountedCopyConstructor.cpp b/build/clang-plugin/tests/TestRefCountedCopyConstructor.cpp new file mode 100644 index 0000000000..d3bd73084c --- /dev/null +++ b/build/clang-plugin/tests/TestRefCountedCopyConstructor.cpp @@ -0,0 +1,25 @@ +// Implicit copy construct which is unused +class RC1 { + void AddRef(); + void Release(); + int mRefCnt; +}; + +// Explicit copy constructor which is used +class RC2 { +public: + RC2(); + RC2(const RC2&); +private: + void AddRef(); + void Release(); + int mRefCnt; +}; + +void f() { + RC1* r1 = new RC1(); + RC1* r1p = new RC1(*r1); // expected-error {{Invalid use of compiler-provided copy constructor on refcounted type}} expected-note {{The default copy constructor also copies the default mRefCnt property, leading to reference count imbalance issues. Please provide your own copy constructor which only copies the fields which need to be copied}} + + RC2* r2 = new RC2(); + RC2* r2p = new RC2(*r2); +} diff --git a/build/clang-plugin/tests/TestRefCountedThisInsideConstructor.cpp b/build/clang-plugin/tests/TestRefCountedThisInsideConstructor.cpp new file mode 100644 index 0000000000..a6684e3812 --- /dev/null +++ b/build/clang-plugin/tests/TestRefCountedThisInsideConstructor.cpp @@ -0,0 +1,76 @@ +#include <functional> + +#include <mozilla/RefPtr.h> + +struct RefCountedBase { + void AddRef(); + void Release(); +}; + +struct Bar; + +struct Foo : RefCountedBase { + void foo(); +}; + +struct Bar : RefCountedBase { + Bar() { + RefPtr<Bar> self = this; // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + auto self2 = RefPtr(this); // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + auto self3 = RefPtr{this}; // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + RefPtr<Bar> self4(this); // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + RefPtr<Bar> self5{this}; // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + [self=RefPtr{this}]{}(); // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + refptr(RefPtr{this}); // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + refptr(this); // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + } + + explicit Bar(float f) { + // Does not match member expressions with `this` + RefPtr<Foo> foo = this->mFoo; + foo->foo(); + auto foo2 = RefPtr(this->mFoo); + foo2->foo(); + auto foo3 = RefPtr{this->mFoo}; + foo3->foo(); + } + + explicit Bar(short i); + + explicit Bar(int i): mBar(this) {} // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + explicit Bar(int i, int i2): mBar(RefPtr(this)) {} // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + + void Init() { + // Does not match outside the constructor + RefPtr<Bar> self = this; + auto self2 = RefPtr(this); + auto self3 = RefPtr{this}; + } + + void refptr(const RefPtr<Bar>& aBar) {} + + RefPtr<Foo> mFoo; + RefPtr<Bar> mBar; +}; + +// Test case for non-inline constructor +Bar::Bar(short i) { + RefPtr<Bar> self = this; // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + auto self2 = RefPtr(this); // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + auto self3 = RefPtr{this}; // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} +} + +// Same goes for any class with MOZ_IS_REFPTR (not including nsCOMPtr because SM +// doesn't have it) +template <typename T> class MOZ_IS_REFPTR MyRefPtr { +public: + MOZ_IMPLICIT MyRefPtr(T *aPtr) : mPtr(aPtr) {} + T *mPtr; +}; + +class Baz : RefCountedBase { + Baz() { + MyRefPtr<Baz> self = this; // expected-error {{Refcounting `this` inside the constructor is a footgun, `this` may be destructed at the end of the constructor unless there's another strong reference. Consider adding a separate Create function and do the work there.}} + (void)self; + } +}; diff --git a/build/clang-plugin/tests/TestSprintfLiteral.cpp b/build/clang-plugin/tests/TestSprintfLiteral.cpp new file mode 100644 index 0000000000..3c54ce004f --- /dev/null +++ b/build/clang-plugin/tests/TestSprintfLiteral.cpp @@ -0,0 +1,41 @@ +#include <cstdio> + +void bad() { + char x[100]; + snprintf(x, sizeof(x), "bar"); // expected-error {{Use SprintfLiteral instead of snprintf when writing into a character array.}} expected-note {{This will prevent passing in the wrong size to snprintf accidentally.}} + snprintf(x, 100, "bar"); // expected-error {{Use SprintfLiteral instead of snprintf when writing into a character array.}} expected-note {{This will prevent passing in the wrong size to snprintf accidentally.}} + const int hundred = 100; + snprintf(x, hundred, "bar"); // expected-error {{Use SprintfLiteral instead of snprintf when writing into a character array.}} expected-note {{This will prevent passing in the wrong size to snprintf accidentally.}} +} + +void ok() { + char x[100]; + int y; + snprintf(x, sizeof(y), "foo"); + + snprintf(x, 50, "foo"); + + int nothundred = 100; + nothundred = 99; + snprintf(x, nothundred, "foo"); +} + +void vargs_bad(va_list args) { + char x[100]; + vsnprintf(x, sizeof(x), "bar", args); // expected-error {{Use VsprintfLiteral instead of vsnprintf when writing into a character array.}} expected-note {{This will prevent passing in the wrong size to vsnprintf accidentally.}} + vsnprintf(x, 100, "bar", args); // expected-error {{Use VsprintfLiteral instead of vsnprintf when writing into a character array.}} expected-note {{This will prevent passing in the wrong size to vsnprintf accidentally.}} + const int hundred = 100; + vsnprintf(x, hundred, "bar", args); // expected-error {{Use VsprintfLiteral instead of vsnprintf when writing into a character array.}} expected-note {{This will prevent passing in the wrong size to vsnprintf accidentally.}} +} + +void vargs_good(va_list args) { + char x[100]; + int y; + vsnprintf(x, sizeof(y), "foo", args); + + vsnprintf(x, 50, "foo", args); + + int nothundred = 100; + nothundred = 99; + vsnprintf(x, nothundred, "foo", args); +} diff --git a/build/clang-plugin/tests/TestStackClass.cpp b/build/clang-plugin/tests/TestStackClass.cpp new file mode 100644 index 0000000000..3289abef1b --- /dev/null +++ b/build/clang-plugin/tests/TestStackClass.cpp @@ -0,0 +1,50 @@ +#define MOZ_STACK_CLASS __attribute__((annotate("moz_stack_class"))) +#include <stddef.h> + +struct MOZ_STACK_CLASS Stack { + int i; + void *operator new(size_t x) throw() { return 0; } + void *operator new(size_t blah, char *buffer) { return buffer; } +}; + +template <class T> +struct MOZ_STACK_CLASS TemplateClass { + T i; +}; + +void gobble(void *) { } + +void misuseStackClass(int len) { + Stack valid; + Stack alsoValid[2]; + static Stack notValid; // expected-error {{variable of type 'Stack' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} + static Stack alsoNotValid[2]; // expected-error-re {{variable of type 'Stack{{ ?}}[2]' only valid on the stack}} expected-note-re {{'Stack{{ ?}}[2]' is a stack type because it is an array of stack type 'Stack'}} expected-note {{value incorrectly allocated in a global variable}} + + gobble(&valid); + gobble(¬Valid); + gobble(&alsoValid[0]); + + gobble(new Stack); // expected-error {{variable of type 'Stack' only valid on the stack}} expected-note {{value incorrectly allocated on the heap}} + gobble(new Stack[10]); // expected-error {{variable of type 'Stack' only valid on the stack}} expected-note {{value incorrectly allocated on the heap}} + gobble(new TemplateClass<int>); // expected-error {{variable of type 'TemplateClass<int>' only valid on the stack}} expected-note {{value incorrectly allocated on the heap}} + gobble(len <= 5 ? &valid : new Stack); // expected-error {{variable of type 'Stack' only valid on the stack}} expected-note {{value incorrectly allocated on the heap}} + + char buffer[sizeof(Stack)]; + gobble(new (buffer) Stack); +} + +Stack notValid; // expected-error {{variable of type 'Stack' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +struct RandomClass { + Stack nonstaticMember; // expected-note {{'RandomClass' is a stack type because member 'nonstaticMember' is a stack type 'Stack'}} + static Stack staticMember; // expected-error {{variable of type 'Stack' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +}; +struct MOZ_STACK_CLASS RandomStackClass { + Stack nonstaticMember; + static Stack staticMember; // expected-error {{variable of type 'Stack' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +}; + +struct BadInherit : Stack {}; // expected-note {{'BadInherit' is a stack type because it inherits from a stack type 'Stack'}} +struct MOZ_STACK_CLASS GoodInherit : Stack {}; + +BadInherit moreInvalid; // expected-error {{variable of type 'BadInherit' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} +RandomClass evenMoreInvalid; // expected-error {{variable of type 'RandomClass' only valid on the stack}} expected-note {{value incorrectly allocated in a global variable}} diff --git a/build/clang-plugin/tests/TestStaticLocalClass.cpp b/build/clang-plugin/tests/TestStaticLocalClass.cpp new file mode 100644 index 0000000000..c3b0d8645a --- /dev/null +++ b/build/clang-plugin/tests/TestStaticLocalClass.cpp @@ -0,0 +1,54 @@ +#define MOZ_STATIC_LOCAL_CLASS __attribute__((annotate("moz_static_local_class"))) +#include <stddef.h> + +struct MOZ_STATIC_LOCAL_CLASS StaticLocal { + int i; + void *operator new(size_t x) throw() { return 0; } + void *operator new(size_t blah, char *buffer) { return buffer; } +}; + +template <class T> +struct MOZ_STATIC_LOCAL_CLASS TemplateClass { + T i; +}; + +void gobble(void *) { } + +void misuseStaticLocalClass(int len) { + StaticLocal notValid; // expected-error {{variable of type 'StaticLocal' is only valid as a static local}} expected-note {{value incorrectly allocated in an automatic variable}} + StaticLocal alsoNotValid[2]; // expected-error-re {{variable of type 'StaticLocal{{ ?}}[2]' is only valid as a static local}} expected-note-re {{'StaticLocal{{ ?}}[2]' is a static-local type because it is an array of static-local type 'StaticLocal'}} expected-note {{value incorrectly allocated in an automatic variable}} + static StaticLocal valid; + static StaticLocal alsoValid[2]; + + gobble(¬Valid); + gobble(&valid); + gobble(&alsoValid[0]); + + gobble(new StaticLocal); // expected-error {{variable of type 'StaticLocal' is only valid as a static local}} expected-note {{value incorrectly allocated on the heap}} + gobble(new StaticLocal[10]); // expected-error {{variable of type 'StaticLocal' is only valid as a static local}} expected-note {{value incorrectly allocated on the heap}} + gobble(new TemplateClass<int>); // expected-error {{variable of type 'TemplateClass<int>' is only valid as a static local}} expected-note {{value incorrectly allocated on the heap}} + gobble(len <= 5 ? &valid : new StaticLocal); // expected-error {{variable of type 'StaticLocal' is only valid as a static local}} expected-note {{value incorrectly allocated on the heap}} + + char buffer[sizeof(StaticLocal)]; + gobble(new (buffer) StaticLocal); +} + +StaticLocal notValid; // expected-error {{variable of type 'StaticLocal' is only valid as a static local}} expected-note {{value incorrectly allocated in a global variable}} + +struct RandomClass { + StaticLocal nonstaticMember; // expected-note {{'RandomClass' is a static-local type because member 'nonstaticMember' is a static-local type 'StaticLocal'}} + static StaticLocal staticMember; // expected-error {{variable of type 'StaticLocal' is only valid as a static local}} expected-note {{value incorrectly allocated in a global variable}} +}; + +struct MOZ_STATIC_LOCAL_CLASS RandomStaticLocalClass { + StaticLocal nonstaticMember; + static StaticLocal staticMember; // expected-error {{variable of type 'StaticLocal' is only valid as a static local}} expected-note {{value incorrectly allocated in a global variable}} +}; + +struct BadInherit : StaticLocal {}; // expected-note {{'BadInherit' is a static-local type because it inherits from a static-local type 'StaticLocal'}} +struct MOZ_STATIC_LOCAL_CLASS GoodInherit : StaticLocal {}; + +void misuseStaticLocalClassEvenMore(int len) { + BadInherit moreInvalid; // expected-error {{variable of type 'BadInherit' is only valid as a static local}} expected-note {{value incorrectly allocated in an automatic variable}} + RandomClass evenMoreInvalid; // expected-error {{variable of type 'RandomClass' is only valid as a static local}} expected-note {{value incorrectly allocated in an automatic variable}} +} diff --git a/build/clang-plugin/tests/TestTemporaryClass.cpp b/build/clang-plugin/tests/TestTemporaryClass.cpp new file mode 100644 index 0000000000..f00acf0d7a --- /dev/null +++ b/build/clang-plugin/tests/TestTemporaryClass.cpp @@ -0,0 +1,72 @@ +#define MOZ_TEMPORARY_CLASS __attribute__((annotate("moz_temporary_class"))) +#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) + +#include <stddef.h> + +struct MOZ_TEMPORARY_CLASS Temporary { + int i; + Temporary() {} + MOZ_IMPLICIT Temporary(int a) {} + Temporary(int a, int b) {} + void *operator new(size_t x) throw() { return 0; } + void *operator new(size_t blah, char *buffer) { return buffer; } +}; + +template <class T> +struct MOZ_TEMPORARY_CLASS TemplateClass { + T i; +}; + +void gobble(void *) { } + +void gobbleref(const Temporary&) { } + +template <class T> +void gobbleanyref(const T&) { } + +void misuseNonTemporaryClass(int len) { + // All of these should error. + Temporary invalid; // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated in an automatic variable}} + Temporary alsoInvalid[2]; // expected-error-re {{variable of type 'Temporary{{ ?}}[2]' is only valid as a temporary}} expected-note {{value incorrectly allocated in an automatic variable}} expected-note-re {{'Temporary{{ ?}}[2]' is a temporary type because it is an array of temporary type 'Temporary'}} + static Temporary invalidStatic; // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated in a global variable}} + static Temporary alsoInvalidStatic[2]; // expected-error-re {{variable of type 'Temporary{{ ?}}[2]' is only valid as a temporary}} expected-note {{value incorrectly allocated in a global variable}} expected-note-re {{'Temporary{{ ?}}[2]' is a temporary type because it is an array of temporary type 'Temporary'}} + + gobble(&invalid); + gobble(&invalidStatic); + gobble(&alsoInvalid[0]); + + // All of these should be fine. + gobbleref(Temporary()); + gobbleref(Temporary(10, 20)); + gobbleref(Temporary(10)); + gobbleref(10); + gobbleanyref(TemplateClass<int>()); + + // All of these should error. + gobble(new Temporary); // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated on the heap}} + gobble(new Temporary[10]); // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated on the heap}} + gobble(new TemplateClass<int>); // expected-error {{variable of type 'TemplateClass<int>' is only valid as a temporary}} expected-note {{value incorrectly allocated on the heap}} + gobble(len <= 5 ? &invalid : new Temporary); // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated on the heap}} + + // Placement new is odd, but okay. + char buffer[sizeof(Temporary)]; + gobble(new (buffer) Temporary); +} + +void defaultArg(const Temporary& arg = Temporary()) { // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated in an automatic variable}} +} + +// Can't be a global, this should error. +Temporary invalidStatic; // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated in a global variable}} + +struct RandomClass { + Temporary nonstaticMember; // This is okay if RandomClass is only used as a temporary. + static Temporary staticMember; // expected-error {{variable of type 'Temporary' is only valid as a temporary}} expected-note {{value incorrectly allocated in a global variable}} +}; + +struct BadInherit : Temporary {}; + +void useStuffWrongly() { + gobbleanyref(BadInherit()); + gobbleanyref(RandomClass()); +} diff --git a/build/clang-plugin/tests/TestTrivialCtorDtor.cpp b/build/clang-plugin/tests/TestTrivialCtorDtor.cpp new file mode 100644 index 0000000000..a1038b6e63 --- /dev/null +++ b/build/clang-plugin/tests/TestTrivialCtorDtor.cpp @@ -0,0 +1,88 @@ +#define MOZ_TRIVIAL_CTOR_DTOR __attribute__((annotate("moz_trivial_ctor_dtor"))) + +struct MOZ_TRIVIAL_CTOR_DTOR EmptyClass{}; + +template <class T> +struct MOZ_TRIVIAL_CTOR_DTOR TemplateEmptyClass{}; + +struct MOZ_TRIVIAL_CTOR_DTOR NonEmptyClass { + void *m; +}; + +template <class T> +struct MOZ_TRIVIAL_CTOR_DTOR TemplateNonEmptyClass { + T* m; +}; + +struct MOZ_TRIVIAL_CTOR_DTOR BadUserDefinedCtor { // expected-error {{class 'BadUserDefinedCtor' must have trivial constructors and destructors}} + BadUserDefinedCtor() {} +}; + +struct MOZ_TRIVIAL_CTOR_DTOR BadUserDefinedDtor { // expected-error {{class 'BadUserDefinedDtor' must have trivial constructors and destructors}} + ~BadUserDefinedDtor() {} +}; + +struct MOZ_TRIVIAL_CTOR_DTOR BadVirtualDtor { // expected-error {{class 'BadVirtualDtor' must have trivial constructors and destructors}} + virtual ~BadVirtualDtor() {} +}; + +struct MOZ_TRIVIAL_CTOR_DTOR OkVirtualMember { + virtual void f(); +}; + +void foo(); +struct MOZ_TRIVIAL_CTOR_DTOR BadNonEmptyCtorDtor { // expected-error {{class 'BadNonEmptyCtorDtor' must have trivial constructors and destructors}} + BadNonEmptyCtorDtor() { foo(); } + ~BadNonEmptyCtorDtor() { foo(); } +}; + +struct NonTrivialCtor { + NonTrivialCtor() { foo(); } +}; + +struct NonTrivialDtor { + ~NonTrivialDtor() { foo(); } +}; + +struct VirtualMember { + virtual void f(); +}; + +struct MOZ_TRIVIAL_CTOR_DTOR BadNonTrivialCtorInBase : NonTrivialCtor { // expected-error {{class 'BadNonTrivialCtorInBase' must have trivial constructors and destructors}} +}; + +struct MOZ_TRIVIAL_CTOR_DTOR BadNonTrivialDtorInBase : NonTrivialDtor { // expected-error {{class 'BadNonTrivialDtorInBase' must have trivial constructors and destructors}} +}; + +struct MOZ_TRIVIAL_CTOR_DTOR BadNonTrivialCtorInMember { // expected-error {{class 'BadNonTrivialCtorInMember' must have trivial constructors and destructors}} + NonTrivialCtor m; +}; + +struct MOZ_TRIVIAL_CTOR_DTOR BadNonTrivialDtorInMember { // expected-error {{class 'BadNonTrivialDtorInMember' must have trivial constructors and destructors}} + NonTrivialDtor m; +}; + +struct MOZ_TRIVIAL_CTOR_DTOR OkVirtualMemberInMember { + VirtualMember m; +}; + +struct MOZ_TRIVIAL_CTOR_DTOR OkConstExprConstructor { + constexpr OkConstExprConstructor() {} +}; + +struct MOZ_TRIVIAL_CTOR_DTOR OkConstExprConstructorInMember { + OkConstExprConstructor m; +}; + +#if __cplusplus >= 202002L +struct +#else +// XXX: This error is unfortunate, but is unlikely to come up in real code. +// In this situation, it should be possible to define a constexpr constructor +// which explicitly initializes the members. +struct // expected-error {{class 'BadUnfortunateError' must have trivial constructors and destructors}} +#endif +MOZ_TRIVIAL_CTOR_DTOR BadUnfortunateError { + OkConstExprConstructor m; + void *n; +}; diff --git a/build/clang-plugin/tests/TestTrivialDtor.cpp b/build/clang-plugin/tests/TestTrivialDtor.cpp new file mode 100644 index 0000000000..f86d41b238 --- /dev/null +++ b/build/clang-plugin/tests/TestTrivialDtor.cpp @@ -0,0 +1,52 @@ +#define MOZ_TRIVIAL_DTOR __attribute__((annotate("moz_trivial_dtor"))) + +struct MOZ_TRIVIAL_DTOR EmptyClass{}; + +template <class T> +struct MOZ_TRIVIAL_DTOR TemplateEmptyClass{}; + +struct MOZ_TRIVIAL_DTOR NonEmptyClass { + void *m; +}; + +template <class T> +struct MOZ_TRIVIAL_DTOR TemplateNonEmptyClass { + T* m; +}; + +struct MOZ_TRIVIAL_DTOR BadUserDefinedDtor { // expected-error {{class 'BadUserDefinedDtor' must have a trivial destructor}} + ~BadUserDefinedDtor() {} +}; + +struct MOZ_TRIVIAL_DTOR BadVirtualDtor { // expected-error {{class 'BadVirtualDtor' must have a trivial destructor}} + virtual ~BadVirtualDtor() {} +}; + +struct MOZ_TRIVIAL_DTOR OkVirtualMember { + virtual void f(); +}; + +void foo(); +struct MOZ_TRIVIAL_DTOR BadNonEmptyCtorDtor { // expected-error {{class 'BadNonEmptyCtorDtor' must have a trivial destructor}} + BadNonEmptyCtorDtor() { foo(); } + ~BadNonEmptyCtorDtor() { foo(); } +}; + +struct NonTrivialDtor { + ~NonTrivialDtor() { foo(); } +}; + +struct VirtualMember { + virtual void f(); +}; + +struct MOZ_TRIVIAL_DTOR BadNonTrivialDtorInBase : NonTrivialDtor { // expected-error {{class 'BadNonTrivialDtorInBase' must have a trivial destructor}} +}; + +struct MOZ_TRIVIAL_DTOR BadNonTrivialDtorInMember { // expected-error {{class 'BadNonTrivialDtorInMember' must have a trivial destructor}} + NonTrivialDtor m; +}; + +struct MOZ_TRIVIAL_DTOR OkVirtualMemberInMember { + VirtualMember m; +}; diff --git a/build/clang-plugin/tests/moz.build b/build/clang-plugin/tests/moz.build new file mode 100644 index 0000000000..45f416be44 --- /dev/null +++ b/build/clang-plugin/tests/moz.build @@ -0,0 +1,101 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# 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", + "TestJSHandleRootedTypedef.cpp", + "TestKnownLive.cpp", + "TestKungFuDeathGrip.cpp", + "TestMultipleAnnotations.cpp", + "TestMustOverride.cpp", + "TestMustReturnFromCaller.cpp", + "TestNANTestingExpr.cpp", + "TestNANTestingExprC.c", + "TestNeedsNoVTableType.cpp", + "TestNoAddRefReleaseOnReturn.cpp", + "TestNoArithmeticExprInArgument.cpp", + "TestNoAutoType.cpp", + "TestNoDuplicateRefCntMember.cpp", + "TestNoExplicitMoveConstructor.cpp", + "TestNoNewThreadsChecker.cpp", + "TestNonHeapClass.cpp", + "TestNonMemMovable.cpp", + "TestNonMemMovableStd.cpp", + "TestNonMemMovableStdAtomic.cpp", + "TestNonParameterChecker.cpp", + "TestNonTemporaryClass.cpp", + "TestNonTrivialTypeInFfi.cpp", + "TestNoPrincipalGetUri.cpp", + "TestNoRefcountedInsideLambdas.cpp", + "TestNoUsingNamespaceMozillaJava.cpp", + "TestOverrideBaseCall.cpp", + "TestOverrideBaseCallAnnotation.cpp", + "TestParamTraitsEnum.cpp", + "TestRefCountedCopyConstructor.cpp", + "TestRefCountedThisInsideConstructor.cpp", + "TestSprintfLiteral.cpp", + "TestStackClass.cpp", + "TestStaticLocalClass.cpp", + "TestTemporaryClass.cpp", + "TestTrivialCtorDtor.cpp", + "TestTrivialDtor.cpp", +] + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "TestFopenUsage.cpp", + "TestLoadLibraryUsage.cpp", + ] + +include("../external/tests/sources.mozbuild") + +if CONFIG["ENABLE_CLANG_PLUGIN_ALPHA"]: + DEFINES["MOZ_CLANG_PLUGIN_ALPHA"] = "1" + include("../alpha/tests/sources.mozbuild") + +DisableStlWrapping() +NoVisibilityFlags() + +# Build without any warning flags, and with clang verify flag for a +# syntax-only build (no codegen), without a limit on the number of errors. +COMPILE_FLAGS["OS_CXXFLAGS"] = [ + f for f in COMPILE_FLAGS.get("OS_CXXFLAGS", []) if not f.startswith("-W") +] + [ + "-fsyntax-only", + "-Xclang", + "-verify", + "-ferror-limit=0", + "-Wno-invalid-noreturn", + # For SpiderMonkey headers in TestJSHandleRootedTypedef.cpp + "-Wno-attributes", + "-Wno-invalid-offsetof", +] +COMPILE_FLAGS["OS_CFLAGS"] = [ + f for f in COMPILE_FLAGS.get("OS_CFLAGS", []) if not f.startswith("-W") +] + [ + "-fsyntax-only", + "-Xclang", + "-verify", + "-ferror-limit=0", + "-Xclang", + "-std=c11", + "-Wno-invalid-noreturn", +] + +# Don't reflect WARNINGS_CFLAGS into CFLAGS, as the warnings flags should be +# as specified in OS_CFLAGS above. +DisableCompilerWarnings() |