1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "NonParamInsideFunctionDeclChecker.h"
6 #include "CustomMatchers.h"
7 #include "clang/Basic/TargetInfo.h"
9 class NonParamAnnotation
: public CustomTypeAnnotation
{
11 NonParamAnnotation() : CustomTypeAnnotation(moz_non_param
, "non-param"){};
14 // Helper for checking if a Decl has an explicitly specified alignment.
15 // Returns the alignment, in char units, of the largest alignment attribute,
16 // if it exceeds pointer alignment, and 0 otherwise.
17 static unsigned checkExplicitAlignment(const Decl
*D
) {
18 ASTContext
&Context
= D
->getASTContext();
19 #if CLANG_VERSION_FULL >= 1600
20 unsigned PointerAlign
= Context
.getTargetInfo().getPointerAlign(LangAS::Default
);
22 unsigned PointerAlign
= Context
.getTargetInfo().getPointerAlign(0);
25 // getMaxAlignment gets the largest alignment, in bits, specified by an
26 // alignment attribute directly on the declaration. If no alignment
27 // attribute is specified, it will return `0`.
28 unsigned MaxAlign
= D
->getMaxAlignment();
29 if (MaxAlign
> PointerAlign
) {
30 return Context
.toCharUnitsFromBits(MaxAlign
).getQuantity();
35 // This is directly derived from the logic in Clang's `canPassInRegisters`
36 // function, from `SemaDeclCXX`. It is used instead of `canPassInRegisters` to
37 // behave consistently on 64-bit windows platforms which are overly
38 // permissive, allowing too many types to be passed in registers.
40 // Types which can be passed in registers will be re-aligned in the called
41 // function by clang, so aren't impacted by the win32 object passing ABI
43 static bool canPassAsTemporary(const CXXRecordDecl
*D
) {
44 // Per C++ [class.temporary]p3:
46 // When an object of class type X is passed to or returned from a function,
47 // if X has at least one eligible copy or move constructor ([special]), each
48 // such constructor is trivial, and the destructor of X is either trivial or
49 // deleted, implementations are permitted to create a temporary object to
50 // hold the function parameter or result object.
52 // The temporary object is constructed from the function argument or return
53 // value, respectively, and the function's parameter or return object is
54 // initialized as if by using the eligible trivial constructor to copy the
55 // temporary (even if that constructor is inaccessible or would not be
56 // selected by overload resolution to perform a copy or move of the object).
57 bool HasNonDeletedCopyOrMove
= false;
59 if (D
->needsImplicitCopyConstructor() &&
60 !D
->defaultedCopyConstructorIsDeleted()) {
61 if (!D
->hasTrivialCopyConstructorForCall())
63 HasNonDeletedCopyOrMove
= true;
66 if (D
->needsImplicitMoveConstructor() &&
67 !D
->defaultedMoveConstructorIsDeleted()) {
68 if (!D
->hasTrivialMoveConstructorForCall())
70 HasNonDeletedCopyOrMove
= true;
73 if (D
->needsImplicitDestructor() && !D
->defaultedDestructorIsDeleted() &&
74 !D
->hasTrivialDestructorForCall())
77 for (const CXXMethodDecl
*MD
: D
->methods()) {
81 auto *CD
= dyn_cast
<CXXConstructorDecl
>(MD
);
82 if (CD
&& CD
->isCopyOrMoveConstructor())
83 HasNonDeletedCopyOrMove
= true;
84 else if (!isa
<CXXDestructorDecl
>(MD
))
87 if (!MD
->isTrivialForCall())
91 return HasNonDeletedCopyOrMove
;
94 // Adding alignas(_) on a struct implicitly marks it as MOZ_NON_PARAM, due to
95 // MSVC limitations which prevent passing explcitly aligned types by value as
96 // parameters. This overload of hasFakeAnnotation injects fake MOZ_NON_PARAM
97 // annotations onto these types.
98 std::string
getImplicitReason(const TagDecl
*D
,
99 VisitFlags
&ToVisit
) const override
{
100 // Some stdlib types are known to have alignments over the pointer size on
101 // non-win32 platforms, but should not be linted against. Clear any
102 // annotations on those types.
103 if (!D
->getASTContext().getTargetInfo().getCXXABI().isMicrosoft() &&
104 getDeclarationNamespace(D
) == "std") {
105 StringRef Name
= getNameChecked(D
);
106 if (Name
== "function") {
107 ToVisit
= VISIT_NONE
;
112 // If the type doesn't have a destructor and can be passed with a temporary,
113 // clang will handle re-aligning it for us automatically, and we don't need
114 // to worry about the passed alignment.
115 auto RD
= dyn_cast
<CXXRecordDecl
>(D
);
116 if (RD
&& RD
->isCompleteDefinition() && canPassAsTemporary(RD
)) {
120 // Check if the decl itself has an explicit alignment on it.
121 if (unsigned ExplicitAlign
= checkExplicitAlignment(D
)) {
122 return "it has an explicit alignment of '" +
123 std::to_string(ExplicitAlign
) + "'";
126 // Check if any of the decl's fields have an explicit alignment on them.
127 if (auto RD
= dyn_cast
<RecordDecl
>(D
)) {
128 for (auto F
: RD
->fields()) {
129 if (unsigned ExplicitAlign
= checkExplicitAlignment(F
)) {
130 return ("member '" + F
->getName() +
131 "' has an explicit alignment of '" +
132 std::to_string(ExplicitAlign
) + "'")
138 // We don't need to check the types of fields, as the CustomTypeAnnotation
139 // infrastructure will handle that for us.
143 NonParamAnnotation NonParam
;
145 void NonParamInsideFunctionDeclChecker::registerMatchers(
146 MatchFinder
*AstMatcher
) {
147 AstMatcher
->addMatcher(
149 anyOf(allOf(isDefinition(),
151 classTemplateSpecializationDecl().bind("spec"))),
155 AstMatcher
->addMatcher(lambdaExpr().bind("lambda"), this);
158 void NonParamInsideFunctionDeclChecker::check(
159 const MatchFinder::MatchResult
&Result
) {
160 static DenseSet
<const FunctionDecl
*> CheckedFunctionDecls
;
162 const FunctionDecl
*func
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("func");
164 const LambdaExpr
*lambda
= Result
.Nodes
.getNodeAs
<LambdaExpr
>("lambda");
166 func
= lambda
->getCallOperator();
174 if (func
->isDeleted()) {
178 // We need to skip decls which have these types as parameters in system
179 // headers, because presumably those headers act like an assertion that the
180 // alignment will be preserved in that situation.
181 if (getDeclarationNamespace(func
) == "std") {
185 if (inThirdPartyPath(func
)) {
189 // Don't report errors on the same declarations more than once.
190 if (CheckedFunctionDecls
.count(func
)) {
193 CheckedFunctionDecls
.insert(func
);
195 const ClassTemplateSpecializationDecl
*Spec
=
196 Result
.Nodes
.getNodeAs
<ClassTemplateSpecializationDecl
>("spec");
198 for (ParmVarDecl
*p
: func
->parameters()) {
199 QualType T
= p
->getType().withoutLocalFastQualifiers();
200 if (NonParam
.hasEffectiveAnnotation(T
)) {
201 diag(p
->getLocation(), "Type %0 must not be used as parameter",
202 DiagnosticIDs::Error
)
204 diag(p
->getLocation(),
205 "Please consider passing a const reference instead",
206 DiagnosticIDs::Note
);
209 diag(Spec
->getPointOfInstantiation(),
210 "The bad argument was passed to %0 here", DiagnosticIDs::Note
)
211 << Spec
->getSpecializedTemplate();
214 NonParam
.dumpAnnotationReason(*this, T
, p
->getLocation());