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/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,43 @@ return false; } + bool IsArrayTooLarge(unsigned BitWidth, uint64_t ElemCount) { + if (BitWidth > ConstantArrayType::getMaxSizeBits(Ctx)) + return true; + + // 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 (ElemCount > std::numeric_limits::max()) + return true; + + // FIXME: GH63562 + // Arrays allocate an APValue per element. + // When constant folding avoid allocating arrays larger than 100MB to + // avoid running out of memory when compiling reasonable programs. + // Constant Expression evaluation can still exhaust the sytem resources + // and crash the compiler. + bool RequiresConstantEvaluation = + !(checkingPotentialConstantExpression() || + EvalMode == EvalInfo::EM_IgnoreSideEffects || + EvalMode == EvalInfo::EM_ConstantFold); + if (RequiresConstantEvaluation) { + // Check whether we are performing constant initialization + if (const auto *VD = dyn_cast_if_present( + EvaluatingDecl.dyn_cast()); + VD && VD->hasGlobalStorage()) { + RequiresConstantEvaluation = + VD->isConstexpr() || VD->hasAttr(); + } + } + if (ElemCount * sizeof(APValue) > 100'000'000 && + !RequiresConstantEvaluation) + return true; + + return false; + } + std::pair getCallFrameAndDepth(unsigned CallIndex) { assert(CallIndex && "no call index in getCallFrameAndDepth"); @@ -3583,6 +3620,19 @@ llvm_unreachable("unknown evaluating decl kind"); } +static bool CheckArraySize(EvalInfo &Info, const ConstantArrayType *CAT, + SourceLocation CallLoc = {}) { + if (Info.IsArrayTooLarge(CAT->getNumAddressingBits(Info.Ctx), + CAT->getSize().getZExtValue())) { + Info.FFDiag(CAT->getSizeExpr() ? CAT->getSizeExpr()->getBeginLoc() + : CallLoc, + diag::note_constexpr_new_too_large) + << CAT->getSize().getZExtValue(); + return false; + } + return true; +} + namespace { /// A handle to a complete object (an object that is not a subobject of /// another object). @@ -3757,6 +3807,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 +6544,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 +6555,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); @@ -6727,12 +6784,11 @@ return false; } - if (ByteSize.getActiveBits() > ConstantArrayType::getMaxSizeBits(Info.Ctx)) { + if (Info.IsArrayTooLarge(ByteSize.getActiveBits(), Size.getZExtValue())) { 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,9 +9671,9 @@ // -- 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.IsArrayTooLarge(ConstantArrayType::getNumAddressingBits( + Info.Ctx, AllocType, ArrayBound), + ArrayBound.getZExtValue())) { if (IsNothrow) return ZeroInitialization(E); 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,66 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s +// RUN: %clang_cc1 -std=c++2a -verify=constexpr %s -DCONSTEXPR +// expected-no-diagnostics + +namespace std { + using size_t = decltype(sizeof(0)); + 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); + } + }; +} +void *operator new(std::size_t, void *p) { return p; } + +namespace std { + template + constexpr void construct_at(void *p, Args &&...args) { + 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); + + } + constexpr ~S() { + alloc.deallocate(data); + } + std::allocator alloc; + T* data; +}; + +#ifdef CONSTEXPR +constexpr S s(1099511627777); // constexpr-error {{constexpr variable 's' must be initialized by a constant expression}} \ // expected-note@#alloc {{annot allocate array; evaluated array bound 1099511627777 is too large}} \ + // constexpr-note@#call {{in call to 'this->alloc.allocate(1099511627777)'}} \ + // constexpr-note@#alloc {{cannot allocate array; evaluated array bound 1099511627777 is too large}} \ + // constexpr-note {{in call to 'S(1099511627777)'}} +#else +// Check that we do not try to fold very large arrays +S s2(1099511627777); +S s3(~0ULL); +#endif + +// Check we do not perform constant initialization in the presence +// of very large arrays (this used to crash) + +constexpr int stack_array(auto N) { + char BIG[N]; + return 0; +} + +int a = stack_array(~0U); +int b = stack_array(~0ULL); + +} +