Skip to content

Commit

Permalink
Implement CWG2631
Browse files Browse the repository at this point in the history
Implement https://cplusplus.github.io/CWG/issues/2631.html.

Immediate calls in default arguments and defaults members
are not evaluated.

Instead, we evaluate them when constructing a
`CXXDefaultArgExpr`/`BuildCXXDefaultInitExpr`.

The immediate calls are executed by doing a
transform on the initializing expression.

Note that lambdas are not considering subexpressions so
we do not need to transform them.

As a result of this patch, unused default member
initializers are not considered odr-used, and
errors about members binding to local variables
in an outer scope only surface at the point
where a constructor is defined.

Reviewed By: aaron.ballman, #clang-language-wg, rupprecht

Differential Revision: https://reviews.llvm.org/D136554
  • Loading branch information
cor3ntin committed Jan 8, 2023
1 parent eda8e99 commit ca61961
Show file tree
Hide file tree
Showing 28 changed files with 1,133 additions and 157 deletions.
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -671,6 +671,11 @@ C++ Language Changes in Clang
- Implemented DR2358 allowing init captures in lambdas in default arguments.
- implemented `DR2654 <https://wg21.link/cwg2654>`_ which undeprecates
all compound assignements operations on volatile qualified variables.
- Implemented DR2631. Invalid ``consteval`` calls in default arguments and default
member initializers are diagnosed when and if the default is used.
This Fixes `Issue 56379 <https://github.com/llvm/llvm-project/issues/56379>`_
and changes the value of ``std::source_location::current()``
used in default parameters calls compared to previous versions of Clang.

C++20 Feature Support
^^^^^^^^^^^^^^^^^^^^^
Expand Down
100 changes: 76 additions & 24 deletions clang/include/clang/AST/ExprCXX.h
Expand Up @@ -1244,8 +1244,12 @@ class CXXThrowExpr : public Expr {
/// This wraps up a function call argument that was created from the
/// corresponding parameter's default argument, when the call did not
/// explicitly supply arguments for all of the parameters.
class CXXDefaultArgExpr final : public Expr {
class CXXDefaultArgExpr final
: public Expr,
private llvm::TrailingObjects<CXXDefaultArgExpr, Expr *> {
friend class ASTStmtReader;
friend class ASTReader;
friend TrailingObjects;

/// The parameter whose default is being used.
ParmVarDecl *Param;
Expand All @@ -1254,7 +1258,7 @@ class CXXDefaultArgExpr final : public Expr {
DeclContext *UsedContext;

CXXDefaultArgExpr(StmtClass SC, SourceLocation Loc, ParmVarDecl *Param,
DeclContext *UsedContext)
Expr *RewrittenExpr, DeclContext *UsedContext)
: Expr(SC,
Param->hasUnparsedDefaultArg()
? Param->getType().getNonReferenceType()
Expand All @@ -1263,28 +1267,54 @@ class CXXDefaultArgExpr final : public Expr {
Param->getDefaultArg()->getObjectKind()),
Param(Param), UsedContext(UsedContext) {
CXXDefaultArgExprBits.Loc = Loc;
CXXDefaultArgExprBits.HasRewrittenInit = RewrittenExpr != nullptr;
if (RewrittenExpr)
*getTrailingObjects<Expr *>() = RewrittenExpr;
setDependence(computeDependence(this));
}

CXXDefaultArgExpr(EmptyShell Empty, bool HasRewrittenInit)
: Expr(CXXDefaultArgExprClass, Empty) {
CXXDefaultArgExprBits.HasRewrittenInit = HasRewrittenInit;
}

public:
CXXDefaultArgExpr(EmptyShell Empty) : Expr(CXXDefaultArgExprClass, Empty) {}
static CXXDefaultArgExpr *CreateEmpty(const ASTContext &C,
bool HasRewrittenInit);

// \p Param is the parameter whose default argument is used by this
// expression.
static CXXDefaultArgExpr *Create(const ASTContext &C, SourceLocation Loc,
ParmVarDecl *Param,
DeclContext *UsedContext) {
return new (C)
CXXDefaultArgExpr(CXXDefaultArgExprClass, Loc, Param, UsedContext);
}

ParmVarDecl *Param, Expr *RewrittenExpr,
DeclContext *UsedContext);
// Retrieve the parameter that the argument was created from.
const ParmVarDecl *getParam() const { return Param; }
ParmVarDecl *getParam() { return Param; }

// Retrieve the actual argument to the function call.
const Expr *getExpr() const { return getParam()->getDefaultArg(); }
Expr *getExpr() { return getParam()->getDefaultArg(); }
bool hasRewrittenInit() const {
return CXXDefaultArgExprBits.HasRewrittenInit;
}

// Retrieve the argument to the function call.
Expr *getExpr();
const Expr *getExpr() const {
return const_cast<CXXDefaultArgExpr *>(this)->getExpr();
}

Expr *getRewrittenExpr() {
return hasRewrittenInit() ? *getTrailingObjects<Expr *>() : nullptr;
}

const Expr *getRewrittenExpr() const {
return const_cast<CXXDefaultArgExpr *>(this)->getRewrittenExpr();
}

// Retrieve the rewritten init expression (for an init expression containing
// immediate calls) with the top level FullExpr and ConstantExpr stripped off.
Expr *getAdjustedRewrittenExpr();
const Expr *getAdjustedRewrittenExpr() const {
return const_cast<CXXDefaultArgExpr *>(this)->getAdjustedRewrittenExpr();
}

const DeclContext *getUsedContext() const { return UsedContext; }
DeclContext *getUsedContext() { return UsedContext; }
Expand Down Expand Up @@ -1321,41 +1351,63 @@ class CXXDefaultArgExpr final : public Expr {
/// is implicitly used in a mem-initializer-list in a constructor
/// (C++11 [class.base.init]p8) or in aggregate initialization
/// (C++1y [dcl.init.aggr]p7).
class CXXDefaultInitExpr : public Expr {
friend class ASTReader;
friend class ASTStmtReader;
class CXXDefaultInitExpr final
: public Expr,
private llvm::TrailingObjects<CXXDefaultInitExpr, Expr *> {

friend class ASTStmtReader;
friend class ASTReader;
friend TrailingObjects;
/// The field whose default is being used.
FieldDecl *Field;

/// The context where the default initializer expression was used.
DeclContext *UsedContext;

CXXDefaultInitExpr(const ASTContext &Ctx, SourceLocation Loc,
FieldDecl *Field, QualType Ty, DeclContext *UsedContext);
FieldDecl *Field, QualType Ty, DeclContext *UsedContext,
Expr *RewrittenInitExpr);

CXXDefaultInitExpr(EmptyShell Empty) : Expr(CXXDefaultInitExprClass, Empty) {}
CXXDefaultInitExpr(EmptyShell Empty, bool HasRewrittenInit)
: Expr(CXXDefaultInitExprClass, Empty) {
CXXDefaultInitExprBits.HasRewrittenInit = HasRewrittenInit;
}

public:
static CXXDefaultInitExpr *CreateEmpty(const ASTContext &C,
bool HasRewrittenInit);
/// \p Field is the non-static data member whose default initializer is used
/// by this expression.
static CXXDefaultInitExpr *Create(const ASTContext &Ctx, SourceLocation Loc,
FieldDecl *Field, DeclContext *UsedContext) {
return new (Ctx) CXXDefaultInitExpr(Ctx, Loc, Field, Field->getType(), UsedContext);
FieldDecl *Field, DeclContext *UsedContext,
Expr *RewrittenInitExpr);

bool hasRewrittenInit() const {
return CXXDefaultInitExprBits.HasRewrittenInit;
}

/// Get the field whose initializer will be used.
FieldDecl *getField() { return Field; }
const FieldDecl *getField() const { return Field; }

/// Get the initialization expression that will be used.
Expr *getExpr();
const Expr *getExpr() const {
assert(Field->getInClassInitializer() && "initializer hasn't been parsed");
return Field->getInClassInitializer();
return const_cast<CXXDefaultInitExpr *>(this)->getExpr();
}
Expr *getExpr() {
assert(Field->getInClassInitializer() && "initializer hasn't been parsed");
return Field->getInClassInitializer();

/// Retrieve the initializing expression with evaluated immediate calls, if
/// any.
const Expr *getRewrittenExpr() const {
assert(hasRewrittenInit() && "expected a rewritten init expression");
return *getTrailingObjects<Expr *>();
}

/// Retrieve the initializing expression with evaluated immediate calls, if
/// any.
Expr *getRewrittenExpr() {
assert(hasRewrittenInit() && "expected a rewritten init expression");
return *getTrailingObjects<Expr *>();
}

const DeclContext *getUsedContext() const { return UsedContext; }
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/AST/Stmt.h
Expand Up @@ -690,6 +690,9 @@ class alignas(void *) Stmt {

unsigned : NumExprBits;

/// Whether this CXXDefaultArgExpr rewrote its argument and stores a copy.
unsigned HasRewrittenInit : 1;

/// The location where the default argument expression was used.
SourceLocation Loc;
};
Expand All @@ -700,6 +703,10 @@ class alignas(void *) Stmt {

unsigned : NumExprBits;

/// Whether this CXXDefaultInitExprBitfields rewrote its argument and stores
/// a copy.
unsigned HasRewrittenInit : 1;

/// The location where the default initializer expression was used.
SourceLocation Loc;
};
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -2646,6 +2646,10 @@ def err_invalid_consteval_take_address : Error<
" of an immediate invocation">;
def err_invalid_consteval_call : Error<
"call to consteval function %q0 is not a constant expression">;
def note_invalid_consteval_initializer : Note<
"in the default initalizer of %0">;
def note_invalid_consteval_initializer_here : Note<
"initialized here %0">;
def err_invalid_consteval_decl_kind : Error<
"%0 cannot be declared consteval">;
def err_invalid_constexpr : Error<
Expand Down
72 changes: 68 additions & 4 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -1330,6 +1330,25 @@ class Sema final {
bool InDiscardedStatement;
bool InImmediateFunctionContext;

bool IsCurrentlyCheckingDefaultArgumentOrInitializer = false;

// When evaluating immediate functions in the initializer of a default
// argument or default member initializer, this is the declaration whose
// default initializer is being evaluated and the location of the call
// or constructor definition.
struct InitializationContext {
InitializationContext(SourceLocation Loc, ValueDecl *Decl,
DeclContext *Context)
: Loc(Loc), Decl(Decl), Context(Context) {
assert(Decl && Context && "invalid initialization context");
}

SourceLocation Loc;
ValueDecl *Decl = nullptr;
DeclContext *Context = nullptr;
};
llvm::Optional<InitializationContext> DelayedDefaultInitializationContext;

ExpressionEvaluationContextRecord(ExpressionEvaluationContext Context,
unsigned NumCleanupObjects,
CleanupInfo ParentCleanup,
Expand Down Expand Up @@ -6211,19 +6230,22 @@ class Sema final {
bool IsStdInitListInitialization, bool RequiresZeroInit,
unsigned ConstructKind, SourceRange ParenRange);

ExprResult ConvertMemberDefaultInitExpression(FieldDecl *FD, Expr *InitExpr,
SourceLocation InitLoc);

ExprResult BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field);


/// Instantiate or parse a C++ default argument expression as necessary.
/// Return true on error.
bool CheckCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD,
ParmVarDecl *Param);
ParmVarDecl *Param, Expr *Init = nullptr,
bool SkipImmediateInvocations = true);

/// BuildCXXDefaultArgExpr - Creates a CXXDefaultArgExpr, instantiating
/// the default expr if needed.
ExprResult BuildCXXDefaultArgExpr(SourceLocation CallLoc,
FunctionDecl *FD,
ParmVarDecl *Param);
ExprResult BuildCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD,
ParmVarDecl *Param, Expr *Init = nullptr);

/// FinalizeVarWithDestructor - Prepare for calling destructor on the
/// constructed variable.
Expand Down Expand Up @@ -9636,6 +9658,48 @@ class Sema final {
return ExprEvalContexts.back().isImmediateFunctionContext();
}

bool isCheckingDefaultArgumentOrInitializer() const {
assert(!ExprEvalContexts.empty() &&
"Must be in an expression evaluation context");
const ExpressionEvaluationContextRecord &Ctx = ExprEvalContexts.back();
return (Ctx.Context ==
ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed) ||
Ctx.IsCurrentlyCheckingDefaultArgumentOrInitializer;
}

llvm::Optional<ExpressionEvaluationContextRecord::InitializationContext>
InnermostDeclarationWithDelayedImmediateInvocations() const {
assert(!ExprEvalContexts.empty() &&
"Must be in an expression evaluation context");
for (const auto &Ctx : llvm::reverse(ExprEvalContexts)) {
if (Ctx.Context == ExpressionEvaluationContext::PotentiallyEvaluated &&
Ctx.DelayedDefaultInitializationContext)
return Ctx.DelayedDefaultInitializationContext;
if (Ctx.isConstantEvaluated() || Ctx.isImmediateFunctionContext() ||
Ctx.isUnevaluated())
break;
}
return std::nullopt;
}

llvm::Optional<ExpressionEvaluationContextRecord::InitializationContext>
OutermostDeclarationWithDelayedImmediateInvocations() const {
assert(!ExprEvalContexts.empty() &&
"Must be in an expression evaluation context");
llvm::Optional<ExpressionEvaluationContextRecord::InitializationContext>
Res;
for (auto &Ctx : llvm::reverse(ExprEvalContexts)) {
if (Ctx.Context == ExpressionEvaluationContext::PotentiallyEvaluated &&
!Ctx.DelayedDefaultInitializationContext && Res)
break;
if (Ctx.isConstantEvaluated() || Ctx.isImmediateFunctionContext() ||
Ctx.isUnevaluated())
break;
Res = Ctx.DelayedDefaultInitializationContext;
}
return Res;
}

/// RAII class used to determine whether SFINAE has
/// trapped any errors that occur during template argument
/// deduction.
Expand Down
21 changes: 18 additions & 3 deletions clang/lib/AST/ASTImporter.cpp
Expand Up @@ -7687,9 +7687,16 @@ ExpectedStmt ASTNodeImporter::VisitCXXDefaultArgExpr(CXXDefaultArgExpr *E) {
if (Error Err = ImportDefaultArgOfParmVarDecl(*FromParam, ToParam))
return std::move(Err);
}

Expr *RewrittenInit = nullptr;
if (E->hasRewrittenInit()) {
ExpectedExpr ExprOrErr = import(E->getRewrittenExpr());
if (!ExprOrErr)
return ExprOrErr.takeError();
RewrittenInit = ExprOrErr.get();
}
return CXXDefaultArgExpr::Create(Importer.getToContext(), *ToUsedLocOrErr,
*ToParamOrErr, *UsedContextOrErr);
*ToParamOrErr, RewrittenInit,
*UsedContextOrErr);
}

ExpectedStmt
Expand Down Expand Up @@ -8381,8 +8388,16 @@ ExpectedStmt ASTNodeImporter::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) {
ToField->setInClassInitializer(*ToInClassInitializerOrErr);
}

Expr *RewrittenInit = nullptr;
if (E->hasRewrittenInit()) {
ExpectedExpr ExprOrErr = import(E->getRewrittenExpr());
if (!ExprOrErr)
return ExprOrErr.takeError();
RewrittenInit = ExprOrErr.get();
}

return CXXDefaultInitExpr::Create(Importer.getToContext(), *ToBeginLocOrErr,
ToField, *UsedContextOrErr);
ToField, *UsedContextOrErr, RewrittenInit);
}

ExpectedStmt ASTNodeImporter::VisitCXXNamedCastExpr(CXXNamedCastExpr *E) {
Expand Down
3 changes: 1 addition & 2 deletions clang/lib/AST/Decl.cpp
Expand Up @@ -2897,8 +2897,7 @@ Expr *ParmVarDecl::getDefaultArg() {

Expr *Arg = getInit();
if (auto *E = dyn_cast_or_null<FullExpr>(Arg))
if (!isa<ConstantExpr>(E))
return E->getSubExpr();
return E->getSubExpr();

return Arg;
}
Expand Down

0 comments on commit ca61961

Please sign in to comment.