aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/clang14/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
blob: 99e11a15c08dc2c836859995cc5c19e43bef413c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//==- CheckPlacementNew.cpp - Check for placement new operation --*- C++ -*-==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
//  This file defines a check for misuse of the default placement new operator.
//
//===----------------------------------------------------------------------===//

#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
#include "llvm/Support/FormatVariadic.h"

using namespace clang;
using namespace ento;

namespace {
class PlacementNewChecker : public Checker<check::PreStmt<CXXNewExpr>> {
public:
  void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const;

private:
  bool checkPlaceCapacityIsSufficient(const CXXNewExpr *NE,
                                      CheckerContext &C) const;

  bool checkPlaceIsAlignedProperly(const CXXNewExpr *NE,
                                   CheckerContext &C) const;

  // Returns the size of the target in a placement new expression.
  // E.g. in "new (&s) long" it returns the size of `long`.
  SVal getExtentSizeOfNewTarget(const CXXNewExpr *NE, CheckerContext &C,
                                bool &IsArray) const;
  // Returns the size of the place in a placement new expression.
  // E.g. in "new (&s) long" it returns the size of `s`.
  SVal getExtentSizeOfPlace(const CXXNewExpr *NE, CheckerContext &C) const;

  void emitBadAlignReport(const Expr *P, CheckerContext &C,
                          unsigned AllocatedTAlign,
                          unsigned StorageTAlign) const;
  unsigned getStorageAlign(CheckerContext &C, const ValueDecl *VD) const;

  void checkElementRegionAlign(const ElementRegion *R, CheckerContext &C,
                               const Expr *P, unsigned AllocatedTAlign) const;

  void checkFieldRegionAlign(const FieldRegion *R, CheckerContext &C,
                             const Expr *P, unsigned AllocatedTAlign) const;

  bool isVarRegionAlignedProperly(const VarRegion *R, CheckerContext &C,
                                  const Expr *P,
                                  unsigned AllocatedTAlign) const;

  BugType SBT{this, "Insufficient storage for placement new",
              categories::MemoryError};
  BugType ABT{this, "Bad align storage for placement new",
              categories::MemoryError};
};
} // namespace

SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE,
                                               CheckerContext &C) const {
  const Expr *Place = NE->getPlacementArg(0);
  return getDynamicExtentWithOffset(C.getState(), C.getSVal(Place));
}

SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE,
                                                   CheckerContext &C,
                                                   bool &IsArray) const {
  ProgramStateRef State = C.getState();
  SValBuilder &SvalBuilder = C.getSValBuilder();
  QualType ElementType = NE->getAllocatedType();
  ASTContext &AstContext = C.getASTContext();
  CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType);
  IsArray = false;
  if (NE->isArray()) {
    IsArray = true;
    const Expr *SizeExpr = *NE->getArraySize();
    SVal ElementCount = C.getSVal(SizeExpr);
    if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) {
      // size in Bytes = ElementCountNL * TypeSize
      return SvalBuilder.evalBinOp(
          State, BO_Mul, *ElementCountNL,
          SvalBuilder.makeArrayIndex(TypeSize.getQuantity()),
          SvalBuilder.getArrayIndexType());
    }
  } else {
    // Create a concrete int whose size in bits and signedness is equal to
    // ArrayIndexType.
    llvm::APInt I(AstContext.getTypeSizeInChars(SvalBuilder.getArrayIndexType())
                          .getQuantity() *
                      C.getASTContext().getCharWidth(),
                  TypeSize.getQuantity());
    return SvalBuilder.makeArrayIndex(I.getZExtValue());
  }
  return UnknownVal();
}

bool PlacementNewChecker::checkPlaceCapacityIsSufficient(
    const CXXNewExpr *NE, CheckerContext &C) const {
  bool IsArrayTypeAllocated;
  SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, C, IsArrayTypeAllocated);
  SVal SizeOfPlace = getExtentSizeOfPlace(NE, C);
  const auto SizeOfTargetCI = SizeOfTarget.getAs<nonloc::ConcreteInt>();
  if (!SizeOfTargetCI)
    return true;
  const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>();
  if (!SizeOfPlaceCI)
    return true;

  if ((SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) ||
      (IsArrayTypeAllocated &&
       SizeOfPlaceCI->getValue() >= SizeOfTargetCI->getValue())) {
    if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
      std::string Msg;
      // TODO: use clang constant
      if (IsArrayTypeAllocated &&
          SizeOfPlaceCI->getValue() > SizeOfTargetCI->getValue())
        Msg = std::string(llvm::formatv(
            "{0} bytes is possibly not enough for array allocation which "
            "requires {1} bytes. Current overhead requires the size of {2} "
            "bytes",
            SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue(),
            SizeOfPlaceCI->getValue() - SizeOfTargetCI->getValue()));
      else if (IsArrayTypeAllocated &&
               SizeOfPlaceCI->getValue() == SizeOfTargetCI->getValue())
        Msg = std::string(llvm::formatv(
            "Storage provided to placement new is only {0} bytes, "
            "whereas the allocated array type requires more space for "
            "internal needs",
            SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));
      else
        Msg = std::string(llvm::formatv(
            "Storage provided to placement new is only {0} bytes, "
            "whereas the allocated type requires {1} bytes",
            SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));

      auto R = std::make_unique<PathSensitiveBugReport>(SBT, Msg, N);
      bugreporter::trackExpressionValue(N, NE->getPlacementArg(0), *R);
      C.emitReport(std::move(R));

      return false;
    }
  }

  return true;
}

void PlacementNewChecker::emitBadAlignReport(const Expr *P, CheckerContext &C,
                                             unsigned AllocatedTAlign,
                                             unsigned StorageTAlign) const {
  ProgramStateRef State = C.getState();
  if (ExplodedNode *N = C.generateErrorNode(State)) {
    std::string Msg(llvm::formatv("Storage type is aligned to {0} bytes but "
                                  "allocated type is aligned to {1} bytes",
                                  StorageTAlign, AllocatedTAlign));

    auto R = std::make_unique<PathSensitiveBugReport>(ABT, Msg, N);
    bugreporter::trackExpressionValue(N, P, *R);
    C.emitReport(std::move(R));
  }
}

unsigned PlacementNewChecker::getStorageAlign(CheckerContext &C,
                                              const ValueDecl *VD) const {
  unsigned StorageTAlign = C.getASTContext().getTypeAlign(VD->getType());
  if (unsigned SpecifiedAlignment = VD->getMaxAlignment())
    StorageTAlign = SpecifiedAlignment;

  return StorageTAlign / C.getASTContext().getCharWidth();
}

void PlacementNewChecker::checkElementRegionAlign(
    const ElementRegion *R, CheckerContext &C, const Expr *P,
    unsigned AllocatedTAlign) const {
  auto IsBaseRegionAlignedProperly = [this, R, &C, P,
                                      AllocatedTAlign]() -> bool {
    // Unwind nested ElementRegion`s to get the type.
    const MemRegion *SuperRegion = R;
    while (true) {
      if (SuperRegion->getKind() == MemRegion::ElementRegionKind) {
        SuperRegion = cast<SubRegion>(SuperRegion)->getSuperRegion();
        continue;
      }

      break;
    }

    const DeclRegion *TheElementDeclRegion = SuperRegion->getAs<DeclRegion>();
    if (!TheElementDeclRegion)
      return false;

    const DeclRegion *BaseDeclRegion = R->getBaseRegion()->getAs<DeclRegion>();
    if (!BaseDeclRegion)
      return false;

    unsigned BaseRegionAlign = 0;
    // We must use alignment TheElementDeclRegion if it has its own alignment
    // specifier
    if (TheElementDeclRegion->getDecl()->getMaxAlignment())
      BaseRegionAlign = getStorageAlign(C, TheElementDeclRegion->getDecl());
    else
      BaseRegionAlign = getStorageAlign(C, BaseDeclRegion->getDecl());

    if (AllocatedTAlign > BaseRegionAlign) {
      emitBadAlignReport(P, C, AllocatedTAlign, BaseRegionAlign);
      return false;
    }

    return true;
  };

  auto CheckElementRegionOffset = [this, R, &C, P, AllocatedTAlign]() -> void {
    RegionOffset TheOffsetRegion = R->getAsOffset();
    if (TheOffsetRegion.hasSymbolicOffset())
      return;

    unsigned Offset =
        TheOffsetRegion.getOffset() / C.getASTContext().getCharWidth();
    unsigned AddressAlign = Offset % AllocatedTAlign;
    if (AddressAlign != 0) {
      emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign);
      return;
    }
  };

  if (IsBaseRegionAlignedProperly()) {
    CheckElementRegionOffset();
  }
}

void PlacementNewChecker::checkFieldRegionAlign(
    const FieldRegion *R, CheckerContext &C, const Expr *P,
    unsigned AllocatedTAlign) const {
  const MemRegion *BaseRegion = R->getBaseRegion();
  if (!BaseRegion)
    return;

  if (const VarRegion *TheVarRegion = BaseRegion->getAs<VarRegion>()) {
    if (isVarRegionAlignedProperly(TheVarRegion, C, P, AllocatedTAlign)) {
      // We've checked type align but, unless FieldRegion
      // offset is zero, we also need to check its own
      // align.
      RegionOffset Offset = R->getAsOffset();
      if (Offset.hasSymbolicOffset())
        return;

      int64_t OffsetValue =
          Offset.getOffset() / C.getASTContext().getCharWidth();
      unsigned AddressAlign = OffsetValue % AllocatedTAlign;
      if (AddressAlign != 0)
        emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign);
    }
  }
}

bool PlacementNewChecker::isVarRegionAlignedProperly(
    const VarRegion *R, CheckerContext &C, const Expr *P,
    unsigned AllocatedTAlign) const {
  const VarDecl *TheVarDecl = R->getDecl();
  unsigned StorageTAlign = getStorageAlign(C, TheVarDecl);
  if (AllocatedTAlign > StorageTAlign) {
    emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign);

    return false;
  }

  return true;
}

bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE,
                                                      CheckerContext &C) const {
  const Expr *Place = NE->getPlacementArg(0);

  QualType AllocatedT = NE->getAllocatedType();
  unsigned AllocatedTAlign = C.getASTContext().getTypeAlign(AllocatedT) /
                             C.getASTContext().getCharWidth();

  SVal PlaceVal = C.getSVal(Place);
  if (const MemRegion *MRegion = PlaceVal.getAsRegion()) {
    if (const ElementRegion *TheElementRegion = MRegion->getAs<ElementRegion>())
      checkElementRegionAlign(TheElementRegion, C, Place, AllocatedTAlign);
    else if (const FieldRegion *TheFieldRegion = MRegion->getAs<FieldRegion>())
      checkFieldRegionAlign(TheFieldRegion, C, Place, AllocatedTAlign);
    else if (const VarRegion *TheVarRegion = MRegion->getAs<VarRegion>())
      isVarRegionAlignedProperly(TheVarRegion, C, Place, AllocatedTAlign);
  }

  return true;
}

void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE,
                                       CheckerContext &C) const {
  // Check only the default placement new.
  if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator())
    return;

  if (NE->getNumPlacementArgs() == 0)
    return;

  if (!checkPlaceCapacityIsSufficient(NE, C))
    return;

  checkPlaceIsAlignedProperly(NE, C);
}

void ento::registerPlacementNewChecker(CheckerManager &mgr) {
  mgr.registerChecker<PlacementNewChecker>();
}

bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) {
  return true;
}