1 //==- CheckPlacementNew.cpp - Check for placement new operation --*- C++ -*-==// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines a check for misuse of the default placement new operator. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 15 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 16 #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" 17 #include "llvm/Support/FormatVariadic.h" 18 19 using namespace clang; 20 using namespace ento; 21 22 namespace { 23 class PlacementNewChecker : public Checker<check::PreStmt<CXXNewExpr>> { 24 public: 25 void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const; 26 27 private: 28 bool checkPlaceCapacityIsSufficient(const CXXNewExpr *NE, 29 CheckerContext &C) const; 30 31 bool checkPlaceIsAlignedProperly(const CXXNewExpr *NE, 32 CheckerContext &C) const; 33 34 // Returns the size of the target in a placement new expression. 35 // E.g. in "new (&s) long" it returns the size of `long`. 36 SVal getExtentSizeOfNewTarget(const CXXNewExpr *NE, CheckerContext &C, 37 bool &IsArray) const; 38 // Returns the size of the place in a placement new expression. 39 // E.g. in "new (&s) long" it returns the size of `s`. 40 SVal getExtentSizeOfPlace(const CXXNewExpr *NE, CheckerContext &C) const; 41 42 void emitBadAlignReport(const Expr *P, CheckerContext &C, 43 unsigned AllocatedTAlign, 44 unsigned StorageTAlign) const; 45 unsigned getStorageAlign(CheckerContext &C, const ValueDecl *VD) const; 46 47 void checkElementRegionAlign(const ElementRegion *R, CheckerContext &C, 48 const Expr *P, unsigned AllocatedTAlign) const; 49 50 void checkFieldRegionAlign(const FieldRegion *R, CheckerContext &C, 51 const Expr *P, unsigned AllocatedTAlign) const; 52 53 bool isVarRegionAlignedProperly(const VarRegion *R, CheckerContext &C, 54 const Expr *P, 55 unsigned AllocatedTAlign) const; 56 57 BugType SBT{this, "Insufficient storage for placement new", 58 categories::MemoryError}; 59 BugType ABT{this, "Bad align storage for placement new", 60 categories::MemoryError}; 61 }; 62 } // namespace 63 64 SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE, 65 CheckerContext &C) const { 66 const Expr *Place = NE->getPlacementArg(0); 67 return getDynamicExtentWithOffset(C.getState(), C.getSVal(Place)); 68 } 69 70 SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, 71 CheckerContext &C, 72 bool &IsArray) const { 73 ProgramStateRef State = C.getState(); 74 SValBuilder &SvalBuilder = C.getSValBuilder(); 75 QualType ElementType = NE->getAllocatedType(); 76 ASTContext &AstContext = C.getASTContext(); 77 CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType); 78 IsArray = false; 79 if (NE->isArray()) { 80 IsArray = true; 81 const Expr *SizeExpr = *NE->getArraySize(); 82 SVal ElementCount = C.getSVal(SizeExpr); 83 if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) { 84 // size in Bytes = ElementCountNL * TypeSize 85 return SvalBuilder.evalBinOp( 86 State, BO_Mul, *ElementCountNL, 87 SvalBuilder.makeArrayIndex(TypeSize.getQuantity()), 88 SvalBuilder.getArrayIndexType()); 89 } 90 } else { 91 // Create a concrete int whose size in bits and signedness is equal to 92 // ArrayIndexType. 93 llvm::APInt I(AstContext.getTypeSizeInChars(SvalBuilder.getArrayIndexType()) 94 .getQuantity() * 95 C.getASTContext().getCharWidth(), 96 TypeSize.getQuantity()); 97 return SvalBuilder.makeArrayIndex(I.getZExtValue()); 98 } 99 return UnknownVal(); 100 } 101 102 bool PlacementNewChecker::checkPlaceCapacityIsSufficient( 103 const CXXNewExpr *NE, CheckerContext &C) const { 104 bool IsArrayTypeAllocated; 105 SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, C, IsArrayTypeAllocated); 106 SVal SizeOfPlace = getExtentSizeOfPlace(NE, C); 107 const auto SizeOfTargetCI = SizeOfTarget.getAs<nonloc::ConcreteInt>(); 108 if (!SizeOfTargetCI) 109 return true; 110 const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>(); 111 if (!SizeOfPlaceCI) 112 return true; 113 114 if ((SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) || 115 (IsArrayTypeAllocated && 116 SizeOfPlaceCI->getValue() >= SizeOfTargetCI->getValue())) { 117 if (ExplodedNode *N = C.generateErrorNode(C.getState())) { 118 std::string Msg; 119 // TODO: use clang constant 120 if (IsArrayTypeAllocated && 121 SizeOfPlaceCI->getValue() > SizeOfTargetCI->getValue()) 122 Msg = std::string(llvm::formatv( 123 "{0} bytes is possibly not enough for array allocation which " 124 "requires {1} bytes. Current overhead requires the size of {2} " 125 "bytes", 126 SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue(), 127 SizeOfPlaceCI->getValue() - SizeOfTargetCI->getValue())); 128 else if (IsArrayTypeAllocated && 129 SizeOfPlaceCI->getValue() == SizeOfTargetCI->getValue()) 130 Msg = std::string(llvm::formatv( 131 "Storage provided to placement new is only {0} bytes, " 132 "whereas the allocated array type requires more space for " 133 "internal needs", 134 SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue())); 135 else 136 Msg = std::string(llvm::formatv( 137 "Storage provided to placement new is only {0} bytes, " 138 "whereas the allocated type requires {1} bytes", 139 SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue())); 140 141 auto R = std::make_unique<PathSensitiveBugReport>(SBT, Msg, N); 142 bugreporter::trackExpressionValue(N, NE->getPlacementArg(0), *R); 143 C.emitReport(std::move(R)); 144 145 return false; 146 } 147 } 148 149 return true; 150 } 151 152 void PlacementNewChecker::emitBadAlignReport(const Expr *P, CheckerContext &C, 153 unsigned AllocatedTAlign, 154 unsigned StorageTAlign) const { 155 ProgramStateRef State = C.getState(); 156 if (ExplodedNode *N = C.generateErrorNode(State)) { 157 std::string Msg(llvm::formatv("Storage type is aligned to {0} bytes but " 158 "allocated type is aligned to {1} bytes", 159 StorageTAlign, AllocatedTAlign)); 160 161 auto R = std::make_unique<PathSensitiveBugReport>(ABT, Msg, N); 162 bugreporter::trackExpressionValue(N, P, *R); 163 C.emitReport(std::move(R)); 164 } 165 } 166 167 unsigned PlacementNewChecker::getStorageAlign(CheckerContext &C, 168 const ValueDecl *VD) const { 169 unsigned StorageTAlign = C.getASTContext().getTypeAlign(VD->getType()); 170 if (unsigned SpecifiedAlignment = VD->getMaxAlignment()) 171 StorageTAlign = SpecifiedAlignment; 172 173 return StorageTAlign / C.getASTContext().getCharWidth(); 174 } 175 176 void PlacementNewChecker::checkElementRegionAlign( 177 const ElementRegion *R, CheckerContext &C, const Expr *P, 178 unsigned AllocatedTAlign) const { 179 auto IsBaseRegionAlignedProperly = [this, R, &C, P, 180 AllocatedTAlign]() -> bool { 181 // Unwind nested ElementRegion`s to get the type. 182 const MemRegion *SuperRegion = R; 183 while (true) { 184 if (SuperRegion->getKind() == MemRegion::ElementRegionKind) { 185 SuperRegion = cast<SubRegion>(SuperRegion)->getSuperRegion(); 186 continue; 187 } 188 189 break; 190 } 191 192 const DeclRegion *TheElementDeclRegion = SuperRegion->getAs<DeclRegion>(); 193 if (!TheElementDeclRegion) 194 return false; 195 196 const DeclRegion *BaseDeclRegion = R->getBaseRegion()->getAs<DeclRegion>(); 197 if (!BaseDeclRegion) 198 return false; 199 200 unsigned BaseRegionAlign = 0; 201 // We must use alignment TheElementDeclRegion if it has its own alignment 202 // specifier 203 if (TheElementDeclRegion->getDecl()->getMaxAlignment()) 204 BaseRegionAlign = getStorageAlign(C, TheElementDeclRegion->getDecl()); 205 else 206 BaseRegionAlign = getStorageAlign(C, BaseDeclRegion->getDecl()); 207 208 if (AllocatedTAlign > BaseRegionAlign) { 209 emitBadAlignReport(P, C, AllocatedTAlign, BaseRegionAlign); 210 return false; 211 } 212 213 return true; 214 }; 215 216 auto CheckElementRegionOffset = [this, R, &C, P, AllocatedTAlign]() -> void { 217 RegionOffset TheOffsetRegion = R->getAsOffset(); 218 if (TheOffsetRegion.hasSymbolicOffset()) 219 return; 220 221 unsigned Offset = 222 TheOffsetRegion.getOffset() / C.getASTContext().getCharWidth(); 223 unsigned AddressAlign = Offset % AllocatedTAlign; 224 if (AddressAlign != 0) { 225 emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign); 226 return; 227 } 228 }; 229 230 if (IsBaseRegionAlignedProperly()) { 231 CheckElementRegionOffset(); 232 } 233 } 234 235 void PlacementNewChecker::checkFieldRegionAlign( 236 const FieldRegion *R, CheckerContext &C, const Expr *P, 237 unsigned AllocatedTAlign) const { 238 const MemRegion *BaseRegion = R->getBaseRegion(); 239 if (!BaseRegion) 240 return; 241 242 if (const VarRegion *TheVarRegion = BaseRegion->getAs<VarRegion>()) { 243 if (isVarRegionAlignedProperly(TheVarRegion, C, P, AllocatedTAlign)) { 244 // We've checked type align but, unless FieldRegion 245 // offset is zero, we also need to check its own 246 // align. 247 RegionOffset Offset = R->getAsOffset(); 248 if (Offset.hasSymbolicOffset()) 249 return; 250 251 int64_t OffsetValue = 252 Offset.getOffset() / C.getASTContext().getCharWidth(); 253 unsigned AddressAlign = OffsetValue % AllocatedTAlign; 254 if (AddressAlign != 0) 255 emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign); 256 } 257 } 258 } 259 260 bool PlacementNewChecker::isVarRegionAlignedProperly( 261 const VarRegion *R, CheckerContext &C, const Expr *P, 262 unsigned AllocatedTAlign) const { 263 const VarDecl *TheVarDecl = R->getDecl(); 264 unsigned StorageTAlign = getStorageAlign(C, TheVarDecl); 265 if (AllocatedTAlign > StorageTAlign) { 266 emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign); 267 268 return false; 269 } 270 271 return true; 272 } 273 274 bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE, 275 CheckerContext &C) const { 276 const Expr *Place = NE->getPlacementArg(0); 277 278 QualType AllocatedT = NE->getAllocatedType(); 279 unsigned AllocatedTAlign = C.getASTContext().getTypeAlign(AllocatedT) / 280 C.getASTContext().getCharWidth(); 281 282 SVal PlaceVal = C.getSVal(Place); 283 if (const MemRegion *MRegion = PlaceVal.getAsRegion()) { 284 if (const ElementRegion *TheElementRegion = MRegion->getAs<ElementRegion>()) 285 checkElementRegionAlign(TheElementRegion, C, Place, AllocatedTAlign); 286 else if (const FieldRegion *TheFieldRegion = MRegion->getAs<FieldRegion>()) 287 checkFieldRegionAlign(TheFieldRegion, C, Place, AllocatedTAlign); 288 else if (const VarRegion *TheVarRegion = MRegion->getAs<VarRegion>()) 289 isVarRegionAlignedProperly(TheVarRegion, C, Place, AllocatedTAlign); 290 } 291 292 return true; 293 } 294 295 void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE, 296 CheckerContext &C) const { 297 // Check only the default placement new. 298 if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator()) 299 return; 300 301 if (NE->getNumPlacementArgs() == 0) 302 return; 303 304 if (!checkPlaceCapacityIsSufficient(NE, C)) 305 return; 306 307 checkPlaceIsAlignedProperly(NE, C); 308 } 309 310 void ento::registerPlacementNewChecker(CheckerManager &mgr) { 311 mgr.registerChecker<PlacementNewChecker>(); 312 } 313 314 bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) { 315 return true; 316 } 317