diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -769,10 +769,13 @@ (`#62796 `_) - Merge lambdas in require expressions in standard C++ modules. (`#63544 `_) - - Fix location of default member initialization in parenthesized aggregate initialization. (`#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 `_) Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h --- a/clang/include/clang/AST/Type.h +++ b/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); diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/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">; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/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::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 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; } diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp --- a/clang/lib/AST/Type.cpp +++ b/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()); diff --git a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc-limits.cpp b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc-limits.cpp new file mode 100644 --- /dev/null +++ b/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 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 + constexpr void construct_at(void *p, Args &&...args) { // #construct + new (p) T((Args&&)args...); + } +} + +namespace GH63562 { + +template +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(data + i, i); // #construct_call + + } + constexpr ~S() { + alloc.deallocate(data); + } + std::allocator alloc; + T* data; +}; + +constexpr S 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 s2(1099511627777); +S s3(~0ULL); + +// We can allocate this array but we hikt the number of steps +constexpr S 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 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 +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}} + + + +}