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
19using namespace clang;
20using namespace ento;
21
22namespace {
23class PlacementNewChecker : public Checker<check::PreStmt<CXXNewExpr>> {
24public:
25  void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const;
26
27private:
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
64SVal 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
70SVal 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
102bool 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
152void 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
167unsigned 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
176void 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
235void 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
260bool 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
274bool 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
295void 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
310void ento::registerPlacementNewChecker(CheckerManager &mgr) {
311  mgr.registerChecker<PlacementNewChecker>();
312}
313
314bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) {
315  return true;
316}
317