diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -371,6 +371,10 @@ ``#pragma clang|GCC diagnostic push|pop`` directive. (`#13920: `_) - Clang now does not try to analyze cast validity on variables with dependent alignment (`#63007: `_). +- Clang constexpr evaluator now displays member function calls more precisely + by making use of the syntactical structure of function calls. This avoids display + of syntactically invalid codes in diagnostics. + (`#57081: `_) Bug Fixes in This Version ------------------------- 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 @@ -531,6 +531,9 @@ /// This - The binding for the this pointer in this call, if any. const LValue *This; + /// CallExpr - The syntactical structure of member function calls + const Expr *CallExpr; + /// Information on how to find the arguments to this call. Our arguments /// are stored in our parent's CallStackFrame, using the ParmVarDecl* as a /// key and this value as the version. @@ -584,7 +587,7 @@ CallStackFrame(EvalInfo &Info, SourceLocation CallLoc, const FunctionDecl *Callee, const LValue *This, - CallRef Arguments); + const Expr *CallExpr, CallRef Arguments); ~CallStackFrame(); // Return the temporary for Key whose version number is Version. @@ -978,7 +981,9 @@ CallStackDepth(0), NextCallIndex(1), StepsLeft(C.getLangOpts().ConstexprStepLimit), EnableNewConstInterp(C.getLangOpts().EnableNewConstInterp), - BottomFrame(*this, SourceLocation(), nullptr, nullptr, CallRef()), + BottomFrame(*this, SourceLocation(), /*Callee=*/nullptr, + /*This=*/nullptr, + /*CallExpr=*/nullptr, CallRef()), EvaluatingDecl((const ValueDecl *)nullptr), EvaluatingDeclValue(nullptr), HasActiveDiagnostic(false), HasFoldFailureDiagnostic(false), EvalMode(Mode) {} @@ -1436,9 +1441,10 @@ CallStackFrame::CallStackFrame(EvalInfo &Info, SourceLocation CallLoc, const FunctionDecl *Callee, const LValue *This, - CallRef Call) + const Expr *CallExpr, CallRef Call) : Info(Info), Caller(Info.CurrentCall), Callee(Callee), This(This), - Arguments(Call), CallLoc(CallLoc), Index(Info.NextCallIndex++) { + CallExpr(CallExpr), Arguments(Call), CallLoc(CallLoc), + Index(Info.NextCallIndex++) { Info.CurrentCall = this; ++Info.CallStackDepth; } @@ -1918,12 +1924,29 @@ Out << *Callee << '('; if (This && IsMemberCall) { - APValue Val; - This->moveInto(Val); - Val.printPretty(Out, Info.Ctx, - This->Designator.MostDerivedType); - // FIXME: Add parens around Val if needed. - Out << "->" << *Callee << '('; + if (const auto *MCE = dyn_cast_if_present(CallExpr)) { + const Expr *Object = MCE->getImplicitObjectArgument(); + Object->printPretty(Out, /*Helper=*/nullptr, Info.Ctx.getPrintingPolicy(), + /*Indentation=*/0); + if (Object->getType()->isPointerType()) + Out << "->"; + else + Out << "."; + } else if (const auto *OCE = + dyn_cast_if_present(CallExpr)) { + OCE->getArg(0)->printPretty(Out, /*Helper=*/nullptr, + Info.Ctx.getPrintingPolicy(), + /*Indentation=*/0); + Out << "."; + } else { + APValue Val; + This->moveInto(Val); + Val.printPretty( + Out, Info.Ctx, + Info.Ctx.getLValueReferenceType(This->Designator.MostDerivedType)); + Out << "."; + } + Out << *Callee << '('; IsMemberCall = false; } @@ -6161,13 +6184,13 @@ /// Evaluate a function call. static bool HandleFunctionCall(SourceLocation CallLoc, const FunctionDecl *Callee, const LValue *This, - ArrayRef Args, CallRef Call, - const Stmt *Body, EvalInfo &Info, + const Expr *E, ArrayRef Args, + CallRef Call, const Stmt *Body, EvalInfo &Info, APValue &Result, const LValue *ResultSlot) { if (!Info.CheckCallLimit(CallLoc)) return false; - CallStackFrame Frame(Info, CallLoc, Callee, This, Call); + CallStackFrame Frame(Info, CallLoc, Callee, This, E, Call); // For a trivial copy or move assignment, perform an APValue copy. This is // essential for unions, where the operations performed by the assignment @@ -6232,7 +6255,7 @@ Info, ObjectUnderConstruction{This.getLValueBase(), This.Designator.Entries}, RD->getNumBases()); - CallStackFrame Frame(Info, CallLoc, Definition, &This, Call); + CallStackFrame Frame(Info, CallLoc, Definition, &This, E, Call); // FIXME: Creating an APValue just to hold a nonexistent return value is // wasteful. @@ -6533,7 +6556,8 @@ if (!CheckConstexprFunction(Info, CallLoc, DD, Definition, Body)) return false; - CallStackFrame Frame(Info, CallLoc, Definition, &This, CallRef()); + CallStackFrame Frame(Info, CallLoc, Definition, &This, /*CallExpr=*/nullptr, + CallRef()); // We're now in the period of destruction of this object. unsigned BasesLeft = RD->getNumBases(); @@ -7802,7 +7826,7 @@ Stmt *Body = FD->getBody(Definition); if (!CheckConstexprFunction(Info, E->getExprLoc(), FD, Definition, Body) || - !HandleFunctionCall(E->getExprLoc(), Definition, This, Args, Call, + !HandleFunctionCall(E->getExprLoc(), Definition, This, E, Args, Call, Body, Info, Result, ResultSlot)) return false; @@ -16204,7 +16228,8 @@ Info.EvalStatus.HasSideEffects = false; // Build fake call to Callee. - CallStackFrame Frame(Info, Callee->getLocation(), Callee, ThisPtr, Call); + CallStackFrame Frame(Info, Callee->getLocation(), Callee, ThisPtr, This, + Call); // FIXME: Missing ExprWithCleanups in enable_if conditions? FullExpressionRAII Scope(Info); return Evaluate(Value, Info, this) && Scope.destroy() && @@ -16261,7 +16286,8 @@ } else { SourceLocation Loc = FD->getLocation(); HandleFunctionCall(Loc, FD, (MD && MD->isInstance()) ? &This : nullptr, - Args, CallRef(), FD->getBody(), Info, Scratch, nullptr); + &VIE, Args, CallRef(), FD->getBody(), Info, Scratch, + /*ResultSlot=*/nullptr); } return Diags.empty(); @@ -16283,7 +16309,8 @@ Info.CheckingPotentialConstantExpression = true; // Fabricate a call stack frame to give the arguments a plausible cover story. - CallStackFrame Frame(Info, SourceLocation(), FD, /*This*/ nullptr, CallRef()); + CallStackFrame Frame(Info, SourceLocation(), FD, /*This=*/nullptr, + /*CallExpr=*/nullptr, CallRef()); APValue ResultScratch; Evaluate(ResultScratch, Info, E); diff --git a/clang/test/AST/Interp/constexpr-nqueens.cpp b/clang/test/AST/Interp/constexpr-nqueens.cpp --- a/clang/test/AST/Interp/constexpr-nqueens.cpp +++ b/clang/test/AST/Interp/constexpr-nqueens.cpp @@ -48,7 +48,7 @@ constexpr Board buildBoardScan(int N, int Col, int Row, const Board &B) { return Row == N ? Board(0, true) : B.ok(Row, Col) ? - tryBoard(buildBoardRecurse(N, Col + 1, B.addQueen(Row, Col)), // ref-note {{in call to '&Board()->addQueen(0, 0)}} \ + tryBoard(buildBoardRecurse(N, Col + 1, B.addQueen(Row, Col)), // ref-note {{in call to 'B.addQueen(0, 0)}} \ // expected-note {{in call to '&Board()->addQueen(0, 0)}} N, Col, Row+1, B) : buildBoardScan(N, Col, Row + 1, B); diff --git a/clang/test/AST/Interp/lambda.cpp b/clang/test/AST/Interp/lambda.cpp --- a/clang/test/AST/Interp/lambda.cpp +++ b/clang/test/AST/Interp/lambda.cpp @@ -48,7 +48,7 @@ }; return f(); // expected-note {{in call to '&f->operator()()'}} \ - // ref-note {{in call to '&f->operator()()'}} + // ref-note {{in call to 'f.operator()()'}} } static_assert(div(8, 2) == 4); static_assert(div(8, 0) == 4); // expected-error {{not an integral constant expression}} \ diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp --- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp +++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp @@ -42,12 +42,30 @@ bool ok; constexpr A(bool ok) : ok(ok) {} constexpr ~A() noexcept(false) { - void oops(); // expected-note 2{{declared here}} - if (!ok) oops(); // expected-note 2{{non-constexpr function}} + void oops(); // expected-note 6{{declared here}} + if (!ok) oops(); // expected-note 6{{non-constexpr function}} } }; +struct B { + A a[2]; + constexpr B(bool ok) : a{A(!ok), A(ok)}{} +}; + +struct Cons { + bool val[2]; + constexpr Cons() : val{true, false} {} +}; + constexpr A const_dtor(true); +static_assert(B(false).a[1].ok); // expected-error {{static assertion expression is not an integral constant expression}} \ + expected-note {{in call to 'B(false).a[1].~A()'}} expected-note {{in call to 'B(false).~B()'}} +static_assert(B(true).a[1].ok); // expected-error {{static assertion expression is not an integral constant expression}} \ + expected-note {{in call to 'B(true).a[0].~A()'}} expected-note {{in call to 'B(true).~B()'}} +static_assert(B(Cons().val[1]).a[1].ok); // expected-error {{static assertion expression is not an integral constant expression}} \ + expected-note {{in call to 'B(Cons().val[1]).a[1].~A()'}} expected-note {{in call to 'B(Cons().val[1]).~B()'}} +static_assert(B((new Cons)->val[0]).a[1].ok); // expected-error {{static assertion expression is not an integral constant expression}} \ + expected-note {{in call to 'B((new Cons)->val[0]).a[0].~A()'}} expected-note {{in call to 'B((new Cons)->val[0]).~B()'}} constexpr A non_const_dtor(false); // expected-error {{must have constant destruction}} expected-note {{in call}} -constexpr A arr_dtor[5] = {true, true, true, false, true}; // expected-error {{must have constant destruction}} expected-note {{in call to '&arr_dtor[3]->~A()'}} +constexpr A arr_dtor[5] = {true, true, true, false, true}; // expected-error {{must have constant destruction}} expected-note {{in call to 'arr_dtor[3].~A()'}} #endif diff --git a/clang/test/CXX/temp/temp.param/p8-cxx20.cpp b/clang/test/CXX/temp/temp.param/p8-cxx20.cpp --- a/clang/test/CXX/temp/temp.param/p8-cxx20.cpp +++ b/clang/test/CXX/temp/temp.param/p8-cxx20.cpp @@ -61,5 +61,5 @@ template struct Z {}; Z z1; - Z z2; // expected-error {{non-type template argument is not a constant expression}} expected-note-re {{in call to '{{.*}}->~D()'}} + Z z2; // expected-error {{non-type template argument is not a constant expression}} expected-note-re {{in call to '{{.*}}.~D()'}} } diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -975,7 +975,7 @@ int n; }; constexpr int S::f() const { - return static_cast(this)->n; // expected-note {{cannot cast}} + return static_cast(this)->n; // expected-note 5{{cannot cast}} } constexpr int S::g() const { // FIXME: Better diagnostic for this. @@ -984,8 +984,20 @@ // The T temporary is implicitly cast to an S subobject, but we can recover the // T full-object via a base-to-derived cast, or a derived-to-base-casted member // pointer. -static_assert(S().f(), ""); // expected-error {{constant expression}} expected-note {{in call to '&S()->f()'}} -static_assert(S().g(), ""); // expected-error {{constant expression}} expected-note {{in call to '&S()->g()'}} +static_assert(S().f(), ""); // expected-error {{constant expression}} expected-note {{in call to 'S().f()'}} +static_assert(S().g(), ""); // expected-error {{constant expression}} expected-note {{in call to 'S().g()'}} +constexpr S sobj; +constexpr const S& slref = sobj; +constexpr const S&& srref = S(); +constexpr const S *sptr = &sobj; +static_assert(sobj.f(), ""); // expected-error {{constant expression}} \ + expected-note {{in call to 'sobj.f()'}} +static_assert(sptr->f(), ""); // expected-error {{constant expression}} \ + expected-note {{in call to 'sptr->f()'}} +static_assert(slref.f(), ""); // expected-error {{constant expression}} \ + expected-note {{in call to 'slref.f()'}} +static_assert(srref.f(), ""); // expected-error {{constant expression}} \ + expected-note {{in call to 'srref.f()'}} static_assert(T(3).f() == 3, ""); static_assert(T(4).g() == 4, ""); diff --git a/clang/test/SemaCXX/constexpr-frame-describe.cpp b/clang/test/SemaCXX/constexpr-frame-describe.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/constexpr-frame-describe.cpp @@ -0,0 +1,60 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s + + +struct Foo { + constexpr void zomg() const { (void)(1 / 0); } // expected-error {{constant expression}} \ + expected-warning {{division by zero}} \ + expected-note 2{{division by zero}} +}; + +struct S { + constexpr S() {} + constexpr bool operator==(const S&) const { // expected-error {{never produces a constant expression}} + return 1 / 0; // expected-warning {{division by zero}} \ + expected-note 3{{division by zero}} + } + + constexpr bool heh() const { + auto F = new Foo(); + F->zomg(); // expected-note {{in call to 'F->zomg()'}} + delete F; + return false; + } +}; + +constexpr S s; + +static_assert(s.heh()); // expected-error {{constant expression}} \ + expected-note {{in call to 's.heh()'}} + +constexpr S s2; +constexpr const S *sptr = &s; +constexpr const S *sptr2 = &s2; +static_assert(s == s2); // expected-error {{constant expression}} \ + expected-note {{in call to 's.operator==(s2)'}} +static_assert(*sptr == *sptr2); // expected-error {{constant expression}} \ + expected-note {{in call to '*sptr.operator==(s2)'}} + +struct A { + constexpr int foo() { (void)(1/0); return 1;} // expected-error {{never produces a constant expression}} \ + expected-warning {{division by zero}} \ + expected-note 2{{division by zero}} +}; + +struct B { + A aa; + A *a = &aa; +}; + +struct C { + B b; +}; + +struct D { + C cc; + C *c = &cc; +}; + +constexpr D d{}; +static_assert(d.c->b.a->foo() == 1); // expected-error {{constant expression}} \ + expected-note {{in call to 'd.c->b.a->foo()'}} diff --git a/clang/test/SemaCXX/cxx2a-consteval.cpp b/clang/test/SemaCXX/cxx2a-consteval.cpp --- a/clang/test/SemaCXX/cxx2a-consteval.cpp +++ b/clang/test/SemaCXX/cxx2a-consteval.cpp @@ -840,13 +840,13 @@ copy fail1{good0}; // expected-error {{call to consteval function 'defaulted_special_member_template::copy::copy' is not a constant expression}} \ expected-note {{in call to 'copy(good0)'}} fail1 = good0; // expected-error {{call to consteval function 'defaulted_special_member_template::copy::operator=' is not a constant expression}} \ - expected-note {{in call to '&fail1->operator=(good0)'}} + expected-note {{in call to 'fail1.operator=(good0)'}} move good1; move fail2{static_cast&&>(good1)}; // expected-error {{call to consteval function 'defaulted_special_member_template::move::move' is not a constant expression}} \ expected-note {{in call to 'move(good1)'}} fail2 = static_cast&&>(good1); // expected-error {{call to consteval function 'defaulted_special_member_template::move::operator=' is not a constant expression}} \ - expected-note {{in call to '&fail2->operator=(good1)'}} + expected-note {{in call to 'fail2.operator=(good1)'}} } } // namespace defaulted_special_member_template diff --git a/clang/test/SemaCXX/deduced-return-type-cxx14.cpp b/clang/test/SemaCXX/deduced-return-type-cxx14.cpp --- a/clang/test/SemaCXX/deduced-return-type-cxx14.cpp +++ b/clang/test/SemaCXX/deduced-return-type-cxx14.cpp @@ -296,7 +296,7 @@ void f() { X().f(); Y().f(); - constexpr int q = Y().f(); // expected-error {{must be initialized by a constant expression}} expected-note {{in call to '&Y()->f()'}} + constexpr int q = Y().f(); // expected-error {{must be initialized by a constant expression}} expected-note {{in call to 'Y().f()'}} } struct NonLiteral { ~NonLiteral(); } nl; // cxx14-note {{user-provided destructor}} // cxx20_23-note@-1 {{'NonLiteral' is not literal because its destructor is not constexpr}}