diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -88,6 +88,10 @@ /// Checks if a method can be called. bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F); +/// Checks if calling the currently active function would exceed +/// the allowed call depth. +bool CheckCallDepth(InterpState &S, CodePtr OpPC); + /// Checks the 'this' pointer. bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This); @@ -158,7 +162,6 @@ template ::T> bool Ret(InterpState &S, CodePtr &PC, APValue &Result) { - S.CallStackDepth--; const T &Ret = S.Stk.pop(); assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); @@ -181,8 +184,6 @@ template inline bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) { - S.CallStackDepth--; - assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); if (Builtin || !S.checkingPotentialConstantExpression()) S.Current->popArgs(); @@ -1598,6 +1599,9 @@ if (!CheckCallable(S, OpPC, Func)) return false; + if (!CheckCallDepth(S, OpPC)) + return false; + auto NewFrame = std::make_unique(S, Func, OpPC); InterpFrame *FrameBefore = S.Current; S.Current = NewFrame.get(); diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -341,6 +341,17 @@ return true; } +bool CheckCallDepth(InterpState &S, CodePtr OpPC) { + if ((S.Current->getDepth() + 1) > S.getLangOpts().ConstexprCallDepth) { + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_depth_limit_exceeded) + << S.getLangOpts().ConstexprCallDepth; + return false; + } + + return true; +} + bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This) { if (!This.isZero()) return true; diff --git a/clang/lib/AST/Interp/InterpFrame.h b/clang/lib/AST/Interp/InterpFrame.h --- a/clang/lib/AST/Interp/InterpFrame.h +++ b/clang/lib/AST/Interp/InterpFrame.h @@ -15,7 +15,6 @@ #include "Frame.h" #include "Program.h" -#include "State.h" #include #include @@ -120,6 +119,8 @@ const Expr *getExpr(CodePtr PC) const; SourceLocation getLocation(CodePtr PC) const; + unsigned getDepth() const { return Depth; } + private: /// Returns an original argument from the stack. template const T &stackRef(unsigned Offset) const { @@ -145,6 +146,8 @@ private: /// Reference to the interpreter state. InterpState &S; + /// Depth of this frame. + unsigned Depth; /// Reference to the function being executed. const Function *Func; /// Current object pointer for methods. diff --git a/clang/lib/AST/Interp/InterpFrame.cpp b/clang/lib/AST/Interp/InterpFrame.cpp --- a/clang/lib/AST/Interp/InterpFrame.cpp +++ b/clang/lib/AST/Interp/InterpFrame.cpp @@ -23,8 +23,8 @@ InterpFrame::InterpFrame(InterpState &S, const Function *Func, InterpFrame *Caller, CodePtr RetPC) - : Caller(Caller), S(S), Func(Func), RetPC(RetPC), - ArgSize(Func ? Func->getArgSize() : 0), + : Caller(Caller), S(S), Depth(Caller ? Caller->Depth + 1 : 0), Func(Func), + RetPC(RetPC), ArgSize(Func ? Func->getArgSize() : 0), Args(static_cast(S.Stk.top())), FrameOffset(S.Stk.size()) { if (!Func) return; diff --git a/clang/lib/AST/Interp/InterpState.h b/clang/lib/AST/Interp/InterpState.h --- a/clang/lib/AST/Interp/InterpState.h +++ b/clang/lib/AST/Interp/InterpState.h @@ -15,6 +15,7 @@ #include "Context.h" #include "Function.h" +#include "InterpFrame.h" #include "InterpStack.h" #include "State.h" #include "clang/AST/APValue.h" @@ -41,7 +42,9 @@ // Stack frame accessors. Frame *getSplitFrame() { return Parent.getCurrentFrame(); } Frame *getCurrentFrame() override; - unsigned getCallStackDepth() override { return CallStackDepth; } + unsigned getCallStackDepth() override { + return Current ? (Current->getDepth() + 1) : 1; + } const Frame *getBottomFrame() const override { return Parent.getBottomFrame(); } @@ -103,8 +106,6 @@ Context &Ctx; /// The current frame. InterpFrame *Current = nullptr; - /// Call stack depth. - unsigned CallStackDepth; }; } // namespace interp diff --git a/clang/lib/AST/Interp/InterpState.cpp b/clang/lib/AST/Interp/InterpState.cpp --- a/clang/lib/AST/Interp/InterpState.cpp +++ b/clang/lib/AST/Interp/InterpState.cpp @@ -17,8 +17,7 @@ InterpState::InterpState(State &Parent, Program &P, InterpStack &Stk, Context &Ctx, SourceMapper *M) - : Parent(Parent), M(M), P(P), Stk(Stk), Ctx(Ctx), Current(nullptr), - CallStackDepth(Parent.getCallStackDepth() + 1) {} + : Parent(Parent), M(M), P(P), Stk(Stk), Ctx(Ctx), Current(nullptr) {} InterpState::~InterpState() { while (Current) { diff --git a/clang/test/AST/Interp/depth-limit.cpp b/clang/test/AST/Interp/depth-limit.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/Interp/depth-limit.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -fconstexpr-depth 100 -verify %s +// RUN: %clang_cc1 -fconstexpr-depth 100 -verify=ref %s + +constexpr int f(int a) { + if (a == 100) + return 1 / 0; // expected-warning {{division by zero is undefined}} \ + // ref-warning {{division by zero is undefined}} + + return f(a + 1); // ref-note {{exceeded maximum depth of 100 calls}} \ + // ref-note {{in call to 'f(99)'}} \ + // ref-note {{in call to 'f(98)'}} \ + // ref-note {{in call to 'f(97)'}} \ + // ref-note {{in call to 'f(96)'}} \ + // ref-note {{in call to 'f(95)'}} \ + // ref-note {{skipping 90 calls in backtrace}} \ + // ref-note {{in call to 'f(4)'}} \ + // ref-note {{in call to 'f(3)'}} \ + // ref-note {{in call to 'f(2)'}} \ + // ref-note {{in call to 'f(1)'}} \ + // expected-note {{exceeded maximum depth of 100 calls}} \ + // expected-note {{in call to 'f(99)'}} \ + // expected-note {{in call to 'f(98)'}} \ + // expected-note {{in call to 'f(97)'}} \ + // expected-note {{in call to 'f(96)'}} \ + // expected-note {{in call to 'f(95)'}} \ + // expected-note {{skipping 90 calls in backtrace}} \ + // expected-note {{in call to 'f(4)'}} \ + // expected-note {{in call to 'f(3)'}} \ + // expected-note {{in call to 'f(2)'}} \ + // expected-note {{in call to 'f(1)'}} +} +static_assert(f(0) == 100); // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to 'f(0)'}} \ + // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to 'f(0)'}} diff --git a/clang/test/AST/Interp/depth-limit2.cpp b/clang/test/AST/Interp/depth-limit2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/Interp/depth-limit2.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -fconstexpr-depth 2 -verify %s +// RUN: %clang_cc1 -fconstexpr-depth 2 -verify=ref %s + + +constexpr int func() { + return 12; +} + +constexpr int foo() { + return func(); // expected-note {{exceeded maximum depth of 2 calls}} \ + // ref-note {{exceeded maximum depth of 2 calls}} +} + +constexpr int bar() { + return foo(); // expected-note {{in call to 'foo()'}} \ + // ref-note {{in call to 'foo()'}} +} + +static_assert(bar() == 12); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to 'bar()'}} \ + // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to 'bar()'}} +