martong created this revision.
martong added reviewers: isuckatcs, NoQ, bruntib, steakhal.
Herald added subscribers: manas, ASDenysPetrov, gamesh411, dkrupp, donat.nagy, 
Szelethus, mikhail.ramalho, a.sidorin, rnkovacs, szepet, baloghadamsoftware, 
xazax.hun.
Herald added a reviewer: Szelethus.
Herald added a project: All.
martong requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Currently, we warn if the given place is not enough to hold an extra
data for the array allocation overhead (cookie). I.e. if the size of the
place is equal to the size of the target. Also, we warn even if the size
of the place is greater than the size of the target. The current logic
does not know the size of the overhead(cookie), thus we just simply
warn and this is clearly wrong as it leads to false reports. This patch
eliminates the false reports and handles C++20 with special care.

Fixes #56264

C++17 and earlier standards state that:

  new(2, f) T[5] results in a call of operator new[](sizeof(T) * 5 +
  y, 2, f). Here, ... and y are non-negative unspecified values
  representing array allocation overhead; the result of the
  new-expression will be offset by this amount from the value returned
  by operator new[]. This overhead may be applied in all array
  new-expressions, including those referencing the library function
  operator new[](std::size_t, void*) and other placement allocation
  functions. The amount of overhead may vary from one invocation of
  new to another.

Since the array overhead is an *unspecified* value, it makes sense to
warn only if the size of the Place is equal to the size of the Target.
Otherwise, e.g if we assumed that the overhead is sizeof(size_t),
then we might report a false positive.

Checking for array cookie does not makes sense if the standard is
C++20 or later. C++20 states that array overhead(cookie) is not
created if the new expression calls the non-allocating (placement)
form of the allocation function.
C++20, section 7.6.2.7 [expr.new], paragraph 15:

  That argument shall be no less than the size of the object being
  created; it may be greater than the size of the object being created
  only if the object is an array and the allocation function is not a
  non-allocating form (17.6.2.3).

C++20, section 7.6.2.7 [expr.new], paragraph 19:

  This overhead may be applied in all array new-expressions, including
  those referencing a placement allocation function, except when
  referencing the library function operator new[](std::size_t, void*).

Related Defect Report:

2382. Array allocation overhead for non-allocating placement new

https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2382


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D129280

Files:
  clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
  clang/test/Analysis/placement-new.cpp

Index: clang/test/Analysis/placement-new.cpp
===================================================================
--- clang/test/Analysis/placement-new.cpp
+++ clang/test/Analysis/placement-new.cpp
@@ -2,7 +2,14 @@
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=cplusplus.NewDelete \
 // RUN:   -analyzer-checker=cplusplus.PlacementNew \
-// RUN:   -analyzer-output=text -verify \
+// RUN:   -analyzer-output=text -verify=expected,cpp11 \
+// RUN:   -triple x86_64-unknown-linux-gnu
+
+// RUN: %clang_analyze_cc1 -std=c++20 %s \
+// RUN:   -analyzer-checker=core \
+// RUN:   -analyzer-checker=cplusplus.NewDelete \
+// RUN:   -analyzer-checker=cplusplus.PlacementNew \
+// RUN:   -analyzer-output=text -verify=expected \
 // RUN:   -triple x86_64-unknown-linux-gnu
 
 #include "Inputs/system-header-simulator-cxx.h"
@@ -157,27 +164,51 @@
 } // namespace testHierarchy
 
 namespace testArrayTypesAllocation {
-void f1() {
+void test_less() {
   struct S {
     short a;
   };
-
-  // bad (not enough space).
   const unsigned N = 32;
-  alignas(S) unsigned char buffer1[sizeof(S) * N]; // expected-note {{'buffer1' initialized here}}
-  ::new (buffer1) S[N];                            // expected-warning{{Storage provided to placement new is only 64 bytes, whereas the allocated array type requires more space for internal needs}} expected-note 1 {{}}
+  alignas(S) unsigned char buffer1[sizeof(S) * N/2]; // expected-note {{'buffer1' initialized here}}
+  ::new (buffer1) S[N];                              // expected-warning{{Storage provided to placement new is only 32 bytes, whereas the allocated type requires 64 bytes}} expected-note 1 {{}}
 }
 
-void f2() {
+void test_equal() {
   struct S {
     short a;
   };
+  // Bad (not enough space).
+  // This should not appear if the standard is >= C++20.
+  // C++20 states that array overhead(cookie) is not created if the new
+  // expression calls the non-allocating (placement) form of the allocation
+  // function.
+  // C++20, section 7.6.2.7 [expr.new], paragraph 15:
+  //   That argument shall be no less than the size of the object being
+  //   created; it may be greater than the size of the object being created
+  //   only if the object is an array and the allocation function is not a
+  //   non-allocating form (17.6.2.3).
+  // C++20, section 7.6.2.7 [expr.new], paragraph 19:
+  //    This overhead may be applied in all array new-expressions, including
+  //    those referencing a placement allocation function, except when
+  //    referencing the library function operator new[](std::size_t, void*).
+  // Related Defect Report:
+  // 2382. Array allocation overhead for non-allocating placement new
+  // https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2382
 
-  // maybe ok but we need to warn.
   const unsigned N = 32;
-  alignas(S) unsigned char buffer2[sizeof(S) * N + sizeof(int)]; // expected-note {{'buffer2' initialized here}}
-  ::new (buffer2) S[N];                                          // expected-warning{{68 bytes is possibly not enough for array allocation which requires 64 bytes. Current overhead requires the size of 4 bytes}} expected-note 1 {{}}
+  alignas(S) unsigned char buffer1[sizeof(S) * N]; // cpp11-note {{'buffer1' initialized here}}
+  ::new (buffer1) S[N];                            // cpp11-warning{{Storage provided to placement new is only 64 bytes, whereas the allocated array type might require more space for allocation overhead}} cpp11-note 1 {{}}
+}
+
+int test_greater() {
+  struct s {
+    int x;
+  };
+  s arr[4];
+  new (arr + 1) s[1]; // no-warning
+  return 0;
 }
+
 } // namespace testArrayTypesAllocation
 
 namespace testStructAlign {
Index: clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
+++ clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
@@ -111,32 +111,57 @@
   if (!SizeOfPlaceCI)
     return true;
 
-  if ((SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) ||
-      (IsArrayTypeAllocated &&
-       SizeOfPlaceCI->getValue() >= SizeOfTargetCI->getValue())) {
+  const llvm::APSInt &PlaceSize = SizeOfPlaceCI->getValue();
+  const llvm::APSInt &TargetSize = SizeOfTargetCI->getValue();
+
+  bool ArrayOverheadCondition =
+      // C++17 and earlier standards state that:
+      //   new(2, f) T[5] results in a call of operator new[](sizeof(T) * 5 +
+      //   y, 2, f). Here, ... and y are non-negative unspecified values
+      //   representing array allocation overhead; the result of the
+      //   new-expression will be offset by this amount from the value returned
+      //   by operator new[]. This overhead may be applied in all array
+      //   new-expressions, including those referencing the library function
+      //   operator new[](std::size_t, void*) and other placement allocation
+      //   functions. The amount of overhead may vary from one invocation of
+      //   new to another.
+      // Since the array overhead is an *unspecified* value, it makes sense to
+      // warn only if the size of the Place is equal to the size of the Target.
+      // Otherwise, e.g if we assumed that the overhead is sizeof(size_t),
+      // then we might report a false positive.
+      IsArrayTypeAllocated && (PlaceSize == TargetSize) &&
+      // Checking for array cookie does not makes sense if the standard is
+      // C++20 or later. C++20 states that array overhead(cookie) is not
+      // created if the new expression calls the non-allocating (placement)
+      // form of the allocation function.
+      // C++20, section 7.6.2.7 [expr.new], paragraph 15:
+      //   That argument shall be no less than the size of the object being
+      //   created; it may be greater than the size of the object being created
+      //   only if the object is an array and the allocation function is not a
+      //   non-allocating form (17.6.2.3).
+      // C++20, section 7.6.2.7 [expr.new], paragraph 19:
+      //    This overhead may be applied in all array new-expressions, including
+      //    those referencing a placement allocation function, except when
+      //    referencing the library function operator new[](std::size_t, void*).
+      // Related Defect Report:
+      // 2382. Array allocation overhead for non-allocating placement new
+      // https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2382
+      !C.getASTContext().getLangOpts().CPlusPlus20;
+
+  if ((PlaceSize < TargetSize) || ArrayOverheadCondition) {
     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())
+      if (ArrayOverheadCondition)
         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()));
+            "whereas the allocated array type might require more space for "
+            "allocation overhead",
+            PlaceSize, TargetSize));
       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()));
+            PlaceSize, TargetSize));
 
       auto R = std::make_unique<PathSensitiveBugReport>(SBT, Msg, N);
       bugreporter::trackExpressionValue(N, NE->getPlacementArg(0), *R);
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to