Backed out 2 changesets (bug 1733498) for causing perma Localised repacks build busta...
[gecko.git] / build / clang-plugin / NonParamInsideFunctionDeclChecker.cpp
blobe74fd2bb080adec66a96ad825782c2eb4800a150
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 {
10 public:
11 NonParamAnnotation() : CustomTypeAnnotation(moz_non_param, "non-param"){};
13 protected:
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);
21 #else
22 unsigned PointerAlign = Context.getTargetInfo().getPointerAlign(0);
23 #endif
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();
32 return 0;
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
42 // alignment issue.
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())
62 return false;
63 HasNonDeletedCopyOrMove = true;
66 if (D->needsImplicitMoveConstructor() &&
67 !D->defaultedMoveConstructorIsDeleted()) {
68 if (!D->hasTrivialMoveConstructorForCall())
69 return false;
70 HasNonDeletedCopyOrMove = true;
73 if (D->needsImplicitDestructor() && !D->defaultedDestructorIsDeleted() &&
74 !D->hasTrivialDestructorForCall())
75 return false;
77 for (const CXXMethodDecl *MD : D->methods()) {
78 if (MD->isDeleted())
79 continue;
81 auto *CD = dyn_cast<CXXConstructorDecl>(MD);
82 if (CD && CD->isCopyOrMoveConstructor())
83 HasNonDeletedCopyOrMove = true;
84 else if (!isa<CXXDestructorDecl>(MD))
85 continue;
87 if (!MD->isTrivialForCall())
88 return false;
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;
108 return "";
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)) {
117 return "";
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) + "'")
133 .str();
138 // We don't need to check the types of fields, as the CustomTypeAnnotation
139 // infrastructure will handle that for us.
140 return "";
143 NonParamAnnotation NonParam;
145 void NonParamInsideFunctionDeclChecker::registerMatchers(
146 MatchFinder *AstMatcher) {
147 AstMatcher->addMatcher(
148 functionDecl(
149 anyOf(allOf(isDefinition(),
150 hasAncestor(
151 classTemplateSpecializationDecl().bind("spec"))),
152 isDefinition()))
153 .bind("func"),
154 this);
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");
163 if (!func) {
164 const LambdaExpr *lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
165 if (lambda) {
166 func = lambda->getCallOperator();
170 if (!func) {
171 return;
174 if (func->isDeleted()) {
175 return;
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") {
182 return;
185 if (inThirdPartyPath(func)) {
186 return;
189 // Don't report errors on the same declarations more than once.
190 if (CheckedFunctionDecls.count(func)) {
191 return;
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)
203 << T;
204 diag(p->getLocation(),
205 "Please consider passing a const reference instead",
206 DiagnosticIDs::Note);
208 if (Spec) {
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());