NonParamInsideFunctionDeclChecker.cpp (7898B)
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/. */ 4 5 #include "NonParamInsideFunctionDeclChecker.h" 6 #include "CustomMatchers.h" 7 #include "clang/Basic/TargetInfo.h" 8 9 class NonParamAnnotation : public CustomTypeAnnotation { 10 public: 11 NonParamAnnotation() : CustomTypeAnnotation(moz_non_param, "non-param"){}; 12 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 24 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(); 31 } 32 return 0; 33 } 34 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. 39 // 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: 45 // 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. 51 // 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; 58 59 if (D->needsImplicitCopyConstructor() && 60 !D->defaultedCopyConstructorIsDeleted()) { 61 if (!D->hasTrivialCopyConstructorForCall()) 62 return false; 63 HasNonDeletedCopyOrMove = true; 64 } 65 66 if (D->needsImplicitMoveConstructor() && 67 !D->defaultedMoveConstructorIsDeleted()) { 68 if (!D->hasTrivialMoveConstructorForCall()) 69 return false; 70 HasNonDeletedCopyOrMove = true; 71 } 72 73 if (D->needsImplicitDestructor() && !D->defaultedDestructorIsDeleted() && 74 !D->hasTrivialDestructorForCall()) 75 return false; 76 77 for (const CXXMethodDecl *MD : D->methods()) { 78 if (MD->isDeleted()) 79 continue; 80 81 auto *CD = dyn_cast<CXXConstructorDecl>(MD); 82 if (CD && CD->isCopyOrMoveConstructor()) 83 HasNonDeletedCopyOrMove = true; 84 else if (!isa<CXXDestructorDecl>(MD)) 85 continue; 86 87 if (!MD->isTrivialForCall()) 88 return false; 89 } 90 91 return HasNonDeletedCopyOrMove; 92 } 93 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 ""; 109 } 110 } 111 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 ""; 118 } 119 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) + "'"; 124 } 125 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(); 134 } 135 } 136 } 137 138 // We don't need to check the types of fields, as the CustomTypeAnnotation 139 // infrastructure will handle that for us. 140 return ""; 141 } 142 }; 143 NonParamAnnotation NonParam; 144 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); 156 } 157 158 void NonParamInsideFunctionDeclChecker::check( 159 const MatchFinder::MatchResult &Result) { 160 static DenseSet<const FunctionDecl *> CheckedFunctionDecls; 161 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(); 167 } 168 } 169 170 if (!func) { 171 return; 172 } 173 174 if (func->isDeleted()) { 175 return; 176 } 177 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; 183 } 184 185 if (inThirdPartyPath(func)) { 186 return; 187 } 188 189 // Don't report errors on the same declarations more than once. 190 if (CheckedFunctionDecls.count(func)) { 191 return; 192 } 193 CheckedFunctionDecls.insert(func); 194 195 const ClassTemplateSpecializationDecl *Spec = 196 Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("spec"); 197 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); 207 208 if (Spec) { 209 diag(Spec->getPointOfInstantiation(), 210 "The bad argument was passed to %0 here", DiagnosticIDs::Note) 211 << Spec->getSpecializedTemplate(); 212 } 213 214 NonParam.dumpAnnotationReason(*this, T, p->getLocation()); 215 } 216 } 217 }