diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1326,6 +1326,10 @@ // an immediate function context, so they need to be tracked independently. bool InDiscardedStatement; bool InImmediateFunctionContext; + // Set to true in contexts where the standard requires to avoid evaluating + // immediate calls to std::source_location::current(). These are default + // arguments and class field initializers. + bool DelaySourceLocationCurrentCalls; ExpressionEvaluationContextRecord(ExpressionEvaluationContext Context, unsigned NumCleanupObjects, @@ -1335,7 +1339,8 @@ : Context(Context), ParentCleanup(ParentCleanup), NumCleanupObjects(NumCleanupObjects), NumTypos(0), ManglingContextDecl(ManglingContextDecl), ExprContext(ExprContext), - InDiscardedStatement(false), InImmediateFunctionContext(false) {} + InDiscardedStatement(false), InImmediateFunctionContext(false), + DelaySourceLocationCurrentCalls(false) {} bool isUnevaluated() const { return Context == ExpressionEvaluationContext::Unevaluated || @@ -1360,6 +1365,10 @@ ExpressionEvaluationContext::ImmediateFunctionContext && InDiscardedStatement); } + + bool shouldDelayCallsToStdSourceLocationCurrent() const { + return DelaySourceLocationCurrentCalls; + } }; /// A stack of expression evaluation contexts. @@ -5262,10 +5271,16 @@ void DiagnoseSentinelCalls(NamedDecl *D, SourceLocation Loc, ArrayRef Args); + enum SourceLocationCurrentBehavior_t { + EvaluateSourceLocation, + DelaySourceLocation + }; void PushExpressionEvaluationContext( ExpressionEvaluationContext NewContext, Decl *LambdaContextDecl = nullptr, ExpressionEvaluationContextRecord::ExpressionKind Type = - ExpressionEvaluationContextRecord::EK_Other); + ExpressionEvaluationContextRecord::EK_Other, + SourceLocationCurrentBehavior_t SourceLocBehavior = + EvaluateSourceLocation); enum ReuseLambdaContextDecl_t { ReuseLambdaContextDecl }; void PushExpressionEvaluationContext( ExpressionEvaluationContext NewContext, ReuseLambdaContextDecl_t, @@ -9436,6 +9451,12 @@ return ExprEvalContexts.back().isImmediateFunctionContext(); } + bool shouldDelayCallsToStdSourceLocationCurrent() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().shouldDelayCallsToStdSourceLocationCurrent(); + } + /// RAII class used to determine whether SFINAE has /// trapped any errors that occur during template argument /// deduction. @@ -13558,11 +13579,13 @@ Decl *LambdaContextDecl = nullptr, Sema::ExpressionEvaluationContextRecord::ExpressionKind ExprContext = Sema::ExpressionEvaluationContextRecord::EK_Other, + Sema::SourceLocationCurrentBehavior_t SourceLocBehavior = + Sema::EvaluateSourceLocation, bool ShouldEnter = true) : Actions(Actions), Entered(ShouldEnter) { if (Entered) Actions.PushExpressionEvaluationContext(NewContext, LambdaContextDecl, - ExprContext); + ExprContext, SourceLocBehavior); } EnterExpressionEvaluationContext( Sema &Actions, Sema::ExpressionEvaluationContext NewContext, diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -20,6 +20,7 @@ #include "clang/AST/APValue.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" +#include "clang/AST/Expr.h" #include "clang/AST/RecordLayout.h" #include "clang/AST/StmtVisitor.h" #include "clang/Basic/Builtins.h" @@ -1392,14 +1393,27 @@ } llvm::Constant *ConstantEmitter::tryEmitConstantExpr(const ConstantExpr *CE) { - if (!CE->hasAPValueResult()) - return nullptr; + APValue Result; + if (CE->hasAPValueResult()) { + Result = CE->getAPValueResult(); + } else { + if (!CE->isImmediateInvocation()) + return nullptr; + // Handle delayed immediate invocations. + // These are rare, std::source_location::current() is an example. + Expr::EvalResult Eval; + if (!CE->EvaluateAsConstantExpr(Eval, CGM.getContext(), + ConstantExprKind::ImmediateInvocation) || + Eval.HasSideEffects) + return nullptr; + Result = std::move(Eval.Val); + } QualType RetType = CE->getType(); if (CE->isGLValue()) RetType = CGM.getContext().getLValueReferenceType(RetType); - return emitAbstract(CE->getBeginLoc(), CE->getAPValueResult(), RetType); + return emitAbstract(CE->getBeginLoc(), Result, RetType); } llvm::Constant * diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3147,7 +3147,9 @@ "Data member initializer not starting with '=' or '{'"); EnterExpressionEvaluationContext Context( - Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, D); + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, D, + Sema::ExpressionEvaluationContextRecord::EK_Other, + Sema::DelaySourceLocation); if (TryConsumeToken(tok::equal, EqualLoc)) { if (Tok.is(tok::kw_delete)) { // In principle, an initializer of '= delete p;' is legal, but it will diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1532,7 +1532,8 @@ EnterExpressionEvaluationContext PotentiallyDiscarded( Actions, Context, nullptr, - Sema::ExpressionEvaluationContextRecord::EK_Other, ShouldEnter); + Sema::ExpressionEvaluationContextRecord::EK_Other, + Sema::EvaluateSourceLocation, ShouldEnter); ThenStmt = ParseStatement(&InnerStatementTrailingElseLoc); } @@ -1577,7 +1578,8 @@ EnterExpressionEvaluationContext PotentiallyDiscarded( Actions, Context, nullptr, - Sema::ExpressionEvaluationContextRecord::EK_Other, ShouldEnter); + Sema::ExpressionEvaluationContextRecord::EK_Other, + Sema::EvaluateSourceLocation, ShouldEnter); ElseStmt = ParseStatement(); if (ElseStmt.isUsable()) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5848,6 +5848,23 @@ ArraySubscriptExpr(LHSExp, RHSExp, ResultType, VK, OK, RLoc); } +static bool IsStdSourceLocationCurrent(Decl *D) { + auto *F = D ? dyn_cast(D) : nullptr; + auto *FuncName = F ? F->getIdentifier() : nullptr; + if (!FuncName || !FuncName->isStr("current") || !F->isConsteval()) + return false; + auto *Cls = dyn_cast(F->getDeclContext()); + auto *ClsName = Cls ? Cls->getIdentifier() : nullptr; + return ClsName && ClsName->isStr("source_location") && + Cls->isInStdNamespace(); +} + +static bool IsCallToStdSourceLocationCurrent(Expr *E) { + CallExpr *Call = dyn_cast(E); + auto *Callee = Call ? Call->getCalleeDecl() : nullptr; + return Callee && IsStdSourceLocationCurrent(Callee); +} + bool Sema::CheckCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD, ParmVarDecl *Param) { if (Param->hasUnparsedDefaultArg()) { @@ -17395,10 +17412,10 @@ return TransformToPE(*this).TransformType(TInfo); } -void -Sema::PushExpressionEvaluationContext( +void Sema::PushExpressionEvaluationContext( ExpressionEvaluationContext NewContext, Decl *LambdaContextDecl, - ExpressionEvaluationContextRecord::ExpressionKind ExprContext) { + ExpressionEvaluationContextRecord::ExpressionKind ExprContext, + SourceLocationCurrentBehavior_t SourceLocBehavior) { ExprEvalContexts.emplace_back(NewContext, ExprCleanupObjects.size(), Cleanup, LambdaContextDecl, ExprContext); @@ -17411,6 +17428,10 @@ ExprEvalContexts.back().InImmediateFunctionContext = ExprEvalContexts[ExprEvalContexts.size() - 2] .isImmediateFunctionContext(); + if (NewContext == ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed || + SourceLocBehavior == DelaySourceLocation) { + ExprEvalContexts.back().DelaySourceLocationCurrentCalls = true; + } Cleanup.reset(); if (!MaybeODRUseExprs.empty()) @@ -17519,11 +17540,18 @@ } static void EvaluateAndDiagnoseImmediateInvocation( - Sema &SemaRef, Sema::ImmediateInvocationCandidate Candidate) { + Sema &SemaRef, Sema::ImmediateInvocationCandidate Candidate, + bool DelaySourceLocCalls) { llvm::SmallVector Notes; Expr::EvalResult Eval; Eval.Diag = &Notes; ConstantExpr *CE = Candidate.getPointer(); + // [support.srcloc.cons]p2. + // Any call to current that appears as a default argument ([dcl.fct.default]), + // or as a subexpression thereof, should correspond to the location of the + // invocation of the function that uses the default argument ([expr.call]). + if (DelaySourceLocCalls && IsCallToStdSourceLocationCurrent(CE->getSubExpr())) + return; // Postpone evaluation until we see the context. bool Result = CE->EvaluateAsConstantExpr( Eval, SemaRef.getASTContext(), ConstantExprKind::ImmediateInvocation); if (!Result || !Notes.empty()) { @@ -17664,7 +17692,8 @@ } for (auto CE : Rec.ImmediateInvocationCandidates) if (!CE.getInt()) - EvaluateAndDiagnoseImmediateInvocation(SemaRef, CE); + EvaluateAndDiagnoseImmediateInvocation( + SemaRef, CE, Rec.shouldDelayCallsToStdSourceLocationCurrent()); for (auto DR : Rec.ReferenceToConsteval) { auto *FD = cast(DR->getDecl()); SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address) diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -26,6 +26,7 @@ #include "clang/Sema/DeclSpec.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/Sema.h" #include "clang/Sema/SemaConcept.h" #include "clang/Sema/SemaInternal.h" #include "clang/Sema/Template.h" @@ -2464,6 +2465,10 @@ // and non-defining declarations. Sema::ContextRAII SavedContext(*this, OwningFunc); LocalInstantiationScope Local(*this, true); + EnterExpressionEvaluationContext EvalContext( + *this, ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed, + Sema::ReuseLambdaContextDecl); + ExprResult NewArg = SubstExpr(Arg, TemplateArgs); if (NewArg.isUsable()) { // It would be nice if we still had this. @@ -3033,7 +3038,8 @@ // we don't have a scope. ContextRAII SavedContext(*this, Instantiation->getParent()); EnterExpressionEvaluationContext EvalContext( - *this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); + *this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, nullptr, + ExpressionEvaluationContextRecord::EK_Other, Sema::DelaySourceLocation); LocalInstantiationScope Scope(*this, true); diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -4492,7 +4492,7 @@ Expr *UninstExpr = Param->getUninstantiatedDefaultArg(); EnterExpressionEvaluationContext EvalContext( - *this, ExpressionEvaluationContext::PotentiallyEvaluated, Param); + *this, ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed, Param); // Instantiate the expression. // diff --git a/clang/test/CodeGenCXX/builtin-source-location.cpp b/clang/test/CodeGenCXX/builtin-source-location.cpp --- a/clang/test/CodeGenCXX/builtin-source-location.cpp +++ b/clang/test/CodeGenCXX/builtin-source-location.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -no-opaque-pointers -std=c++2a -fblocks %s -triple x86_64-unknown-unknown -emit-llvm -o %t.ll +// RUN: %clang_cc1 -no-opaque-pointers -std=c++20 -fblocks %s -triple x86_64-unknown-unknown -emit-llvm -o %t.ll // This needs to be performed before #line directives which alter filename -// RUN: %clang_cc1 -no-opaque-pointers -fno-file-reproducible -fmacro-prefix-map=%p=/UNLIKELY/PATH -emit-llvm -o - %s | FileCheck %s --check-prefix=CHECK-PREFIX-MAP +// RUN: %clang_cc1 -no-opaque-pointers -std=c++20 -fno-file-reproducible -fmacro-prefix-map=%p=/UNLIKELY/PATH -emit-llvm -o - %s | FileCheck %s --check-prefix=CHECK-PREFIX-MAP // // CHECK-PREFIX-MAP: /UNLIKELY/PATH{{/|\\\\}}builtin-source-location.cpp void testRemap() { @@ -13,13 +13,15 @@ namespace std { class source_location { public: - static constexpr source_location current(const void *__p = __builtin_source_location()) noexcept { + static consteval source_location current(const void *__p = __builtin_source_location()) noexcept { source_location __loc; __loc.__m_impl = static_cast(__p); return __loc; } static source_location bad_current(const void *__p = __builtin_source_location()) noexcept { - return current(__p); + source_location __loc; + __loc.__m_impl = static_cast(__p); + return __loc; } constexpr source_location() = default; constexpr source_location(source_location const &) = default; @@ -79,7 +81,7 @@ // CHECK-LOCAL-ONE-DAG: @[[IMPL:.*]] = private unnamed_addr constant %"struct.std::source_location::__impl" { {{[^@]*}}@[[FILE]], {{[^@]*}}@[[FUNC]], {{.*}} i32 2100, i32 {{[0-9]+}} }, align 8 // // CHECK-LOCAL-ONE: define {{.*}} @test_function -// CHECK-LOCAL-ONE: call %"struct.std::source_location::__impl"* @_ZNSt15source_location7currentEPKv({{.*}} @[[IMPL]] +// CHECK-LOCAL-ONE: store %"struct.std::source_location::__impl"* @.constant.{{.*}}, %"struct.std::source_location::__impl"** %0, align 8 #line 2100 "test_current.cpp" SL local = SL::current(); } diff --git a/clang/test/SemaCXX/source_location.cpp b/clang/test/SemaCXX/source_location.cpp --- a/clang/test/SemaCXX/source_location.cpp +++ b/clang/test/SemaCXX/source_location.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++1z -fcxx-exceptions -fexceptions -verify %s +// RUN: %clang_cc1 -std=c++20 -fcxx-exceptions -fexceptions -verify %s // expected-no-diagnostics #define assert(...) ((__VA_ARGS__) ? ((void)0) : throw 42) @@ -13,7 +13,7 @@ struct __impl; public: - static constexpr source_location current(const __impl *__p = __builtin_source_location()) noexcept { + static consteval source_location current(const __impl *__p = __builtin_source_location()) noexcept { source_location __loc; __loc.__m_impl = __p; return __loc; @@ -364,8 +364,8 @@ template void func_template_tests() { constexpr auto P = test_func_template(42); - //static_assert(is_equal(P.first.function(), __func__), ""); - //static_assert(!is_equal(P.second.function(), __func__), ""); + static_assert(is_equal(P.first.function(), __PRETTY_FUNCTION__), ""); + static_assert(!is_equal(P.second.function(), __PRETTY_FUNCTION__), ""); } template void func_template_tests(); diff --git a/clang/test/SemaCXX/source_location_consteval.cpp b/clang/test/SemaCXX/source_location_consteval.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/source_location_consteval.cpp @@ -0,0 +1,68 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s +// expected-no-diagnostics + +namespace std { +class source_location { + struct __impl; + +public: + static consteval source_location current(const __impl *__p = __builtin_source_location()) noexcept { + source_location __loc; + __loc.__m_impl = __p; + return __loc; + } + constexpr source_location() = default; + constexpr source_location(source_location const &) = default; + constexpr unsigned int line() const noexcept { return __m_impl ? __m_impl->_M_line : 0; } + constexpr unsigned int column() const noexcept { return __m_impl ? __m_impl->_M_column : 0; } + constexpr const char *file() const noexcept { return __m_impl ? __m_impl->_M_file_name : ""; } + constexpr const char *function() const noexcept { return __m_impl ? __m_impl->_M_function_name : ""; } + +private: + // Note: The type name "std::source_location::__impl", and its constituent + // field-names are required by __builtin_source_location(). + struct __impl { + const char *_M_file_name; + const char *_M_function_name; + unsigned _M_line; + unsigned _M_column; + }; + const __impl *__m_impl = nullptr; + +public: + using public_impl_alias = __impl; +}; +} // namespace std + +using SL = std::source_location; + +constexpr bool is_equal(const char *LHS, const char *RHS) { + while (*LHS != 0 && *RHS != 0) { + if (*LHS != *RHS) + return false; + ++LHS; + ++RHS; + } + return *LHS == 0 && *RHS == 0; +} + +constexpr SL get_sl(SL l = SL::current()) { return l; } +SL get_sl_not_const(SL l = SL::current()) { return l; } + +#line 700 "CheckDefaultArg.h" +constexpr SL l = get_sl(); +static_assert(l.line() == 700); +static_assert(is_equal(l.file(), "CheckDefaultArg.h")); + + +consteval SL get_sl_rec_consteval(SL l = get_sl()) { return l; } +constexpr SL get_sl_rec_constexpr(SL l = get_sl()) { return l; } + +static_assert(get_sl_rec_constexpr().line() == __LINE__); +static_assert(get_sl_rec_consteval().line() == __LINE__); + +int test() { + static_assert(is_equal(get_sl().function(), __PRETTY_FUNCTION__)); + static_assert(get_sl().line() == __LINE__); + return get_sl().line() + get_sl_not_const().line(); +}