| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 15 | #include "clang/AST/CharUnits.h" |
| 16 | #include "clang/AST/DeclTemplate.h" |
| 17 | #include "clang/AST/RecordLayout.h" |
| 18 | #include "clang/AST/RecursiveASTVisitor.h" |
| 19 | #include "clang/Driver/DriverDiagnostic.h" |
| 20 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
| 21 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| 22 | #include "clang/StaticAnalyzer/Core/Checker.h" |
| 23 | #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" |
| 24 | #include "llvm/ADT/SmallString.h" |
| 25 | #include "llvm/Support/MathExtras.h" |
| 26 | #include "llvm/Support/raw_ostream.h" |
| 27 | #include <numeric> |
| 28 | |
| 29 | using namespace clang; |
| 30 | using namespace ento; |
| 31 | |
| 32 | namespace { |
| 33 | class PaddingChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
| 34 | private: |
| 35 | mutable std::unique_ptr<BugType> PaddingBug; |
| 36 | mutable BugReporter *BR; |
| 37 | |
| 38 | public: |
| 39 | int64_t AllowedPad; |
| 40 | |
| 41 | void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, |
| 42 | BugReporter &BRArg) const { |
| 43 | BR = &BRArg; |
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { |
| 49 | const PaddingChecker *Checker; |
| 50 | bool shouldVisitTemplateInstantiations() const { return true; } |
| 51 | bool shouldVisitImplicitCode() const { return true; } |
| 52 | explicit LocalVisitor(const PaddingChecker *Checker) : Checker(Checker) {} |
| 53 | bool VisitRecordDecl(const RecordDecl *RD) { |
| 54 | Checker->visitRecord(RD); |
| 55 | return true; |
| 56 | } |
| 57 | bool VisitVarDecl(const VarDecl *VD) { |
| 58 | Checker->visitVariable(VD); |
| 59 | return true; |
| 60 | } |
| 61 | |
| 62 | }; |
| 63 | |
| 64 | LocalVisitor visitor(this); |
| 65 | visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); |
| 66 | } |
| 67 | |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
| 72 | void visitRecord(const RecordDecl *RD, uint64_t PadMultiplier = 1) const { |
| 73 | if (shouldSkipDecl(RD)) |
| 74 | return; |
| 75 | |
| 76 | |
| 77 | |
| 78 | if (!(RD = RD->getDefinition())) |
| 79 | return; |
| 80 | |
| 81 | |
| 82 | |
| 83 | |
| 84 | |
| 85 | if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) |
| 86 | if (CXXRD->field_empty() && CXXRD->getNumBases() == 1) |
| 87 | return visitRecord(CXXRD->bases().begin()->getType()->getAsRecordDecl(), |
| 88 | PadMultiplier); |
| 89 | |
| 90 | auto &ASTContext = RD->getASTContext(); |
| 91 | const ASTRecordLayout &RL = ASTContext.getASTRecordLayout(RD); |
| 92 | assert(llvm::isPowerOf2_64(RL.getAlignment().getQuantity())); |
| 93 | |
| 94 | CharUnits BaselinePad = calculateBaselinePad(RD, ASTContext, RL); |
| 95 | if (BaselinePad.isZero()) |
| 96 | return; |
| 97 | |
| 98 | CharUnits OptimalPad; |
| 99 | SmallVector<const FieldDecl *, 20> OptimalFieldsOrder; |
| 100 | std::tie(OptimalPad, OptimalFieldsOrder) = |
| 101 | calculateOptimalPad(RD, ASTContext, RL); |
| 102 | |
| 103 | CharUnits DiffPad = PadMultiplier * (BaselinePad - OptimalPad); |
| 104 | if (DiffPad.getQuantity() <= AllowedPad) { |
| 105 | (0) . __assert_fail ("!DiffPad.isNegative() && \"DiffPad should not be negative\"", "/home/seafit/code_projects/clang_source/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp", 105, __PRETTY_FUNCTION__))" file_link="../../../../include/assert.h.html#88" macro="true">assert(!DiffPad.isNegative() && "DiffPad should not be negative"); |
| 106 | |
| 107 | return; |
| 108 | } |
| 109 | reportRecord(RD, BaselinePad, OptimalPad, OptimalFieldsOrder); |
| 110 | } |
| 111 | |
| 112 | |
| 113 | |
| 114 | void visitVariable(const VarDecl *VD) const { |
| 115 | const ArrayType *ArrTy = VD->getType()->getAsArrayTypeUnsafe(); |
| 116 | if (ArrTy == nullptr) |
| 117 | return; |
| 118 | uint64_t Elts = 0; |
| 119 | if (const ConstantArrayType *CArrTy = dyn_cast<ConstantArrayType>(ArrTy)) |
| 120 | Elts = CArrTy->getSize().getZExtValue(); |
| 121 | if (Elts == 0) |
| 122 | return; |
| 123 | const RecordType *RT = ArrTy->getElementType()->getAs<RecordType>(); |
| 124 | if (RT == nullptr) |
| 125 | return; |
| 126 | |
| 127 | |
| 128 | visitRecord(RT->getDecl(), Elts); |
| 129 | } |
| 130 | |
| 131 | bool shouldSkipDecl(const RecordDecl *RD) const { |
| 132 | |
| 133 | |
| 134 | if (!(RD = RD->getDefinition())) |
| 135 | return true; |
| 136 | auto Location = RD->getLocation(); |
| 137 | |
| 138 | |
| 139 | if (!Location.isValid()) |
| 140 | return true; |
| 141 | SrcMgr::CharacteristicKind Kind = |
| 142 | BR->getSourceManager().getFileCharacteristic(Location); |
| 143 | |
| 144 | if (Kind != SrcMgr::C_User) |
| 145 | return true; |
| 146 | |
| 147 | |
| 148 | if (RD->isUnion()) |
| 149 | return true; |
| 150 | if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) { |
| 151 | |
| 152 | |
| 153 | |
| 154 | |
| 155 | if (!CXXRD->field_empty() && CXXRD->getNumBases() != 0) |
| 156 | return true; |
| 157 | if (CXXRD->field_empty() && CXXRD->getNumBases() != 1) |
| 158 | return true; |
| 159 | |
| 160 | if (CXXRD->getNumVBases() != 0) |
| 161 | return true; |
| 162 | |
| 163 | |
| 164 | if (CXXRD->getTypeForDecl()->isDependentType()) |
| 165 | return true; |
| 166 | if (CXXRD->getTypeForDecl()->isInstantiationDependentType()) |
| 167 | return true; |
| 168 | } |
| 169 | |
| 170 | else if (RD->field_empty()) |
| 171 | return true; |
| 172 | |
| 173 | auto IsTrickyField = [](const FieldDecl *FD) -> bool { |
| 174 | |
| 175 | if (FD->isBitField()) |
| 176 | return true; |
| 177 | |
| 178 | |
| 179 | QualType Ty = FD->getType(); |
| 180 | if (Ty->isIncompleteArrayType()) |
| 181 | return true; |
| 182 | return false; |
| 183 | }; |
| 184 | |
| 185 | if (std::any_of(RD->field_begin(), RD->field_end(), IsTrickyField)) |
| 186 | return true; |
| 187 | return false; |
| 188 | } |
| 189 | |
| 190 | static CharUnits calculateBaselinePad(const RecordDecl *RD, |
| 191 | const ASTContext &ASTContext, |
| 192 | const ASTRecordLayout &RL) { |
| 193 | CharUnits PaddingSum; |
| 194 | CharUnits Offset = ASTContext.toCharUnitsFromBits(RL.getFieldOffset(0)); |
| 195 | for (const FieldDecl *FD : RD->fields()) { |
| 196 | |
| 197 | |
| 198 | |
| 199 | |
| 200 | CharUnits FieldSize = ASTContext.getTypeSizeInChars(FD->getType()); |
| 201 | auto FieldOffsetBits = RL.getFieldOffset(FD->getFieldIndex()); |
| 202 | CharUnits FieldOffset = ASTContext.toCharUnitsFromBits(FieldOffsetBits); |
| 203 | PaddingSum += (FieldOffset - Offset); |
| 204 | Offset = FieldOffset + FieldSize; |
| 205 | } |
| 206 | PaddingSum += RL.getSize() - Offset; |
| 207 | return PaddingSum; |
| 208 | } |
| 209 | |
| 210 | |
| 211 | |
| 212 | |
| 213 | |
| 214 | |
| 215 | |
| 216 | |
| 217 | |
| 218 | |
| 219 | |
| 220 | |
| 221 | |
| 222 | |
| 223 | |
| 224 | |
| 225 | |
| 226 | static std::pair<CharUnits, SmallVector<const FieldDecl *, 20>> |
| 227 | calculateOptimalPad(const RecordDecl *RD, const ASTContext &ASTContext, |
| 228 | const ASTRecordLayout &RL) { |
| 229 | struct FieldInfo { |
| 230 | CharUnits Align; |
| 231 | CharUnits Size; |
| 232 | const FieldDecl *Field; |
| 233 | bool operator<(const FieldInfo &RHS) const { |
| 234 | |
| 235 | |
| 236 | |
| 237 | return std::make_tuple(Align, -Size, |
| 238 | Field ? -static_cast<int>(Field->getFieldIndex()) |
| 239 | : 0) < |
| 240 | std::make_tuple( |
| 241 | RHS.Align, -RHS.Size, |
| 242 | RHS.Field ? -static_cast<int>(RHS.Field->getFieldIndex()) |
| 243 | : 0); |
| 244 | } |
| 245 | }; |
| 246 | SmallVector<FieldInfo, 20> Fields; |
| 247 | auto GatherSizesAndAlignments = [](const FieldDecl *FD) { |
| 248 | FieldInfo RetVal; |
| 249 | RetVal.Field = FD; |
| 250 | auto &Ctx = FD->getASTContext(); |
| 251 | std::tie(RetVal.Size, RetVal.Align) = |
| 252 | Ctx.getTypeInfoInChars(FD->getType()); |
| 253 | assert(llvm::isPowerOf2_64(RetVal.Align.getQuantity())); |
| 254 | if (auto Max = FD->getMaxAlignment()) |
| 255 | RetVal.Align = std::max(Ctx.toCharUnitsFromBits(Max), RetVal.Align); |
| 256 | return RetVal; |
| 257 | }; |
| 258 | std::transform(RD->field_begin(), RD->field_end(), |
| 259 | std::back_inserter(Fields), GatherSizesAndAlignments); |
| 260 | llvm::sort(Fields); |
| 261 | |
| 262 | |
| 263 | |
| 264 | |
| 265 | CharUnits NewOffset = ASTContext.toCharUnitsFromBits(RL.getFieldOffset(0)); |
| 266 | CharUnits NewPad; |
| 267 | SmallVector<const FieldDecl *, 20> OptimalFieldsOrder; |
| 268 | while (!Fields.empty()) { |
| 269 | unsigned TrailingZeros = |
| 270 | llvm::countTrailingZeros((unsigned long long)NewOffset.getQuantity()); |
| 271 | |
| 272 | |
| 273 | |
| 274 | long long CurAlignmentBits = 1ull << (std::min)(TrailingZeros, 62u); |
| 275 | CharUnits CurAlignment = CharUnits::fromQuantity(CurAlignmentBits); |
| 276 | FieldInfo InsertPoint = {CurAlignment, CharUnits::Zero(), nullptr}; |
| 277 | auto CurBegin = Fields.begin(); |
| 278 | auto CurEnd = Fields.end(); |
| 279 | |
| 280 | |
| 281 | |
| 282 | |
| 283 | |
| 284 | auto Iter = std::upper_bound(CurBegin, CurEnd, InsertPoint); |
| 285 | if (Iter != CurBegin) { |
| 286 | |
| 287 | --Iter; |
| 288 | NewOffset += Iter->Size; |
| 289 | OptimalFieldsOrder.push_back(Iter->Field); |
| 290 | Fields.erase(Iter); |
| 291 | } else { |
| 292 | |
| 293 | |
| 294 | |
| 295 | CharUnits NextOffset = NewOffset.alignTo(Fields[0].Align); |
| 296 | NewPad += NextOffset - NewOffset; |
| 297 | NewOffset = NextOffset; |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | CharUnits NewSize = NewOffset.alignTo(RL.getAlignment()); |
| 302 | NewPad += NewSize - NewOffset; |
| 303 | return {NewPad, std::move(OptimalFieldsOrder)}; |
| 304 | } |
| 305 | |
| 306 | void reportRecord( |
| 307 | const RecordDecl *RD, CharUnits BaselinePad, CharUnits OptimalPad, |
| 308 | const SmallVector<const FieldDecl *, 20> &OptimalFieldsOrder) const { |
| 309 | if (!PaddingBug) |
| 310 | PaddingBug = |
| 311 | llvm::make_unique<BugType>(this, "Excessive Padding", "Performance"); |
| 312 | |
| 313 | SmallString<100> Buf; |
| 314 | llvm::raw_svector_ostream Os(Buf); |
| 315 | Os << "Excessive padding in '"; |
| 316 | Os << QualType::getAsString(RD->getTypeForDecl(), Qualifiers(), |
| 317 | LangOptions()) |
| 318 | << "'"; |
| 319 | |
| 320 | if (auto *TSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) { |
| 321 | |
| 322 | |
| 323 | |
| 324 | SourceLocation ILoc = TSD->getPointOfInstantiation(); |
| 325 | if (ILoc.isValid()) |
| 326 | Os << " instantiated here: " |
| 327 | << ILoc.printToString(BR->getSourceManager()); |
| 328 | } |
| 329 | |
| 330 | Os << " (" << BaselinePad.getQuantity() << " padding bytes, where " |
| 331 | << OptimalPad.getQuantity() << " is optimal). \n" |
| 332 | << "Optimal fields order: \n"; |
| 333 | for (const auto *FD : OptimalFieldsOrder) |
| 334 | Os << FD->getName() << ", \n"; |
| 335 | Os << "consider reordering the fields or adding explicit padding " |
| 336 | "members."; |
| 337 | |
| 338 | PathDiagnosticLocation CELoc = |
| 339 | PathDiagnosticLocation::create(RD, BR->getSourceManager()); |
| 340 | auto Report = llvm::make_unique<BugReport>(*PaddingBug, Os.str(), CELoc); |
| 341 | Report->setDeclWithIssue(RD); |
| 342 | Report->addRange(RD->getSourceRange()); |
| 343 | BR->emitReport(std::move(Report)); |
| 344 | } |
| 345 | }; |
| 346 | } |
| 347 | |
| 348 | void ento::registerPaddingChecker(CheckerManager &Mgr) { |
| 349 | auto *Checker = Mgr.registerChecker<PaddingChecker>(); |
| 350 | Checker->AllowedPad = Mgr.getAnalyzerOptions() |
| 351 | .getCheckerIntegerOption(Checker, "AllowedPad", 24); |
| 352 | if (Checker->AllowedPad < 0) |
| 353 | Mgr.reportInvalidCheckerOptionValue( |
| 354 | Checker, "AllowedPad", "a non-negative value"); |
| 355 | } |
| 356 | |
| 357 | bool ento::shouldRegisterPaddingChecker(const LangOptions &LO) { |
| 358 | return true; |
| 359 | } |
| 360 | |