diff options
Diffstat (limited to '')
-rw-r--r-- | build/clang-plugin/NonParamInsideFunctionDeclChecker.cpp | 217 |
1 files changed, 217 insertions, 0 deletions
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()); + } + } +} |