Skip to content

Commit

Permalink
Get constant initialization working.
Browse files Browse the repository at this point in the history
  • Loading branch information
EricWF committed Aug 1, 2024
1 parent dd42a17 commit 7b5d440
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 14 deletions.
12 changes: 9 additions & 3 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1369,7 +1369,8 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {

private:
APValue *evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
bool IsConstantInitialization) const;
bool IsConstantInitialization,
bool EnableContracts) const;

public:
/// Return the already-evaluated value of this variable's
Expand Down Expand Up @@ -1401,8 +1402,13 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
/// Evaluate the initializer of this variable to determine whether it's a
/// constant initializer. Should only be called once, after completing the
/// definition of the variable.
bool checkForConstantInitialization(
SmallVectorImpl<PartialDiagnosticAt> &Notes) const;
bool
checkForConstantInitialization(SmallVectorImpl<PartialDiagnosticAt> &Notes,
bool EnableContracts = true) const;

bool
recheckForConstantInitialization(SmallVectorImpl<PartialDiagnosticAt> &Notes,
bool EnableContracts = true) const;

void setInitStyle(InitializationStyle Style) {
VarDeclBits.InitStyle = Style;
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,8 @@ class Expr : public ValueStmt {
bool EvaluateAsInitializer(APValue &Result, const ASTContext &Ctx,
const VarDecl *VD,
SmallVectorImpl<PartialDiagnosticAt> &Notes,
bool IsConstantInitializer) const;
bool IsConstantInitializer,
bool EvaluateContracts = true) const;

/// EvaluateWithSubstitution - Evaluate an expression as if from the context
/// of a call to the given function with the given arguments, inside an
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -11959,6 +11959,10 @@ def warn_constexpr_contract_failure : Warning<
ShowInSystemHeader, ShowInSystemMacro,
InGroup<ContractViolation>;

def err_initialization_of_constant_initialized_variable_failed : Error<
"initialization of constant-initialized variable failed">;
def note_initialization_changed_contract_semantic : Note<
"initializer is constant when evaluated with 'ignore' contract evaluation semantic">;
} // end of contracts issues
let CategoryName = "Documentation Issue" in {
def warn_not_a_doxygen_trailing_member_comment : Warning<
Expand Down
39 changes: 32 additions & 7 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2570,11 +2570,12 @@ EvaluatedStmt *VarDecl::getEvaluatedStmt() const {

APValue *VarDecl::evaluateValue() const {
SmallVector<PartialDiagnosticAt, 8> Notes;
return evaluateValueImpl(Notes, hasConstantInitialization());
return evaluateValueImpl(Notes, hasConstantInitialization(), true);
}

APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
bool IsConstantInitialization) const {
bool IsConstantInitialization,
bool EvaluateContracts) const {
EvaluatedStmt *Eval = ensureEvaluatedStmt();

const auto *Init = getInit();
Expand All @@ -2594,8 +2595,9 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
Eval->IsEvaluating = true;

ASTContext &Ctx = getASTContext();
bool Result = Init->EvaluateAsInitializer(Eval->Evaluated, Ctx, this, Notes,
IsConstantInitialization);
bool Result =
Init->EvaluateAsInitializer(Eval->Evaluated, Ctx, this, Notes,
IsConstantInitialization, EvaluateContracts);

// In C++, or in C23 if we're initialising a 'constexpr' variable, this isn't
// a constant initializer if we produced notes. In that case, we can't keep
Expand Down Expand Up @@ -2655,12 +2657,12 @@ bool VarDecl::hasConstantInitialization() const {
}

bool VarDecl::checkForConstantInitialization(
SmallVectorImpl<PartialDiagnosticAt> &Notes) const {
SmallVectorImpl<PartialDiagnosticAt> &Notes, bool EvaluateContracts) const {
EvaluatedStmt *Eval = ensureEvaluatedStmt();
// If we ask for the value before we know whether we have a constant
// initializer, we can compute the wrong value (for example, due to
// std::is_constant_evaluated()).
assert(!Eval->WasEvaluated &&
assert((!Eval->WasEvaluated) &&
"already evaluated var value before checking for constant init");
assert((getASTContext().getLangOpts().CPlusPlus ||
getASTContext().getLangOpts().C23) &&
Expand All @@ -2670,7 +2672,8 @@ bool VarDecl::checkForConstantInitialization(

// Evaluate the initializer to check whether it's a constant expression.
Eval->HasConstantInitialization =
evaluateValueImpl(Notes, true) && Notes.empty();
evaluateValueImpl(Notes, true, /*EnableContracts=*/EvaluateContracts) &&
Notes.empty();

// If evaluation as a constant initializer failed, allow re-evaluation as a
// non-constant initializer if we later find we want the value.
Expand All @@ -2680,6 +2683,28 @@ bool VarDecl::checkForConstantInitialization(
return Eval->HasConstantInitialization;
}

bool VarDecl::recheckForConstantInitialization(
SmallVectorImpl<PartialDiagnosticAt> &Notes, bool EnableContracts) const {
EvaluatedStmt *Eval = ensureEvaluatedStmt();

assert((Eval->WasEvaluated && Eval->HasConstantInitialization) &&
"Trial initialization should have been performed first");

// If we ask for the value before we know whether we have a constant
// initializer, we can compute the wrong value (for example, due to
// std::is_constant_evaluated()).

assert((getASTContext().getLangOpts().CPlusPlus ||
getASTContext().getLangOpts().C23) &&
"only meaningful in C++/C23");

assert(!getInit()->isValueDependent());
Eval->WasEvaluated = false;

// Evaluate the initializer to check whether it's a constant expression.
return evaluateValueImpl(Notes, true, EnableContracts) && Notes.empty();
}

bool VarDecl::isParameterPack() const {
return isa<PackExpansionType>(getType());
}
Expand Down
8 changes: 7 additions & 1 deletion clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5229,6 +5229,10 @@ static bool CheckLocalVariableDeclaration(EvalInfo &Info, const VarDecl *VD) {
static bool EvaluateContract(const ContractStmt *S, EvalInfo &Info) {
using CES = ContractEvaluationSemantic;
auto &Ctx = Info.Ctx;

if (!Info.EvaluateContracts)
return true;

CES Sem = S->getSemantic(Ctx.getLangOpts());
if (Sem == CES::Ignore)
return true;
Expand Down Expand Up @@ -16366,7 +16370,8 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx,
bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
const VarDecl *VD,
SmallVectorImpl<PartialDiagnosticAt> &Notes,
bool IsConstantInitialization) const {
bool IsConstantInitialization,
bool EvaluateContracts) const {
assert(!isValueDependent() &&
"Expression evaluator can't be called on a dependent expression.");

Expand All @@ -16387,6 +16392,7 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
: EvalInfo::EM_ConstantFold);
Info.setEvaluatingDecl(VD, Value);
Info.InConstantContext = IsConstantInitialization;
Info.EvaluateContracts = EvaluateContracts;

SourceLocation DeclLoc = VD->getLocation();
QualType DeclTy = VD->getType();
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/Interp/State.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ class State {
/// constant value.
bool InConstantContext = false;

// Whether we're allowed to evaluate contracts
bool EvaluateContracts = true;

private:
void addCallStack(unsigned Limit);

Expand Down
43 changes: 41 additions & 2 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14347,6 +14347,27 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
// do this lazily, because the result might depend on things that change
// later, such as which constexpr functions happen to be defined.
SmallVector<PartialDiagnosticAt, 8> Notes;

// Evaluate the initializer to see if it's a constant initializer.
//
// For variables that can fallback to dynamic initialization
// C++ contracts require us to do this in two phases:
//
// (1) evaluate the initalizer with all contracts disabled. If it succeeds
// the variable is 'constant initialized'. Otherwise, perform dynamic
// initialization.
//
// (2) On success, reevaluate the initializer with contracts having
// their user-specified
// semantics. If it fails, we need to diagnose the failure instead
// of falling back to dynamic initializer.
//
// FIXME(EricWF): Technically we're required to do this check for constexpr
// variables too, since the initializer may be non-constant only when
// contracts are enabled.
bool ConstantInitializerIsRequired =
var->isConstexpr() || (GlobalStorage && var->hasAttr<ConstInitAttr>());

if (!getLangOpts().CPlusPlus11 && !getLangOpts().C23) {
// Prior to C++11, in contexts where a constant initializer is required,
// the set of valid constant initializers is described by syntactic rules
Expand All @@ -14366,8 +14387,26 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
Notes.back().second << CacheCulprit->getSourceRange();
}
} else {
// Evaluate the initializer to see if it's a constant initializer.
HasConstInit = var->checkForConstantInitialization(Notes);
HasConstInit = var->checkForConstantInitialization(
Notes,
/*EnableContracts=*/ConstantInitializerIsRequired);
}

if (HasConstInit && getLangOpts().Contracts &&
!ConstantInitializerIsRequired) {
if (!var->recheckForConstantInitialization(Notes,
/*EnableContracts=*/true)) {
// If we have a contract failure, we need to diagnose it.
// We need to clear the notes, as we will re-diagnose the contract
// failure.
SourceLocation DiagLoc = var->getLocation();
Diag(DiagLoc,
diag::err_initialization_of_constant_initialized_variable_failed)
<< var;
Diag(DiagLoc, diag::note_initialization_changed_contract_semantic);
for (unsigned I = 0, N = Notes.size(); I != N; ++I)
Diag(Notes[I].first, Notes[I].second);
}
}

if (HasConstInit) {
Expand Down

0 comments on commit 7b5d440

Please sign in to comment.