cor3ntin updated this revision to Diff 543472.
cor3ntin added a comment.

- Use -fconstexpr-steps to limit the size of the array.
- Aply the logic to both constant evaluation and constant folding as is 
constant evaluation fail the limit can now be increased.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D155955/new/

https://reviews.llvm.org/D155955

Files:
  clang/docs/ReleaseNotes.rst
  clang/include/clang/AST/Type.h
  clang/include/clang/Basic/DiagnosticASTKinds.td
  clang/lib/AST/ExprConstant.cpp
  clang/lib/AST/Type.cpp
  clang/test/SemaCXX/cxx2a-constexpr-dynalloc-limits.cpp

Index: clang/test/SemaCXX/cxx2a-constexpr-dynalloc-limits.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/cxx2a-constexpr-dynalloc-limits.cpp
@@ -0,0 +1,82 @@
+// RUN: %clang_cc1 -std=c++2a -verify -fconstexpr-steps=1024 %s
+
+namespace std {
+  using size_t = decltype(sizeof(0));
+}
+
+void *operator new(std::size_t, void *p) { return p; }
+
+namespace std {
+  template<typename T> struct allocator {
+    constexpr T *allocate(size_t N) {
+      return (T*)operator new(sizeof(T) * N); // #alloc
+    }
+    constexpr void deallocate(void *p) {
+      operator delete(p);
+    }
+  };
+  template<typename T, typename ...Args>
+  constexpr void construct_at(void *p, Args &&...args) { // #construct
+    new (p) T((Args&&)args...);
+  }
+}
+
+namespace GH63562 {
+
+template <typename T>
+struct S {
+    constexpr S(unsigned long long N)
+    : data(nullptr){
+        data = alloc.allocate(N);  // #call
+        for(std::size_t i = 0; i < N; i ++)
+            std::construct_at<T>(data + i, i); // #construct_call
+
+    }
+    constexpr ~S() {
+        alloc.deallocate(data);
+    }
+    std::allocator<T> alloc;
+    T* data;
+};
+
+constexpr S<std::size_t> s(1099511627777); // expected-error {{constexpr variable 's' must be initialized by a constant expression}} \
+                                           // expected-note@#call {{in call to 'this->alloc.allocate(1099511627777)'}} \
+                                           // expected-note@#alloc {{cannot allocate array; evaluated array bound 1099511627777 is too large}} \
+                                           // expected-note {{in call to 'S(1099511627777)'}}
+// Check that we do not try to fold very large arrays
+S<std::size_t> s2(1099511627777);
+S<std::size_t> s3(~0ULL);
+
+// We can allocate this array but we hikt the number of steps
+constexpr S<std::size_t> s4(1024); // expected-error {{constexpr variable 's4' must be initialized by a constant expression}} \
+                                   // expected-note@#construct {{constexpr evaluation hit maximum step limit; possible infinite loop?}} \
+                                   // expected-note@#construct_call {{in call}} \
+                                   // expected-note {{in call}}
+
+
+
+constexpr S<std::size_t> s5(1025); // expected-error{{constexpr variable 's5' must be initialized by a constant expression}} \
+                                   // expected-note@#alloc {{cannot allocate array; evaluated array bound 1025 exceeds the limit (1024); use -fconstexpr-steps=1025 to increase this limit}} \
+                                   // expected-note@#call {{in call to 'this->alloc.allocate(1025)'}} \
+                                   // expected-note {{in call}}
+
+
+// Check we do not perform constant initialization in the presence
+// of very large arrays (this used to crash)
+
+template <auto N>
+constexpr int stack_array() {
+    [[maybe_unused]] char BIG[N] = {0};  // expected-note  {{cannot allocate array; evaluated array bound 1025 exceeds the limit (1024); use -fconstexpr-steps=1025 to increase this limit}}
+    return BIG[N-1];
+}
+
+int a = stack_array<~0U>();
+int c = stack_array<1024>();
+int d = stack_array<1025>();
+constexpr int e = stack_array<1024>();
+constexpr int f = stack_array<1025>(); // expected-error {{constexpr variable 'f' must be initialized by a constant expression}} \
+                                       //  expected-note {{in call}}
+
+
+
+}
Index: clang/lib/AST/Type.cpp
===================================================================
--- clang/lib/AST/Type.cpp
+++ clang/lib/AST/Type.cpp
@@ -175,6 +175,11 @@
   return TotalSize.getActiveBits();
 }
 
+unsigned
+ConstantArrayType::getNumAddressingBits(const ASTContext &Context) const {
+  return getNumAddressingBits(Context, getElementType(), getSize());
+}
+
 unsigned ConstantArrayType::getMaxSizeBits(const ASTContext &Context) {
   unsigned Bits = Context.getTypeSize(Context.getSizeType());
 
Index: clang/lib/AST/ExprConstant.cpp
===================================================================
--- clang/lib/AST/ExprConstant.cpp
+++ clang/lib/AST/ExprConstant.cpp
@@ -1019,6 +1019,34 @@
       return false;
     }
 
+    bool CheckArraySize(SourceLocation Loc, unsigned BitWidth,
+                        uint64_t ElemCount, bool Diag) {
+      // FIXME: GH63562
+      // APValue stores array extents as unsigned,
+      // so anything that is greater that unsigned would overflow when
+      // constructing the array, we catch this here.
+      if (BitWidth > ConstantArrayType::getMaxSizeBits(Ctx) ||
+          ElemCount > uint64_t(std::numeric_limits<unsigned>::max())) {
+        if (Diag)
+          FFDiag(Loc, diag::note_constexpr_new_too_large) << ElemCount;
+        return false;
+      }
+
+      // FIXME: GH63562
+      // Arrays allocate an APValue per element.
+      // We use the number of constexpr steps as a proxy for the maximum size
+      // of arrays to avoid exhausting the system resources, as initialization
+      // of each element in likely to take some number of steps anyway.
+      uint64_t Limit = Ctx.getLangOpts().ConstexprStepLimit;
+      if (ElemCount > Limit) {
+        if (Diag)
+          FFDiag(Loc, diag::note_constexpr_new_exceeds_limits)
+              << ElemCount << Limit;
+        return false;
+      }
+      return true;
+    }
+
     std::pair<CallStackFrame *, unsigned>
     getCallFrameAndDepth(unsigned CallIndex) {
       assert(CallIndex && "no call index in getCallFrameAndDepth");
@@ -3583,6 +3611,14 @@
   llvm_unreachable("unknown evaluating decl kind");
 }
 
+static bool CheckArraySize(EvalInfo &Info, const ConstantArrayType *CAT,
+                           SourceLocation CallLoc = {}) {
+  return Info.CheckArraySize(
+      CAT->getSizeExpr() ? CAT->getSizeExpr()->getBeginLoc() : CallLoc,
+      CAT->getNumAddressingBits(Info.Ctx), CAT->getSize().getZExtValue(),
+      /*Diag=*/true);
+}
+
 namespace {
 /// A handle to a complete object (an object that is not a subobject of
 /// another object).
@@ -3757,6 +3793,9 @@
       if (O->getArrayInitializedElts() > Index)
         O = &O->getArrayInitializedElt(Index);
       else if (!isRead(handler.AccessKind)) {
+        if (!CheckArraySize(Info, CAT, E->getExprLoc()))
+          return handler.failed();
+
         expandArray(*O, Index);
         O = &O->getArrayInitializedElt(Index);
       } else
@@ -6491,6 +6530,9 @@
     uint64_t Size = CAT->getSize().getZExtValue();
     QualType ElemT = CAT->getElementType();
 
+    if (!CheckArraySize(Info, CAT, CallLoc))
+      return false;
+
     LValue ElemLV = This;
     ElemLV.addArray(Info, &LocE, CAT);
     if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, Size))
@@ -6499,8 +6541,9 @@
     // Ensure that we have actual array elements available to destroy; the
     // destructors might mutate the value, so we can't run them on the array
     // filler.
-    if (Size && Size > Value.getArrayInitializedElts())
+    if (Size && Size > Value.getArrayInitializedElts()) {
       expandArray(Value, Value.getArraySize() - 1);
+    }
 
     for (; Size != 0; --Size) {
       APValue &Elem = Value.getArrayInitializedElt(Size - 1);
@@ -6681,7 +6724,7 @@
   return HandleDestructionImpl(Info, Loc, LV, Value, T);
 }
 
-/// Perform a call to 'perator new' or to `__builtin_operator_new'.
+/// Perform a call to 'operator new' or to `__builtin_operator_new'.
 static bool HandleOperatorNewCall(EvalInfo &Info, const CallExpr *E,
                                   LValue &Result) {
   if (Info.checkingPotentialConstantExpression() ||
@@ -6727,13 +6770,12 @@
     return false;
   }
 
-  if (ByteSize.getActiveBits() > ConstantArrayType::getMaxSizeBits(Info.Ctx)) {
+  if (!Info.CheckArraySize(E->getBeginLoc(), ByteSize.getActiveBits(),
+                           Size.getZExtValue(), /*Diag=*/!IsNothrow)) {
     if (IsNothrow) {
       Result.setNull(Info.Ctx, E->getType());
       return true;
     }
-
-    Info.FFDiag(E, diag::note_constexpr_new_too_large) << APSInt(Size, true);
     return false;
   }
 
@@ -9615,14 +9657,12 @@
 
     //   -- its value is such that the size of the allocated object would
     //      exceed the implementation-defined limit
-    if (ConstantArrayType::getNumAddressingBits(Info.Ctx, AllocType,
-                                                ArrayBound) >
-        ConstantArrayType::getMaxSizeBits(Info.Ctx)) {
+    if (!Info.CheckArraySize(ArraySize.value()->getExprLoc(),
+                             ConstantArrayType::getNumAddressingBits(
+                                 Info.Ctx, AllocType, ArrayBound),
+                             ArrayBound.getZExtValue(), /*Diag=*/!IsNothrow)) {
       if (IsNothrow)
         return ZeroInitialization(E);
-
-      Info.FFDiag(*ArraySize, diag::note_constexpr_new_too_large)
-        << ArrayBound << (*ArraySize)->getSourceRange();
       return false;
     }
 
Index: clang/include/clang/Basic/DiagnosticASTKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticASTKinds.td
+++ clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -351,6 +351,9 @@
   "cannot allocate array; evaluated array bound %0 is negative">;
 def note_constexpr_new_too_large : Note<
   "cannot allocate array; evaluated array bound %0 is too large">;
+def note_constexpr_new_exceeds_limits : Note<
+  "cannot allocate array; evaluated array bound %0 exceeds the limit (%1); "
+  "use -fconstexpr-steps=%0 to increase this limit">;
 def note_constexpr_new_too_small : Note<
   "cannot allocate array; evaluated array bound %0 is too small to hold "
   "%1 explicitly initialized elements">;
Index: clang/include/clang/AST/Type.h
===================================================================
--- clang/include/clang/AST/Type.h
+++ clang/include/clang/AST/Type.h
@@ -3144,6 +3144,8 @@
                                        QualType ElementType,
                                        const llvm::APInt &NumElements);
 
+  unsigned getNumAddressingBits(const ASTContext &Context) const;
+
   /// Determine the maximum number of active bits that an array's size
   /// can require, which limits the maximum size of the array.
   static unsigned getMaxSizeBits(const ASTContext &Context);
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -769,10 +769,13 @@
   (`#62796 <https://github.com/llvm/llvm-project/issues/62796>`_)
 - Merge lambdas in require expressions in standard C++ modules.
   (`#63544 <https://github.com/llvm/llvm-project/issues/63544>`_)
-
 - Fix location of default member initialization in parenthesized aggregate
   initialization.
   (`#63903 <https://github.com/llvm/llvm-project/issues/63903>`_)
+- Clang no longer tries to evaluate at compile time arrays that are large
+  enough to cause resource exhaustion, unless they are part of a constant
+  expression.
+  (`#63562 <https://github.com/llvm/llvm-project/issues/63562>`_)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to