зеркало из https://github.com/mozilla/gecko-dev.git
218 строки
7.7 KiB
C++
218 строки
7.7 KiB
C++
/* 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());
|
|
}
|
|
}
|
|
}
|