[clang] [C++26][clang] Implement P2795R5 'Erroneous behaviour for uninitialized reads' (PR #177614)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 14 10:28:57 PDT 2026
https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/177614
>From acb043dc208cf70c3b0ebf4c35cbf0ef77a2f72b Mon Sep 17 00:00:00 2001
From: "Wang, Yihan" <yronglin777 at gmail.com>
Date: Sat, 24 Jan 2026 00:53:21 +0800
Subject: [PATCH 1/3] [C++26][clang] Implement P2795R5 'Erroneous behaviour for
uninitialized reads'
Signed-off-by: Wang, Yihan <yronglin777 at gmail.com>
---
clang/docs/ReleaseNotes.rst | 1 +
clang/include/clang/AST/APValue.h | 18 +++-
clang/include/clang/AST/PropertiesBase.td | 3 +
clang/include/clang/Basic/Attr.td | 22 +++++
clang/include/clang/Basic/AttrDocs.td | 34 ++++++++
clang/include/clang/Basic/DiagnosticGroups.td | 1 +
.../clang/Basic/DiagnosticSemaKinds.td | 13 +++
clang/lib/AST/APValue.cpp | 3 +
clang/lib/AST/ASTImporter.cpp | 1 +
clang/lib/AST/Expr.cpp | 1 +
clang/lib/AST/ExprConstant.cpp | 11 ++-
clang/lib/AST/ItaniumMangle.cpp | 2 +
clang/lib/AST/MicrosoftMangle.cpp | 1 +
clang/lib/AST/TextNodeDumper.cpp | 4 +
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 4 +-
clang/lib/CodeGen/CGDecl.cpp | 5 ++
clang/lib/CodeGen/CGExprConstant.cpp | 3 +
clang/lib/Sema/AnalysisBasedWarnings.cpp | 30 ++++++-
clang/lib/Sema/SemaDecl.cpp | 23 ++++++
clang/lib/Sema/SemaTemplate.cpp | 1 +
.../cxx26-indeterminate-attribute.cpp | 82 +++++++++++++++++++
.../cxx26-erroneous-behavior-warning.cpp | 29 +++++++
.../SemaCXX/cxx26-erroneous-constexpr.cpp | 55 +++++++++++++
.../SemaCXX/cxx26-indeterminate-attribute.cpp | 66 +++++++++++++++
clang/utils/TableGen/ClangAttrEmitter.cpp | 2 +-
clang/www/cxx_status.html | 2 +-
26 files changed, 409 insertions(+), 8 deletions(-)
create mode 100644 clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
create mode 100644 clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp
create mode 100644 clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp
create mode 100644 clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a734804865c57..92ece34adefff 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -81,6 +81,7 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
+- Clang now supports `P2795R5 <https://wg21.link/p2795r5>`_ Erroneous behaviour for uninitialized reads.
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h
index 8a2d6d434792a..620cbf2fe09e4 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -129,6 +129,15 @@ class APValue {
None,
/// This object has an indeterminate value (C++ [basic.indet]).
Indeterminate,
+
+ /// [defns.erroneous]:
+ /// Erroneous behavior is always the consequence of incorrectprogram code.
+ /// Implementations are allowed, but not required, to diagnose it
+ /// ([intro.compliance.general]). Evaluation of a constant expression
+ /// ([expr.const]) never exhibits behavior specified as erroneous in [intro]
+ /// through [cpp].
+ /// Reading it has erroneous behavior but the value is well-defined.
+ Erroneous,
Int,
Float,
FixedPoint,
@@ -435,11 +444,17 @@ class APValue {
return Result;
}
+ static APValue ErroneousValue() {
+ APValue Result;
+ Result.Kind = Erroneous;
+ return Result;
+ }
+
APValue &operator=(const APValue &RHS);
APValue &operator=(APValue &&RHS);
~APValue() {
- if (Kind != None && Kind != Indeterminate)
+ if (Kind != None && Kind != Indeterminate && Kind != Erroneous)
DestroyDataAndMakeUninit();
}
@@ -462,6 +477,7 @@ class APValue {
bool isAbsent() const { return Kind == None; }
bool isIndeterminate() const { return Kind == Indeterminate; }
+ bool isErroneous() const { return Kind == Erroneous; }
bool hasValue() const { return Kind != None && Kind != Indeterminate; }
bool isInt() const { return Kind == Int; }
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index 5b10127526e4e..3ea1be6bbbd18 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -266,6 +266,9 @@ let Class = PropertyTypeCase<APValue, "None"> in {
let Class = PropertyTypeCase<APValue, "Indeterminate"> in {
def : Creator<[{ return APValue::IndeterminateValue(); }]>;
}
+let Class = PropertyTypeCase<APValue, "Erroneous"> in {
+ def : Creator<[{ return APValue::ErroneousValue(); }]>;
+}
let Class = PropertyTypeCase<APValue, "Int"> in {
def : Property<"value", APSInt> {
let Read = [{ node.getInt() }];
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index ba44266d22c8c..aaa5e04effebe 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -100,6 +100,9 @@ class SubsetSubject<AttrSubject base, code check, string diag> : AttrSubject {
def LocalVar : SubsetSubject<Var,
[{S->hasLocalStorage() && !isa<ParmVarDecl>(S)}],
"local variables">;
+def AutomaticStorageVar : SubsetSubject<Var,
+ [{S->hasLocalStorage()}],
+ "local variables or function parameters">;
def NonParmVar : SubsetSubject<Var,
[{S->getKind() != Decl::ParmVar}],
"variables">;
@@ -350,6 +353,10 @@ class CXX11<string namespace, string name, int version = 1>
: Spelling<name, "CXX11", version> {
string Namespace = namespace;
}
+class CXX26<string namespace, string name, int version = 1>
+ : Spelling<name, "CXX26", version> {
+ string Namespace = namespace;
+}
class C23<string namespace, string name, int version = 1>
: Spelling<name, "C23", version> {
string Namespace = namespace;
@@ -432,6 +439,7 @@ def SYCLHost : LangOpt<"SYCLIsHost">;
def SYCLDevice : LangOpt<"SYCLIsDevice">;
def COnly : LangOpt<"", "!LangOpts.CPlusPlus">;
def CPlusPlus : LangOpt<"CPlusPlus">;
+def CPlusPlus26 : LangOpt<"CPlusPlus26">;
def OpenCL : LangOpt<"OpenCL">;
def ObjC : LangOpt<"ObjC">;
def BlocksSupported : LangOpt<"Blocks">;
@@ -4827,6 +4835,20 @@ def Uninitialized : InheritableAttr {
let Documentation = [UninitializedDocs];
}
+// [dcl.attr.indet]/p1:
+// The attribute-token indeterminate may be applied to the definition of a block variable
+// with automatic storage duration or to a parameter-declaration of a function declaration.
+// No attribute-argument-clause shall be present. The attribute specifies that the storage
+// of an object with automatic storage duration is initially indeterminate rather than
+// erroneous ([basic.indet]).
+def Indeterminate : InheritableAttr {
+ let Spellings = [CXX11<"", "indeterminate", 202403>];
+ let Subjects = SubjectList<[AutomaticStorageVar]>;
+ let LangOpts = [CPlusPlus26];
+ let Documentation = [IndeterminateDocs];
+ let SimpleHandler = 1;
+}
+
def LoaderUninitialized : Attr {
let Spellings = [Clang<"loader_uninitialized">];
let Subjects = SubjectList<[GlobalVar]>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 812b48058d189..98a74f551a89a 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7205,6 +7205,40 @@ it rather documents the programmer's intent.
}];
}
+def IndeterminateDocs : Documentation {
+ let Category = DocCatVariable;
+ let Content = [{
+The ``[[indeterminate]]`` attribute is a C++26 standard attribute that can be
+applied to the definition of a variable with automatic storage duration or to
+a function parameter declaration.
+
+In C++26, reading from an uninitialized variable has *erroneous behavior*
+rather than undefined behavior. Variables are implicitly initialized with
+implementation-defined values. The ``[[indeterminate]]`` attribute opts out
+of this erroneous initialization, restoring the previous behavior where the
+variable has an *indeterminate value* and reading it causes undefined behavior.
+
+This attribute is intended for performance-critical code where the overhead
+of automatic initialization is unacceptable. Use with caution, as reading
+from such variables before proper initialization causes undefined behavior.
+
+.. code-block:: c++
+
+ void example() {
+ int x [[indeterminate]]; // x has indeterminate value (not erroneous)
+ int y; // y has erroneous value (reading is erroneous behavior)
+
+ // Reading x before initialization is undefined behavior
+ // Reading y before initialization is erroneous behavior
+ }
+
+ void f([[indeterminate]] int param); // param has indeterminate value
+
+If a function parameter is declared with the ``[[indeterminate]]`` attribute,
+it must be so declared in the first declaration of its function.
+ }];
+}
+
def LoaderUninitializedDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 34624dd3eed3a..027ddb35a6464 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -982,6 +982,7 @@ def Uninitialized : DiagGroup<"uninitialized", [UninitializedSometimes,
UninitializedStaticSelfInit,
UninitializedConstReference,
UninitializedConstPointer]>;
+def ErroneousBehavior : DiagGroup<"erroneous-behavior">;
def IgnoredPragmaIntrinsic : DiagGroup<"ignored-pragma-intrinsic">;
// #pragma optimize is often used to avoid to work around MSVC codegen bugs or
// to disable inlining. It's not completely clear what alternative to suggest
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a2be7ab3791b9..9de87275d6c0b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2567,6 +2567,19 @@ def warn_uninit_const_pointer : Warning<
"variable %0 is uninitialized when passed as a const pointer argument here">,
InGroup<UninitializedConstPointer>, DefaultIgnore;
+def warn_erroneous_behavior_uninitialized_read : Warning<
+ "reading from variable %0 with erroneous value is erroneous behavior">,
+ InGroup<ErroneousBehavior>, DefaultIgnore;
+def note_variable_erroneous_init : Note<
+ "variable %0 was default-initialized here; "
+ "consider using '[[indeterminate]]' attribute to opt out">;
+def err_indeterminate_attr_not_on_first_decl : Error<
+ "'[[indeterminate]]' attribute on parameter %0 must appear on the "
+ "first declaration of the function">;
+def err_indeterminate_attr_mismatch : Error<
+ "'[[indeterminate]]' attribute on parameter %0 does not match "
+ "previous declaration">;
+
def warn_unsequenced_mod_mod : Warning<
"multiple unsequenced modifications to %0">, InGroup<Unsequenced>;
def warn_unsequenced_mod_use : Warning<
diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index 2e1c8eb3726cf..1aed1ea610b85 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -724,6 +724,8 @@ void APValue::printPretty(raw_ostream &Out, const PrintingPolicy &Policy,
case APValue::Indeterminate:
Out << "<uninitialized>";
return;
+ case APValue::Erroneous:
+ Out << "<erroneous>";
case APValue::Int:
if (Ty->isBooleanType())
Out << (getInt().getBoolValue() ? "true" : "false");
@@ -1133,6 +1135,7 @@ LinkageInfo LinkageComputer::getLVForValue(const APValue &V,
switch (V.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
case APValue::Int:
case APValue::Float:
case APValue::FixedPoint:
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 101ab2c40973b..f190950fc707b 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -10601,6 +10601,7 @@ ASTNodeImporter::ImportAPValue(const APValue &FromValue) {
switch (FromValue.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
case APValue::Int:
case APValue::Float:
case APValue::FixedPoint:
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 4bb979e51b75d..143024ea7b11e 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -302,6 +302,7 @@ ConstantResultStorageKind ConstantExpr::getStorageKind(const APValue &Value) {
switch (Value.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
return ConstantResultStorageKind::None;
case APValue::Int:
if (!Value.getInt().needsCleanup())
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 857688ed8039d..9ebd45eb77bcb 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2694,6 +2694,7 @@ static bool HandleConversionToBool(const APValue &Val, bool &Result) {
switch (Val.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
return false;
case APValue::Int:
Result = Val.getInt().getBoolValue();
@@ -4251,9 +4252,11 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
// Walk the designator's path to find the subobject.
for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) {
- // Reading an indeterminate value is undefined, but assigning over one is OK.
+ // Reading an indeterminate value is undefined, but assigning over one is
+ // OK. Reading an erroneous value is erroneous behavior also not allowed in
+ // constant expressions.
if ((O->isAbsent() && !(handler.AccessKind == AK_Construct && I == N)) ||
- (O->isIndeterminate() &&
+ ((O->isIndeterminate() || O->isErroneous()) &&
!isValidIndeterminateAccess(handler.AccessKind))) {
// Object has ended lifetime.
// If I is non-zero, some subobject (member or array element) of a
@@ -4263,7 +4266,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
return false;
if (!Info.checkingPotentialConstantExpression())
Info.FFDiag(E, diag::note_constexpr_access_uninit)
- << handler.AccessKind << O->isIndeterminate()
+ << handler.AccessKind << (O->isIndeterminate() || O->isErroneous())
<< E->getSourceRange();
return handler.failed();
}
@@ -5062,6 +5065,7 @@ struct CompoundAssignSubobjectHandler {
case APValue::Vector:
return foundVector(Subobj, SubobjType);
case APValue::Indeterminate:
+ case APValue::Erroneous:
Info.FFDiag(E, diag::note_constexpr_access_uninit)
<< /*read of=*/0 << /*uninitialized object=*/1
<< E->getLHS()->getSourceRange();
@@ -7828,6 +7832,7 @@ class APValueToBufferConverter {
// Dig through Src to find the byte at SrcOffset.
switch (Val.getKind()) {
case APValue::Indeterminate:
+ case APValue::Erroneous:
case APValue::None:
return true;
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index fa28c0d444cc4..65c770a6589ce 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6411,6 +6411,7 @@ static bool isZeroInitialized(QualType T, const APValue &V) {
switch (V.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
case APValue::AddrLabelDiff:
return false;
@@ -6564,6 +6565,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
switch (V.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
Out << 'L';
mangleType(T);
Out << 'E';
diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index 551aa7bf3321c..73f0c52163d19 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -1940,6 +1940,7 @@ void MicrosoftCXXNameMangler::mangleTemplateArgValue(QualType T,
switch (V.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
// FIXME: MSVC doesn't allow this, so we can't be sure how it should be
// mangled.
if (WithScalarType)
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 7bc0404db1bee..898582aa4287e 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -608,6 +608,7 @@ static bool isSimpleAPValue(const APValue &Value) {
switch (Value.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
case APValue::Int:
case APValue::Float:
case APValue::FixedPoint:
@@ -682,6 +683,9 @@ void TextNodeDumper::Visit(const APValue &Value, QualType Ty) {
case APValue::Indeterminate:
OS << "Indeterminate";
return;
+ case APValue::Erroneous:
+ OS << "Erroneous";
+ return;
case APValue::Int:
OS << "Int ";
{
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index ecb65d901de54..795dd7caafba8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -1783,7 +1783,9 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value,
switch (value.getKind()) {
case APValue::None:
case APValue::Indeterminate:
- cgm.errorNYI("ConstExprEmitter::tryEmitPrivate none or indeterminate");
+ case APValue::Erroneous:
+ cgm.errorNYI(
+ "ConstExprEmitter::tryEmitPrivate none, indeterminate or erroneous");
return {};
case APValue::Int: {
mlir::Type ty = cgm.convertType(destType);
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 8b1cd83af2396..0378b249bbf1d 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1980,9 +1980,14 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
auto hasNoTrivialAutoVarInitAttr = [&](const Decl *D) {
return D && D->hasAttr<NoTrivialAutoVarInitAttr>();
};
+
+ // C++26 [[indeterminate]] attribute opts out of an erroneous
+ // initialization, restoring indeterminate (undefined) behavior.
+
// Note: constexpr already initializes everything correctly.
LangOptions::TrivialAutoVarInitKind trivialAutoVarInit =
((D.isConstexpr() || D.getAttr<UninitializedAttr>() ||
+ D.hasAttr<IndeterminateAttr>() ||
hasNoTrivialAutoVarInitAttr(type->getAsTagDecl()) ||
hasNoTrivialAutoVarInitAttr(CurFuncDecl))
? LangOptions::TrivialAutoVarInitKind::Uninitialized
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index 0eec4dba4824a..e19982522f8cb 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -2441,7 +2441,10 @@ ConstantEmitter::tryEmitPrivate(const APValue &Value, QualType DestType,
switch (Value.getKind()) {
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
// Out-of-lifetime and indeterminate values can be modeled as 'undef'.
+ // For C++ erroneous values, runtime code generation uses a defined pattern,
+ // but for constant expression failures we use undef.
return llvm::UndefValue::get(CGM.getTypes().ConvertType(DestType));
case APValue::LValue:
return ConstantLValueEmitter(*this, Value, DestType,
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 03d84fc935b8e..ab14bf6a458f8 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -976,8 +976,34 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
bool IsCapturedByBlock) {
bool Diagnosed = false;
+ // [basic.indet]/p1.1:
+ // - If the object has dynamic storage duration, or is the object associated
+ // with a variable or function parameter whose first declaration is marked
+ // with the [[indeterminate]] attribute ([dcl.attr.indet]), the bytes have
+ // indeterminate values;
+ //
+ // - otherwise, the bytes have erroneous values, where each value is
+ // determined
+ // by the implementation independently of the state of the program.
+ //
+ // If variable has automatic storage duration and does
+ // not have [[indeterminate]], reading it is erroneous behavior (not
+ // undefined). However, we still warn about it.
+ bool IsErroneousBehavior = S.getLangOpts().CPlusPlus26 &&
+ VD->hasLocalStorage() &&
+ !VD->hasAttr<IndeterminateAttr>();
switch (Use.getKind()) {
case UninitUse::Always:
+ if (IsErroneousBehavior &&
+ !S.Diags.isIgnored(diag::warn_erroneous_behavior_uninitialized_read,
+ Use.getUser()->getBeginLoc())) {
+ S.Diag(Use.getUser()->getBeginLoc(),
+ diag::warn_erroneous_behavior_uninitialized_read)
+ << VD->getDeclName() << Use.getUser()->getSourceRange();
+ S.Diag(VD->getLocation(), diag::note_variable_erroneous_init)
+ << VD->getDeclName();
+ return;
+ }
S.Diag(Use.getUser()->getBeginLoc(), diag::warn_uninit_var)
<< VD->getDeclName() << IsCapturedByBlock
<< Use.getUser()->getSourceRange();
@@ -3164,7 +3190,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
!Diags.isIgnored(diag::warn_sometimes_uninit_var, D->getBeginLoc()) ||
!Diags.isIgnored(diag::warn_maybe_uninit_var, D->getBeginLoc()) ||
!Diags.isIgnored(diag::warn_uninit_const_reference, D->getBeginLoc()) ||
- !Diags.isIgnored(diag::warn_uninit_const_pointer, D->getBeginLoc())) {
+ !Diags.isIgnored(diag::warn_uninit_const_pointer, D->getBeginLoc()) ||
+ !Diags.isIgnored(diag::warn_erroneous_behavior_uninitialized_read,
+ D->getBeginLoc())) {
if (CFG *cfg = AC.getCFG()) {
UninitValsDiagReporter reporter(S);
UninitVariablesAnalysisStats stats;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index ae779d6830d9b..6692d1d00a129 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -30,6 +30,8 @@
#include "clang/AST/Type.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/DiagnosticComment.h"
+#include "clang/Basic/DiagnosticIDs.h"
+#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/HLSLRuntime.h"
#include "clang/Basic/PartialDiagnostic.h"
#include "clang/Basic/SourceManager.h"
@@ -3426,6 +3428,27 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
diag::note_carries_dependency_missing_first_decl) << 1/*Param*/;
}
+ // C++26 [dcl.attr.indet]/p2:
+ // If a function parameter is declared with the indeterminate attribute, it
+ // shall be so declared in the first declaration of its function. If a
+ // function parameter is declared with the indeterminate attribute in the
+ // first declaration of its function in one translation unit and the same
+ // function is declared without the indeterminate attribute on the same
+ // parameter in its first declaration in another translation unit, the program
+ // is ill-formed, no diagnostic required.
+ if (S.getLangOpts().CPlusPlus26) {
+ const IndeterminateAttr *IA = newDecl->getAttr<IndeterminateAttr>();
+ if (IA && !oldDecl->hasAttr<IndeterminateAttr>()) {
+ S.Diag(IA->getLocation(), diag::err_indeterminate_attr_not_on_first_decl)
+ << newDecl;
+ const FunctionDecl *FirstFD =
+ cast<FunctionDecl>(oldDecl->getDeclContext())->getFirstDecl();
+ const ParmVarDecl *FirstVD =
+ FirstFD->getParamDecl(oldDecl->getFunctionScopeIndex());
+ S.Diag(FirstVD->getLocation(), diag::note_previous_declaration);
+ }
+ }
+
propagateAttributes(
newDecl, oldDecl, [&S](ParmVarDecl *To, const ParmVarDecl *From) {
unsigned found = 0;
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 3497ff7856eed..fd8d44c703d48 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -8129,6 +8129,7 @@ static Expr *BuildExpressionFromNonTypeTemplateArgumentValue(
case APValue::None:
case APValue::Indeterminate:
+ case APValue::Erroneous:
llvm_unreachable("Unexpected APValue kind.");
case APValue::LValue:
case APValue::MemberPointer:
diff --git a/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp b/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
new file mode 100644
index 0000000000000..bc9bffda65129
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
@@ -0,0 +1,82 @@
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown %s -emit-llvm -o - | FileCheck %s -check-prefix=CXX26
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s -check-prefix=ZERO
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s -check-prefix=PATTERN
+
+// Test for C++26 [[indeterminate]] attribute (P2795R5)
+// The [[indeterminate]] attribute opts out of erroneous initialization.
+
+template<typename T> void used(T &) noexcept;
+
+extern "C" {
+
+// Test: [[indeterminate]] should suppress zero/pattern initialization
+// CXX26-LABEL: test_indeterminate_local_var(
+// CXX26: alloca i32
+// CXX26-NOT: store
+// CXX26: call void
+// ZERO-LABEL: test_indeterminate_local_var(
+// ZERO: alloca i32
+// ZERO-NOT: store
+// ZERO: call void
+// PATTERN-LABEL: test_indeterminate_local_var(
+// PATTERN: alloca i32
+// PATTERN-NOT: store
+// PATTERN: call void
+void test_indeterminate_local_var() {
+ [[indeterminate]] int x;
+ used(x);
+}
+
+// Test: Without [[indeterminate]], zero/pattern init should apply
+// CXX26-LABEL: test_normal_local_var(
+// CXX26: alloca i32
+// CXX26-NEXT: call void
+// ZERO-LABEL: test_normal_local_var(
+// ZERO: alloca i32
+// ZERO: store i32 0
+// PATTERN-LABEL: test_normal_local_var(
+// PATTERN: alloca i32
+// PATTERN: store i32 -1431655766
+void test_normal_local_var() {
+ int y;
+ used(y);
+}
+
+// Test: [[indeterminate]] on multiple variables
+// ZERO-LABEL: test_indeterminate_multiple_vars(
+// ZERO: %a = alloca i32
+// ZERO: %b = alloca [10 x i32]
+// ZERO: %c = alloca [10 x [10 x i32]]
+// ZERO-NOT: call void @llvm.memset
+// ZERO: call void @_Z4used
+void test_indeterminate_multiple_vars() {
+ [[indeterminate]] int a, b[10], c[10][10];
+ used(a);
+}
+
+// Test: Mixed indeterminate and normal variables
+// ZERO-LABEL: test_mixed_vars(
+void test_mixed_vars() {
+ int normal = {}; // Explicitly zero-initialized
+ [[indeterminate]] int indeterminate_var;
+ int erroneous; // Will get erroneous initialization if -ftrivial-auto-var-init
+ used(normal);
+ used(indeterminate_var);
+ used(erroneous);
+}
+
+} // extern "C"
+
+// Test: Struct with indeterminate member initialization
+struct SelfStorage {
+ char data[512];
+ void use_data();
+};
+
+// ZERO-LABEL: test_struct_indeterminate
+// ZERO: alloca %struct.SelfStorage
+// ZERO-NOT: call void @llvm.memset
+void test_struct_indeterminate() {
+ [[indeterminate]] SelfStorage s;
+ s.use_data();
+}
diff --git a/clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp b/clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp
new file mode 100644
index 0000000000000..d8d7cfa22eb6d
--- /dev/null
+++ b/clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -Werroneous-behavior -verify %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wuninitialized -verify=cxx23 %s
+
+// Test for C++26 erroneous behavior warnings (P2795R5)
+
+void test_erroneous_read() {
+ int x; // expected-note {{variable 'x' was default-initialized here}} \
+ // expected-note {{initialize the variable 'x' to silence this warning}} \
+ // cxx23-note {{initialize the variable 'x' to silence this warning}}
+ int y = x; // expected-warning {{reading from variable 'x' with erroneous value is erroneous behavior}} \
+ // cxx23-warning {{variable 'x' is uninitialized when used here}}
+}
+
+// In C++23, this is regular uninitialized warning
+void test_cxx23_uninit() {
+ int x; // cxx23-note {{initialize the variable 'x' to silence this warning}} \
+ // expected-note {{variable 'x' was default-initialized here}} \
+ // expected-note {{initialize the variable 'x' to silence this warning}}
+ int y = x; // cxx23-warning {{variable 'x' is uninitialized when used here}} \
+ // expected-warning {{reading from variable 'x' with erroneous value is erroneous behavior}}
+}
+
+#if __cplusplus >= 202400L
+// With [[indeterminate]], it's still a regular uninitialized warning (UB, not erroneous)
+void test_indeterminate_uninit() {
+ [[indeterminate]] int x;
+ int y = x; // No erroneous behavior warning - this is UB, not erroneous
+}
+#endif
diff --git a/clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp b/clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp
new file mode 100644
index 0000000000000..336679e52bed5
--- /dev/null
+++ b/clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp
@@ -0,0 +1,55 @@
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s
+
+// Test for C++26 erroneous behavior in constant expressions (P2795R5)
+// Reading an uninitialized/erroneous value in a constant expression is an error.
+
+// Direct read of default-initialized variable
+constexpr int test1() {
+ int x; // default-initialized, has erroneous value
+ return x; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr int val1 = test1(); // expected-error {{constexpr variable 'val1' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test1()'}}
+
+// Reading member with erroneous value
+struct S {
+ int x;
+ constexpr S() {} // x has erroneous value
+};
+
+constexpr int test2() {
+ S s;
+ return s.x; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr int val2 = test2(); // expected-error {{constexpr variable 'val2' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test2()'}}
+
+// [[indeterminate]] in constexpr - also an error
+constexpr int test3() {
+ [[indeterminate]] int x; // x has indeterminate value (UB in general, error in constexpr)
+ return x; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr int val3 = test3(); // expected-error {{constexpr variable 'val3' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test3()'}}
+
+// Proper initialization is fine
+constexpr int test4() {
+ int x = 42;
+ return x;
+}
+constexpr int val4 = test4(); // OK
+
+// Array with erroneous elements
+constexpr int test5() {
+ int arr[3]; // elements have erroneous values
+ return arr[0]; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr int val5 = test5(); // expected-error {{constexpr variable 'val5' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test5()'}}
+
+// Partial initialization - uninitialized portion is erroneous
+constexpr int test6() {
+ int arr[3] = {1}; // arr[1] and arr[2] are zero-initialized, not erroneous
+ return arr[1]; // OK - zero-initialized
+}
+constexpr int val6 = test6(); // OK, val6 == 0
diff --git a/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp b/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
new file mode 100644
index 0000000000000..ac272c7962d00
--- /dev/null
+++ b/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
@@ -0,0 +1,66 @@
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=cxx23 %s
+
+// Test for C++26 [[indeterminate]] attribute (P2795R5)
+
+// In C++23, the attribute is unknown and ignored
+void test_cxx23() {
+ [[indeterminate]] int x; // cxx23-warning {{'indeterminate' attribute ignored}}
+}
+
+#if __cplusplus >= 202400L
+
+// local variable with automatic storage duration
+void test_local_var() {
+ [[indeterminate]] int x; // OK
+ [[indeterminate]] int arr[10]; // OK
+ [[indeterminate]] int a, b, c; // OK - multiple declarators
+}
+
+// function parameter
+void test_param([[indeterminate]] int x); // OK
+
+// static storage duration
+// expected-warning at +1 {{'indeterminate' attribute only applies to local variables or function parameters}}
+[[indeterminate]] int global_var;
+
+void test_static() {
+ // expected-warning at +1 {{'indeterminate' attribute only applies to local variables or function parameters}}
+ [[indeterminate]] static int x;
+ // expected-warning at +1 {{'indeterminate' attribute only applies to local variables or function parameters}}
+ [[indeterminate]] thread_local int y;
+}
+
+// attribute on class-type local variable
+struct S {
+ int x;
+ S() {}
+};
+
+void test_class_type() {
+ [[indeterminate]] S s; // OK - member x has indeterminate value
+}
+
+// constexpr context should error on reading indeterminate value
+constexpr int test_constexpr() {
+ [[indeterminate]] int x;
+ return x; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr int val_constexpr = test_constexpr(); // expected-error {{constexpr variable 'val_constexpr' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test_constexpr()'}}
+
+// declaration position
+void test_decl_position() {
+ int x [[indeterminate]]; // OK - attribute on declarator
+ [[indeterminate]] int y; // OK - attribute at beginning
+}
+
+// [[indeterminate]] must be on first declaration (P2795R5 [dcl.attr.indet]/p2)
+void first_decl_test(int x); // first declaration without attribute
+void first_decl_test([[indeterminate]] int x); // expected-error {{'[[indeterminate]]' attribute on parameter 'x' must appear on the first declaration of the function}}
+ // expected-note at -2 {{previous declaration is here}}
+
+void first_decl_ok([[indeterminate]] int x); // first declaration with attribute - OK
+void first_decl_ok([[indeterminate]] int x) {} // redeclaration with attribute - OK
+
+#endif // __cplusplus >= 202400L
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 61fb40e79ea91..b92dfacfbe1dc 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -60,7 +60,7 @@ class FlattenedSpelling {
N(Spelling.getValueAsString("Name")), OriginalSpelling(Spelling) {
assert(V != "GCC" && V != "Clang" &&
"Given a GCC spelling, which means this hasn't been flattened!");
- if (V == "CXX11" || V == "C23" || V == "Pragma")
+ if (V == "CXX11" || V == "C23" || V == "Pragma" || V == "CXX26")
NS = Spelling.getValueAsString("Namespace");
}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 30da7a636fda6..331e851393850 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -192,7 +192,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Erroneous behaviour for uninitialized reads</td>
<td><a href="https://wg21.link/P2795R5">P2795R5</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 23</td>
</tr>
<tr>
<td><tt>= delete("should have a reason");</tt></td>
>From b7b2ed34c137559c18f1469abd3cde5bd7cee48b Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 14 Apr 2026 08:13:51 -0700
Subject: [PATCH 2/3] [clang] Address review comments
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/include/clang/AST/APValue.h | 5 +-
clang/include/clang/Basic/Attr.td | 8 +-
clang/include/clang/Basic/DiagnosticGroups.td | 7 +-
.../clang/Basic/DiagnosticSemaKinds.td | 10 +-
clang/lib/AST/ExprConstant.cpp | 6 +-
clang/lib/CodeGen/CGDecl.cpp | 5 +-
clang/lib/CodeGen/CGExprConstant.cpp | 8 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 50 ++++-----
clang/lib/Sema/SemaDecl.cpp | 20 ++--
clang/lib/Sema/SemaDeclAttr.cpp | 12 +++
clang/test/CXX/drs/cwg1736.cpp | 9 +-
.../cxx26-indeterminate-attribute.cpp | 100 +++++++++++-------
.../cxx26-erroneous-behavior-warning.cpp | 30 +++---
.../SemaCXX/cxx26-erroneous-constexpr.cpp | 71 ++++++++++++-
.../SemaCXX/cxx26-indeterminate-attribute.cpp | 64 +++++++++--
.../SemaCXX/cxx2c-constexpr-placement-new.cpp | 4 +-
.../ASTMatchers/ASTMatchersNarrowingTest.cpp | 57 +++++-----
.../ASTMatchers/ASTMatchersNodeTest.cpp | 51 ++++-----
.../Tooling/Syntax/BuildTreeTest.cpp | 7 +-
clang/utils/TableGen/ClangAttrEmitter.cpp | 2 +-
20 files changed, 332 insertions(+), 194 deletions(-)
diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h
index 48880429a1597..2ec51fc20a636 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -131,7 +131,7 @@ class APValue {
Indeterminate,
/// [defns.erroneous]:
- /// Erroneous behavior is always the consequence of incorrectprogram code.
+ /// Erroneous behavior is always the consequence of incorrect program code.
/// Implementations are allowed, but not required, to diagnose it
/// ([intro.compliance.general]). Evaluation of a constant expression
/// ([expr.const]) never exhibits behavior specified as erroneous in [intro]
@@ -496,6 +496,9 @@ class APValue {
bool isAbsent() const { return Kind == None; }
bool isIndeterminate() const { return Kind == Indeterminate; }
bool isErroneous() const { return Kind == Erroneous; }
+ /// Whether the value is indeterminate or erroneous (i.e., not properly
+ /// initialized). Use isErroneous() when the distinction matters.
+ bool isUninit() const { return Kind == Indeterminate || Kind == Erroneous; }
bool hasValue() const { return Kind != None && Kind != Indeterminate; }
bool isInt() const { return Kind == Int; }
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0bd8c8ce83075..fc8486facb873 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -360,10 +360,6 @@ class CXX11<string namespace, string name, int version = 1>
: Spelling<name, "CXX11", version> {
string Namespace = namespace;
}
-class CXX26<string namespace, string name, int version = 1>
- : Spelling<name, "CXX26", version> {
- string Namespace = namespace;
-}
class C23<string namespace, string name, int version = 1>
: Spelling<name, "C23", version> {
string Namespace = namespace;
@@ -446,7 +442,6 @@ def SYCLHost : LangOpt<"SYCLIsHost">;
def SYCLDevice : LangOpt<"SYCLIsDevice">;
def COnly : LangOpt<"", "!LangOpts.CPlusPlus">;
def CPlusPlus : LangOpt<"CPlusPlus">;
-def CPlusPlus26 : LangOpt<"CPlusPlus26">;
def OpenCL : LangOpt<"OpenCL">;
def ObjC : LangOpt<"ObjC">;
def BlocksSupported : LangOpt<"Blocks">;
@@ -4900,9 +4895,8 @@ def Uninitialized : InheritableAttr {
def Indeterminate : InheritableAttr {
let Spellings = [CXX11<"", "indeterminate", 202403>];
let Subjects = SubjectList<[AutomaticStorageVar]>;
- let LangOpts = [CPlusPlus26];
+ let LangOpts = [CPlusPlus];
let Documentation = [IndeterminateDocs];
- let SimpleHandler = 1;
}
def LoaderUninitialized : Attr {
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 2b6c6a5d1a045..06d5e93d5a63c 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1107,7 +1107,6 @@ def Uninitialized : DiagGroup<"uninitialized", [UninitializedSometimes,
UninitializedStaticSelfInit,
UninitializedConstReference,
UninitializedConstPointer]>;
-def ErroneousBehavior : DiagGroup<"erroneous-behavior">;
def IgnoredPragmaIntrinsic : DiagGroup<"ignored-pragma-intrinsic">;
// #pragma optimize is often used to avoid to work around MSVC codegen bugs or
// to disable inlining. It's not completely clear what alternative to suggest
@@ -1488,10 +1487,12 @@ def CXX14Attrs : DiagGroup<"c++14-attribute-extensions">;
def CXX17Attrs : DiagGroup<"c++17-attribute-extensions">;
def CXX20Attrs : DiagGroup<"c++20-attribute-extensions">;
def CXX23Attrs : DiagGroup<"c++23-attribute-extensions">;
+def CXX26Attrs : DiagGroup<"c++26-attribute-extensions">;
def FutureAttrs : DiagGroup<"future-attribute-extensions", [CXX14Attrs,
CXX17Attrs,
CXX20Attrs,
- CXX23Attrs]>;
+ CXX23Attrs,
+ CXX26Attrs]>;
def CXX23AttrsOnLambda : DiagGroup<"c++23-lambda-attributes">;
@@ -1518,7 +1519,7 @@ def CXX23 : DiagGroup<"c++23-extensions", [CXX23AttrsOnLambda]>;
// A warning group for warnings about using C++26 features as extensions in
// earlier C++ versions.
-def CXX26 : DiagGroup<"c++26-extensions">;
+def CXX26 : DiagGroup<"c++26-extensions", [CXX26Attrs]>;
def : DiagGroup<"c++0x-extensions", [CXX11]>;
def : DiagGroup<"c++1y-extensions", [CXX14]>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8f7b1fe46ee46..fda34398aaf85 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2539,6 +2539,8 @@ def warn_uninit_self_reference_in_reference_init : Warning<
def warn_uninit_var : Warning<
"variable %0 is uninitialized when %select{used here|captured by block}1">,
InGroup<Uninitialized>, DefaultIgnore;
+def err_uninit_var : Error<
+ "variable %0 is uninitialized when %select{used here|captured by block}1">;
def warn_sometimes_uninit_var : Warning<
"variable %0 is %select{used|captured}1 uninitialized whenever "
"%select{'%3' condition is %select{true|false}4|"
@@ -2586,12 +2588,6 @@ def warn_uninit_const_pointer : Warning<
"variable %0 is uninitialized when passed as a const pointer argument here">,
InGroup<UninitializedConstPointer>, DefaultIgnore;
-def warn_erroneous_behavior_uninitialized_read : Warning<
- "reading from variable %0 with erroneous value is erroneous behavior">,
- InGroup<ErroneousBehavior>, DefaultIgnore;
-def note_variable_erroneous_init : Note<
- "variable %0 was default-initialized here; "
- "consider using '[[indeterminate]]' attribute to opt out">;
def err_indeterminate_attr_not_on_first_decl : Error<
"'[[indeterminate]]' attribute on parameter %0 must appear on the "
"first declaration of the function">;
@@ -10005,6 +10001,8 @@ def ext_cxx20_attr : Extension<
"use of the %0 attribute is a C++20 extension">, InGroup<CXX20Attrs>;
def ext_cxx23_attr : Extension<
"use of the %0 attribute is a C++23 extension">, InGroup<CXX23Attrs>;
+def ext_cxx26_attr : Extension<
+ "use of the %0 attribute is a C++26 extension">, InGroup<CXX26Attrs>;
def warn_unused_comparison : Warning<
"%select{equality|inequality|relational|three-way}0 comparison result unused">,
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index ee73a1540016c..9d5f9e685e5b9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -4175,8 +4175,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
// OK. Reading an erroneous value is erroneous behavior also not allowed in
// constant expressions.
if ((O->isAbsent() && !(handler.AccessKind == AK_Construct && I == N)) ||
- ((O->isIndeterminate() || O->isErroneous()) &&
- !isValidIndeterminateAccess(handler.AccessKind))) {
+ (O->isUninit() && !isValidIndeterminateAccess(handler.AccessKind))) {
// Object has ended lifetime.
// If I is non-zero, some subobject (member or array element) of a
// complete object has ended its lifetime, so this is valid for
@@ -4185,8 +4184,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
return false;
if (!Info.checkingPotentialConstantExpression())
Info.FFDiag(E, diag::note_constexpr_access_uninit)
- << handler.AccessKind << (O->isIndeterminate() || O->isErroneous())
- << E->getSourceRange();
+ << handler.AccessKind << O->isUninit() << E->getSourceRange();
return handler.failed();
}
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 158b6b9b7ff44..d9b434112abd2 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1996,10 +1996,9 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
return D && D->hasAttr<NoTrivialAutoVarInitAttr>();
};
- // C++26 [[indeterminate]] attribute opts out of an erroneous
- // initialization, restoring indeterminate (undefined) behavior.
-
// Note: constexpr already initializes everything correctly.
+ // C++26 [[indeterminate]] attribute opts out of erroneous initialization,
+ // restoring indeterminate (undefined) behavior.
LangOptions::TrivialAutoVarInitKind trivialAutoVarInit =
((D.isConstexpr() || D.getAttr<UninitializedAttr>() ||
D.hasAttr<IndeterminateAttr>() ||
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index bdde46f2876d3..ed714a6fad4f1 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -2569,11 +2569,13 @@ ConstantEmitter::tryEmitPrivate(const APValue &Value, QualType DestType,
switch (Value.getKind()) {
case APValue::None:
case APValue::Indeterminate:
- case APValue::Erroneous:
// Out-of-lifetime and indeterminate values can be modeled as 'undef'.
- // For C++ erroneous values, runtime code generation uses a defined pattern,
- // but for constant expression failures we use undef.
return llvm::UndefValue::get(CGM.getTypes().ConvertType(DestType));
+ case APValue::Erroneous:
+ // Erroneous values are well-defined (not UB) in C++26, so use zero
+ // rather than undef to avoid reintroducing undefined behavior at the
+ // LLVM IR level.
+ return llvm::Constant::getNullValue(CGM.getTypes().ConvertType(DestType));
case APValue::LValue:
return ConstantLValueEmitter(*this, Value, DestType,
EnablePtrAuthFunctionTypeDiscrimination)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 68798b6676d0b..b61d80c9c5d61 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -977,39 +977,26 @@ static void CreateIfFixit(Sema &S, const Stmt *If, const Stmt *Then,
/// uninitialized use of a variable.
static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
bool IsCapturedByBlock) {
+ // [[indeterminate]] explicitly opts into indeterminate values, so
+ // suppress uninitialized warnings for such variables.
+ if (VD->hasAttr<IndeterminateAttr>())
+ return;
+
bool Diagnosed = false;
- // [basic.indet]/p1.1:
- // - If the object has dynamic storage duration, or is the object associated
- // with a variable or function parameter whose first declaration is marked
- // with the [[indeterminate]] attribute ([dcl.attr.indet]), the bytes have
- // indeterminate values;
- //
- // - otherwise, the bytes have erroneous values, where each value is
- // determined
- // by the implementation independently of the state of the program.
- //
- // If variable has automatic storage duration and does
- // not have [[indeterminate]], reading it is erroneous behavior (not
- // undefined). However, we still warn about it.
- bool IsErroneousBehavior = S.getLangOpts().CPlusPlus26 &&
- VD->hasLocalStorage() &&
- !VD->hasAttr<IndeterminateAttr>();
switch (Use.getKind()) {
case UninitUse::Always:
- if (IsErroneousBehavior &&
- !S.Diags.isIgnored(diag::warn_erroneous_behavior_uninitialized_read,
- Use.getUser()->getBeginLoc())) {
- S.Diag(Use.getUser()->getBeginLoc(),
- diag::warn_erroneous_behavior_uninitialized_read)
- << VD->getDeclName() << Use.getUser()->getSourceRange();
- S.Diag(VD->getLocation(), diag::note_variable_erroneous_init)
- << VD->getDeclName();
- return;
+ // In C++26, reading an uninitialized local variable without
+ // [[indeterminate]] is erroneous behavior ([basic.indet]).
+ if (S.getLangOpts().CPlusPlus26 && VD->hasLocalStorage()) {
+ S.Diag(Use.getUser()->getBeginLoc(), diag::err_uninit_var)
+ << VD->getDeclName() << IsCapturedByBlock
+ << Use.getUser()->getSourceRange();
+ } else {
+ S.Diag(Use.getUser()->getBeginLoc(), diag::warn_uninit_var)
+ << VD->getDeclName() << IsCapturedByBlock
+ << Use.getUser()->getSourceRange();
}
- S.Diag(Use.getUser()->getBeginLoc(), diag::warn_uninit_var)
- << VD->getDeclName() << IsCapturedByBlock
- << Use.getUser()->getSourceRange();
return;
case UninitUse::AfterDecl:
@@ -3152,13 +3139,12 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
Analyzer.run(AC);
}
- if (!Diags.isIgnored(diag::warn_uninit_var, D->getBeginLoc()) ||
+ if (S.getLangOpts().CPlusPlus26 ||
+ !Diags.isIgnored(diag::warn_uninit_var, D->getBeginLoc()) ||
!Diags.isIgnored(diag::warn_sometimes_uninit_var, D->getBeginLoc()) ||
!Diags.isIgnored(diag::warn_maybe_uninit_var, D->getBeginLoc()) ||
!Diags.isIgnored(diag::warn_uninit_const_reference, D->getBeginLoc()) ||
- !Diags.isIgnored(diag::warn_uninit_const_pointer, D->getBeginLoc()) ||
- !Diags.isIgnored(diag::warn_erroneous_behavior_uninitialized_read,
- D->getBeginLoc())) {
+ !Diags.isIgnored(diag::warn_uninit_const_pointer, D->getBeginLoc())) {
if (CFG *cfg = AC.getCFG()) {
UninitValsDiagReporter reporter(S);
UninitVariablesAnalysisStats stats;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1e05d9ce72248..a81f35234a569 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3457,17 +3457,15 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
// function is declared without the indeterminate attribute on the same
// parameter in its first declaration in another translation unit, the program
// is ill-formed, no diagnostic required.
- if (S.getLangOpts().CPlusPlus26) {
- const IndeterminateAttr *IA = newDecl->getAttr<IndeterminateAttr>();
- if (IA && !oldDecl->hasAttr<IndeterminateAttr>()) {
- S.Diag(IA->getLocation(), diag::err_indeterminate_attr_not_on_first_decl)
- << newDecl;
- const FunctionDecl *FirstFD =
- cast<FunctionDecl>(oldDecl->getDeclContext())->getFirstDecl();
- const ParmVarDecl *FirstVD =
- FirstFD->getParamDecl(oldDecl->getFunctionScopeIndex());
- S.Diag(FirstVD->getLocation(), diag::note_previous_declaration);
- }
+ if (const auto *IA = newDecl->getAttr<IndeterminateAttr>();
+ IA && !oldDecl->hasAttr<IndeterminateAttr>()) {
+ S.Diag(IA->getLocation(), diag::err_indeterminate_attr_not_on_first_decl)
+ << newDecl;
+ const FunctionDecl *FirstFD =
+ cast<FunctionDecl>(oldDecl->getDeclContext())->getFirstDecl();
+ const ParmVarDecl *FirstVD =
+ FirstFD->getParamDecl(oldDecl->getFunctionScopeIndex());
+ S.Diag(FirstVD->getLocation(), diag::note_previous_declaration);
}
propagateAttributes(
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 8a856215a9627..650bdcf6da34a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6939,6 +6939,14 @@ static void handleUninitializedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(::new (S.Context) UninitializedAttr(S.Context, AL));
}
+static void handleIndeterminateAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ if (!S.getLangOpts().CPlusPlus26 && AL.isCXX11Attribute() &&
+ !AL.getScopeName()) {
+ S.Diag(AL.getLoc(), diag::ext_cxx26_attr) << AL;
+ }
+ D->addAttr(::new (S.Context) IndeterminateAttr(S.Context, AL));
+}
+
static void handleMIGServerRoutineAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// Check that the return type is a `typedef int kern_return_t` or a typedef
// around it, because otherwise MIG convention checks make no sense.
@@ -8198,6 +8206,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
handleUninitializedAttr(S, D, AL);
break;
+ case ParsedAttr::AT_Indeterminate:
+ handleIndeterminateAttr(S, D, AL);
+ break;
+
case ParsedAttr::AT_ObjCExternallyRetained:
S.ObjC().handleExternallyRetainedAttr(D, AL);
break;
diff --git a/clang/test/CXX/drs/cwg1736.cpp b/clang/test/CXX/drs/cwg1736.cpp
index 14c840677b1f7..1da8cc0a5614b 100644
--- a/clang/test/CXX/drs/cwg1736.cpp
+++ b/clang/test/CXX/drs/cwg1736.cpp
@@ -4,7 +4,7 @@
// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11
// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11
// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11
-// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11
+// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx26
// cxx98-no-diagnostics
@@ -16,10 +16,13 @@ struct S {
using S::S;
};
typename T::type value;
- // since-cxx11-error at -1 {{type 'int' cannot be used prior to '::' because it has no members}}
+ L l(value); // #cwg1736-l
+ // since-cxx26-error at -1 {{variable 'value' is uninitialized when used here}}
+ // since-cxx26-note@#cwg1736-s {{in instantiation of function template specialization 'cwg1736::S::S<cwg1736::Q>' requested here}}
+ // since-cxx26-note at -4 {{initialize the variable 'value' to silence this warning}}
+ // since-cxx11-error at -5 {{type 'int' cannot be used prior to '::' because it has no members}}
// since-cxx11-note@#cwg1736-l {{in instantiation of function template specialization 'cwg1736::S::S<int>' requested here}}
// since-cxx11-note@#cwg1736-s {{in instantiation of function template specialization 'cwg1736::S::S<cwg1736::Q>' requested here}}
- L l(value); // #cwg1736-l
}
};
struct Q { typedef int type; } q;
diff --git a/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp b/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
index bc9bffda65129..af8d0888d2959 100644
--- a/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
+++ b/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
@@ -3,63 +3,78 @@
// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s -check-prefix=PATTERN
// Test for C++26 [[indeterminate]] attribute (P2795R5)
-// The [[indeterminate]] attribute opts out of erroneous initialization.
+// The [[indeterminate]] attribute opts out of erroneous initialization,
+// suppressing trivial auto var init even with -ftrivial-auto-var-init.
template<typename T> void used(T &) noexcept;
extern "C" {
-// Test: [[indeterminate]] should suppress zero/pattern initialization
-// CXX26-LABEL: test_indeterminate_local_var(
-// CXX26: alloca i32
-// CXX26-NOT: store
-// CXX26: call void
-// ZERO-LABEL: test_indeterminate_local_var(
-// ZERO: alloca i32
-// ZERO-NOT: store
-// ZERO: call void
-// PATTERN-LABEL: test_indeterminate_local_var(
-// PATTERN: alloca i32
-// PATTERN-NOT: store
-// PATTERN: call void
+// [[indeterminate]] suppresses all initialization: no store between alloca and use.
+// CXX26-LABEL: @test_indeterminate_local_var(
+// CXX26: %x = alloca i32
+// CXX26-NOT: store
+// CXX26: call void @_Z4usedIiEvRT_(
+// ZERO-LABEL: @test_indeterminate_local_var(
+// ZERO: %x = alloca i32
+// ZERO-NOT: store
+// ZERO: call void @_Z4usedIiEvRT_(
+// PATTERN-LABEL: @test_indeterminate_local_var(
+// PATTERN: %x = alloca i32
+// PATTERN-NOT: store
+// PATTERN: call void @_Z4usedIiEvRT_(
void test_indeterminate_local_var() {
[[indeterminate]] int x;
used(x);
}
-// Test: Without [[indeterminate]], zero/pattern init should apply
-// CXX26-LABEL: test_normal_local_var(
-// CXX26: alloca i32
-// CXX26-NEXT: call void
-// ZERO-LABEL: test_normal_local_var(
-// ZERO: alloca i32
-// ZERO: store i32 0
-// PATTERN-LABEL: test_normal_local_var(
-// PATTERN: alloca i32
-// PATTERN: store i32 -1431655766
+// Without [[indeterminate]], -ftrivial-auto-var-init should apply normally.
+// Default (no flag): no store.
+// CXX26-LABEL: @test_normal_local_var(
+// CXX26: %y = alloca i32
+// CXX26-NOT: store
+// CXX26: call void @_Z4usedIiEvRT_(
+// ZERO-LABEL: @test_normal_local_var(
+// ZERO: %y = alloca i32
+// ZERO: store i32 0, ptr %y
+// ZERO: call void @_Z4usedIiEvRT_(
+// PATTERN-LABEL: @test_normal_local_var(
+// PATTERN: %y = alloca i32
+// PATTERN: store i32 -1431655766, ptr %y
+// PATTERN: call void @_Z4usedIiEvRT_(
void test_normal_local_var() {
int y;
used(y);
}
-// Test: [[indeterminate]] on multiple variables
-// ZERO-LABEL: test_indeterminate_multiple_vars(
-// ZERO: %a = alloca i32
-// ZERO: %b = alloca [10 x i32]
-// ZERO: %c = alloca [10 x [10 x i32]]
-// ZERO-NOT: call void @llvm.memset
-// ZERO: call void @_Z4used
+// [[indeterminate]] on multiple variables: no memset or store for any of them.
+// ZERO-LABEL: @test_indeterminate_multiple_vars(
+// ZERO: %a = alloca i32
+// ZERO: %b = alloca [10 x i32]
+// ZERO: %c = alloca [10 x [10 x i32]]
+// ZERO-NOT: store
+// ZERO-NOT: call void @llvm.memset
+// ZERO: call void @_Z4usedIiEvRT_(
void test_indeterminate_multiple_vars() {
[[indeterminate]] int a, b[10], c[10][10];
used(a);
}
-// Test: Mixed indeterminate and normal variables
-// ZERO-LABEL: test_mixed_vars(
+// Mixed: normal var gets zero-init, [[indeterminate]] var does not,
+// erroneous var (no attribute) gets zero-init.
+// ZERO-LABEL: @test_mixed_vars(
+// ZERO: %normal = alloca i32
+// ZERO: %indeterminate_var = alloca i32
+// ZERO: %erroneous = alloca i32
+// ZERO: store i32 0, ptr %normal
+// ZERO: store i32 0, ptr %erroneous
+// ZERO: call void @_Z4usedIiEvRT_(ptr {{.*}} %normal)
+// ZERO: call void @_Z4usedIiEvRT_(ptr {{.*}} %indeterminate_var)
+// ZERO: call void @_Z4usedIiEvRT_(ptr {{.*}} %erroneous)
void test_mixed_vars() {
int normal = {}; // Explicitly zero-initialized
[[indeterminate]] int indeterminate_var;
- int erroneous; // Will get erroneous initialization if -ftrivial-auto-var-init
+ int erroneous; // Will get zero-init with -ftrivial-auto-var-init=zero
used(normal);
used(indeterminate_var);
used(erroneous);
@@ -73,10 +88,21 @@ struct SelfStorage {
void use_data();
};
-// ZERO-LABEL: test_struct_indeterminate
-// ZERO: alloca %struct.SelfStorage
-// ZERO-NOT: call void @llvm.memset
+// ZERO-LABEL: @_Z25test_struct_indeterminatev(
+// ZERO: %s = alloca %struct.SelfStorage
+// ZERO-NOT: call void @llvm.memset
+// ZERO: call void @_ZN11SelfStorage8use_dataEv(
void test_struct_indeterminate() {
[[indeterminate]] SelfStorage s;
s.use_data();
}
+
+// Without [[indeterminate]], struct gets zero-init.
+// ZERO-LABEL: @_Z18test_struct_normalv(
+// ZERO: %s = alloca %struct.SelfStorage
+// ZERO: call void @llvm.memset
+// ZERO: call void @_ZN11SelfStorage8use_dataEv(
+void test_struct_normal() {
+ SelfStorage s;
+ s.use_data();
+}
diff --git a/clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp b/clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp
index d8d7cfa22eb6d..0c7b60dfcc695 100644
--- a/clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp
+++ b/clang/test/SemaCXX/cxx26-erroneous-behavior-warning.cpp
@@ -1,29 +1,23 @@
-// RUN: %clang_cc1 -std=c++26 -fsyntax-only -Werroneous-behavior -verify %s
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -Wuninitialized -verify %s
// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wuninitialized -verify=cxx23 %s
-// Test for C++26 erroneous behavior warnings (P2795R5)
+// Test for C++26 erroneous behavior diagnostics (P2795R5)
+// In C++26, reading an uninitialized local variable without [[indeterminate]]
+// is erroneous behavior and produces an error.
+// In C++23 and earlier, it's the usual -Wuninitialized warning.
-void test_erroneous_read() {
- int x; // expected-note {{variable 'x' was default-initialized here}} \
- // expected-note {{initialize the variable 'x' to silence this warning}} \
+void test_uninit_read() {
+ int x; // expected-note {{initialize the variable 'x' to silence this warning}} \
// cxx23-note {{initialize the variable 'x' to silence this warning}}
- int y = x; // expected-warning {{reading from variable 'x' with erroneous value is erroneous behavior}} \
+ int y = x; // expected-error {{variable 'x' is uninitialized when used here}} \
// cxx23-warning {{variable 'x' is uninitialized when used here}}
}
-// In C++23, this is regular uninitialized warning
-void test_cxx23_uninit() {
- int x; // cxx23-note {{initialize the variable 'x' to silence this warning}} \
- // expected-note {{variable 'x' was default-initialized here}} \
- // expected-note {{initialize the variable 'x' to silence this warning}}
- int y = x; // cxx23-warning {{variable 'x' is uninitialized when used here}} \
- // expected-warning {{reading from variable 'x' with erroneous value is erroneous behavior}}
-}
-
#if __cplusplus >= 202400L
-// With [[indeterminate]], it's still a regular uninitialized warning (UB, not erroneous)
-void test_indeterminate_uninit() {
+// With [[indeterminate]], the user explicitly opts into indeterminate values,
+// so the diagnostic is suppressed entirely.
+void test_indeterminate_no_warning() {
[[indeterminate]] int x;
- int y = x; // No erroneous behavior warning - this is UB, not erroneous
+ int y = x; // no diagnostic
}
#endif
diff --git a/clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp b/clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp
index 336679e52bed5..6ed52692e3a9a 100644
--- a/clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp
+++ b/clang/test/SemaCXX/cxx26-erroneous-constexpr.cpp
@@ -1,12 +1,15 @@
-// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected,ref %s
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected,newinterp %s -fexperimental-new-constant-interpreter
// Test for C++26 erroneous behavior in constant expressions (P2795R5)
// Reading an uninitialized/erroneous value in a constant expression is an error.
// Direct read of default-initialized variable
constexpr int test1() {
- int x; // default-initialized, has erroneous value
- return x; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
+ int x; // default-initialized, has erroneous value \
+ // expected-note {{initialize the variable 'x' to silence this warning}}
+ return x; // expected-note {{read of uninitialized object is not allowed in a constant expression}} \
+ // expected-error {{variable 'x' is uninitialized when used here}}
}
constexpr int val1 = test1(); // expected-error {{constexpr variable 'val1' must be initialized by a constant expression}} \
// expected-note {{in call to 'test1()'}}
@@ -53,3 +56,65 @@ constexpr int test6() {
return arr[1]; // OK - zero-initialized
}
constexpr int val6 = test6(); // OK, val6 == 0
+
+// (P2795R5 [bit.cast]) Erroneous/indeterminate values should propagate through bit_cast.
+
+// bit_cast of erroneous value to non-byte type is an error.
+// The erroneous bytes become indeterminate in BitCastBuffer, so the diagnostic
+// reports them as indeterminate. This is the current behavior; a fully
+// conforming implementation would distinguish erroneous from indeterminate
+// per [bit.cast]/2.
+constexpr int test_bitcast_erroneous() {
+ int x; // erroneous value
+ return __builtin_bit_cast(int, x); // ref-note {{indeterminate value can only initialize an object of type 'unsigned char'}} \
+ // newinterp-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr int val_bc1 = test_bitcast_erroneous(); // expected-error {{constexpr variable 'val_bc1' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test_bitcast_erroneous()'}}
+
+// bit_cast of [[indeterminate]] value to non-byte type is an error
+constexpr int test_bitcast_indeterminate() {
+ [[indeterminate]] int x; // indeterminate value
+ return __builtin_bit_cast(int, x); // ref-note {{indeterminate value can only initialize an object of type 'unsigned char'}} \
+ // newinterp-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr int val_bc2 = test_bitcast_indeterminate(); // expected-error {{constexpr variable 'val_bc2' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test_bitcast_indeterminate()'}}
+
+// bit_cast of erroneous value to unsigned char preserves the uninitialized
+// status.
+//
+// Per P2795R5 [bit.cast]/2, the result has erroneous value for
+// unsigned char / std::byte. Reading it in constexpr is still an error.
+constexpr unsigned char test_bitcast_erroneous_to_byte() {
+ unsigned char x; // erroneous value
+ unsigned char y = __builtin_bit_cast(unsigned char, x); // newinterp-note {{read of uninitialized object is not allowed in a constant expression}}
+ return y; // ref-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+constexpr unsigned char val_bc3 = test_bitcast_erroneous_to_byte(); // expected-error {{constexpr variable 'val_bc3' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test_bitcast_erroneous_to_byte()'}}
+
+// bit_cast of properly initialized value is fine
+constexpr int test_bitcast_ok() {
+ int x = 42;
+ return __builtin_bit_cast(int, x);
+}
+constexpr int val_bc4 = test_bitcast_ok(); // OK
+
+// bit_cast of erroneous struct member
+struct BitCastSrc {
+ int a;
+ constexpr BitCastSrc() {} // a has erroneous value
+};
+
+struct BitCastDst {
+ int a;
+};
+
+constexpr int test_bitcast_struct_erroneous() {
+ BitCastSrc src;
+ BitCastDst dst = __builtin_bit_cast(BitCastDst, src); // expected-note {{indeterminate value can only initialize an object of type 'unsigned char'}}
+ return dst.a;
+}
+constexpr int val_bc5 = test_bitcast_struct_erroneous(); // expected-error {{constexpr variable 'val_bc5' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test_bitcast_struct_erroneous()'}}
diff --git a/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp b/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
index ac272c7962d00..025fb7b57a8da 100644
--- a/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
+++ b/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
@@ -1,26 +1,23 @@
// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s
-// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=cxx23 %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -pedantic -verify=cxx23 %s
// Test for C++26 [[indeterminate]] attribute (P2795R5)
-// In C++23, the attribute is unknown and ignored
+// In C++23, the attribute is accepted as an extension with a warning.
void test_cxx23() {
- [[indeterminate]] int x; // cxx23-warning {{'indeterminate' attribute ignored}}
+ [[indeterminate]] int x; // cxx23-warning {{use of the 'indeterminate' attribute is a C++26 extension}}
}
#if __cplusplus >= 202400L
-// local variable with automatic storage duration
void test_local_var() {
[[indeterminate]] int x; // OK
[[indeterminate]] int arr[10]; // OK
[[indeterminate]] int a, b, c; // OK - multiple declarators
}
-// function parameter
void test_param([[indeterminate]] int x); // OK
-// static storage duration
// expected-warning at +1 {{'indeterminate' attribute only applies to local variables or function parameters}}
[[indeterminate]] int global_var;
@@ -31,7 +28,6 @@ void test_static() {
[[indeterminate]] thread_local int y;
}
-// attribute on class-type local variable
struct S {
int x;
S() {}
@@ -63,4 +59,58 @@ void first_decl_test([[indeterminate]] int x); // expected-error {{
void first_decl_ok([[indeterminate]] int x); // first declaration with attribute - OK
void first_decl_ok([[indeterminate]] int x) {} // redeclaration with attribute - OK
+struct NoCtorInit {
+ int a;
+ int b;
+};
+
+void test_struct_no_ctor() {
+ [[indeterminate]] NoCtorInit n; // OK - members have indeterminate values
+}
+
+struct PartialInit {
+ int x;
+ int y;
+ constexpr PartialInit() : x(0) {} // y not initialized
+};
+
+void test_partial_init() {
+ [[indeterminate]] PartialInit p; // OK - y is indeterminate
+}
+
+template<typename T>
+void test_template() {
+ [[indeterminate]] T x; // OK
+}
+void instantiate_templates() {
+ test_template<int>();
+ test_template<float>();
+ test_template<S>();
+}
+
+template<typename... Ts>
+void test_pack() {
+ [[indeterminate]] int arr[sizeof...(Ts)]; // OK
+}
+void instantiate_pack() {
+ test_pack<int, float, double>();
+}
+
+template<typename T>
+void test_template_param([[indeterminate]] T x) {} // OK
+
+void instantiate_template_param() {
+ int a;
+ test_template_param(a);
+ test_template_param(3.14);
+}
+
+template<typename T, typename... Rest>
+void test_variadic_param([[indeterminate]] T first) {
+ [[indeterminate]] T local; // OK
+}
+void instantiate_variadic() {
+ test_variadic_param<int, float, double>(0);
+}
+
#endif // __cplusplus >= 202400L
diff --git a/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp b/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp
index 4cf0e9ffe1d64..c38560cd90be2 100644
--- a/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp
+++ b/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp
@@ -26,8 +26,8 @@ consteval int conversion() {
}
consteval int indeterminate() {
- int * indeterminate;
- new (indeterminate) int(0);
+ int * indeterminate; // expected-note {{initialize the variable 'indeterminate' to silence this warning}}
+ new (indeterminate) int(0); // expected-error {{variable 'indeterminate' is uninitialized when used here}}
// expected-note at -1 {{read of uninitialized object is not allowed in a constant expression}}
return 0;
}
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index d9647c201fc30..f0337fb426ca0 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -563,8 +563,8 @@ bool operator!=(const HasOpFree& lhs, const HasOpFree& rhs)
void binop()
{
- int s1;
- int s2;
+ int s1 = 0;
+ int s2 = 0;
if (s1 != s2)
return;
}
@@ -815,7 +815,7 @@ bool operator!(HasOpBangFree const&)
void unop()
{
- int s1;
+ int s1 = 0;
if (!s1)
return;
}
@@ -1377,7 +1377,7 @@ TEST_P(ASTMatchersTest, HasType_MatchesAsString) {
}
EXPECT_TRUE(
- matches("class Y { public: void x(); }; void z() {Y* y; y->x(); }",
+ matches("class Y { public: void x(); }; void z() {Y* y = 0; y->x(); }",
cxxMemberCallExpr(on(hasType(asString("Y *"))))));
EXPECT_TRUE(
matches("class X { void x(int x) {} };",
@@ -2235,22 +2235,25 @@ TEST_P(ASTMatchersTest, HasArgument_CXXConstructorDecl) {
TK_AsIs,
cxxConstructExpr(hasArgument(0, declRefExpr(to(varDecl(hasName("y")))))));
- EXPECT_TRUE(matches(
- "class X { public: X(int); }; void x() { int y; X x(y); }", Constructor));
EXPECT_TRUE(
- matches("class X { public: X(int); }; void x() { int y; X x = X(y); }",
+ matches("class X { public: X(int); }; void x() { int y = 0; X x(y); }",
Constructor));
+ EXPECT_TRUE(matches(
+ "class X { public: X(int); }; void x() { int y = 0; X x = X(y); }",
+ Constructor));
EXPECT_TRUE(
- matches("class X { public: X(int); }; void x() { int y; X x = y; }",
+ matches("class X { public: X(int); }; void x() { int y = 0; X x = y; }",
Constructor));
- EXPECT_TRUE(notMatches(
- "class X { public: X(int); }; void x() { int z; X x(z); }", Constructor));
+ EXPECT_TRUE(
+ notMatches("class X { public: X(int); }; void x() { int z = 0; X x(z); }",
+ Constructor));
StatementMatcher WrongIndex =
traverse(TK_AsIs, cxxConstructExpr(hasArgument(
42, declRefExpr(to(varDecl(hasName("y")))))));
- EXPECT_TRUE(notMatches(
- "class X { public: X(int); }; void x() { int y; X x(y); }", WrongIndex));
+ EXPECT_TRUE(
+ notMatches("class X { public: X(int); }; void x() { int y = 0; X x(y); }",
+ WrongIndex));
}
TEST_P(ASTMatchersTest, ArgumentCountIs_CXXConstructExpr) {
@@ -2406,7 +2409,7 @@ TEST(ASTMatchersTest, ArgumentCountIs_CXXUnresolvedConstructExpr) {
TEST(ASTMatchersTest, HasArgument_CXXUnresolvedConstructExpr) {
const auto *Code =
"template <typename T> struct S{ S(int){} }; template <typename "
- "T> void x() { int y; auto s = S<T>(y); }";
+ "T> void x() { int y = 0; auto s = S<T>(y); }";
EXPECT_TRUE(matches(Code, cxxUnresolvedConstructExpr(hasArgument(
0, declRefExpr(to(varDecl(hasName("y"))))))));
EXPECT_TRUE(
@@ -4270,14 +4273,14 @@ TEST_P(ASTMatchersTest, IsAssignmentOperator) {
StatementMatcher CXXAsgmtOperator =
cxxOperatorCallExpr(isAssignmentOperator());
- EXPECT_TRUE(matches("void x() { int a; a += 1; }", BinAsgmtOperator));
- EXPECT_TRUE(matches("void x() { int a; a = 2; }", BinAsgmtOperator));
- EXPECT_TRUE(matches("void x() { int a; a &= 3; }", BinAsgmtOperator));
+ EXPECT_TRUE(matches("void x() { int a = 0; a += 1; }", BinAsgmtOperator));
+ EXPECT_TRUE(matches("void x() { int a = 0; a = 2; }", BinAsgmtOperator));
+ EXPECT_TRUE(matches("void x() { int a = 0; a &= 3; }", BinAsgmtOperator));
EXPECT_TRUE(matches("struct S { S& operator=(const S&); };"
"void x() { S s1, s2; s1 = s2; }",
CXXAsgmtOperator));
- EXPECT_TRUE(
- notMatches("void x() { int a; if(a == 0) return; }", BinAsgmtOperator));
+ EXPECT_TRUE(notMatches("void x() { int a = 0; if(a == 0) return; }",
+ BinAsgmtOperator));
}
TEST_P(ASTMatchersTest, IsComparisonOperator) {
@@ -4289,13 +4292,13 @@ TEST_P(ASTMatchersTest, IsComparisonOperator) {
StatementMatcher CXXCompOperator =
cxxOperatorCallExpr(isComparisonOperator());
- EXPECT_TRUE(matches("void x() { int a; a == 1; }", BinCompOperator));
- EXPECT_TRUE(matches("void x() { int a; a > 2; }", BinCompOperator));
+ EXPECT_TRUE(matches("void x() { int a = 0; a == 1; }", BinCompOperator));
+ EXPECT_TRUE(matches("void x() { int a = 0; a > 2; }", BinCompOperator));
EXPECT_TRUE(matches("struct S { bool operator==(const S&); };"
"void x() { S s1, s2; bool b1 = s1 == s2; }",
CXXCompOperator));
EXPECT_TRUE(
- notMatches("void x() { int a; if(a = 0) return; }", BinCompOperator));
+ notMatches("void x() { int a = 0; if(a = 0) return; }", BinCompOperator));
}
TEST_P(ASTMatchersTest, isRightFold) {
@@ -5513,12 +5516,12 @@ TEST_P(ASTMatchersTest, IsImplicit_LambdaCapture) {
}
auto matcher = lambdaExpr(hasAnyCapture(
lambdaCapture(isImplicit(), capturesVar(varDecl(hasName("cc"))))));
- EXPECT_TRUE(
- matches("int main() { int cc; auto f = [&](){ return cc; }; }", matcher));
- EXPECT_TRUE(
- matches("int main() { int cc; auto f = [=](){ return cc; }; }", matcher));
- EXPECT_FALSE(matches("int main() { int cc; auto f = [cc](){ return cc; }; }",
- matcher));
+ EXPECT_TRUE(matches(
+ "int main() { int cc = 0; auto f = [&](){ return cc; }; }", matcher));
+ EXPECT_TRUE(matches(
+ "int main() { int cc = 0; auto f = [=](){ return cc; }; }", matcher));
+ EXPECT_FALSE(matches(
+ "int main() { int cc = 0; auto f = [cc](){ return cc; }; }", matcher));
}
} // namespace ast_matchers
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp
index 4190d4703e37d..19bfd49d122ec 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp
@@ -350,13 +350,14 @@ TEST_P(ASTMatchersTest, CallExpr_CXX) {
EXPECT_TRUE(notMatches(
"class Y { public: void x(); }; void z(Y y[]) { y->x(); }", MethodOnY));
EXPECT_TRUE(notMatches(
- "class Y { public: void x(); }; void z() { Y *y; y->x(); }", MethodOnY));
+ "class Y { public: void x(); }; void z() { Y *y = 0; y->x(); }",
+ MethodOnY));
StatementMatcher MethodOnYPointer =
cxxMemberCallExpr(on(hasType(pointsTo(recordDecl(hasName("Y"))))));
EXPECT_TRUE(
- matches("class Y { public: void x(); }; void z() { Y *y; y->x(); }",
+ matches("class Y { public: void x(); }; void z() { Y *y = 0; y->x(); }",
MethodOnYPointer));
EXPECT_TRUE(
matches("class Y { public: void x(); }; void z(Y *&y) { y->x(); }",
@@ -381,7 +382,7 @@ TEST_P(ASTMatchersTest, LambdaExpr) {
TEST_P(ASTMatchersTest, CXXForRangeStmt) {
EXPECT_TRUE(
- notMatches("void f() { for (int i; i<5; ++i); }", cxxForRangeStmt()));
+ notMatches("void f() { for (int i = 0; i<5; ++i); }", cxxForRangeStmt()));
}
TEST_P(ASTMatchersTest, CXXForRangeStmt_CXX11) {
@@ -518,8 +519,9 @@ TEST_P(ASTMatchersTest, ThisPointerType) {
"class Y { public: void x(); }; void z(Y *&y) { y->x(); }", MethodOnY));
EXPECT_TRUE(matches(
"class Y { public: void x(); }; void z(Y y[]) { y->x(); }", MethodOnY));
- EXPECT_TRUE(matches(
- "class Y { public: void x(); }; void z() { Y *y; y->x(); }", MethodOnY));
+ EXPECT_TRUE(
+ matches("class Y { public: void x(); }; void z() { Y *y = 0; y->x(); }",
+ MethodOnY));
EXPECT_TRUE(matches("class Y {"
" public: virtual void x();"
@@ -527,7 +529,7 @@ TEST_P(ASTMatchersTest, ThisPointerType) {
"class X : public Y {"
" public: virtual void x();"
"};"
- "void z() { X *x; x->Y::x(); }",
+ "void z() { X *x = 0; x->Y::x(); }",
MethodOnY));
}
@@ -589,11 +591,11 @@ TEST_P(ASTMatchersTest, CXXMemberCallExpr) {
"class X : public Y { void z() { X y; y.x(); } };",
CallOnVariableY));
EXPECT_TRUE(matches("class Y { public: void x(); };"
- "class X : public Y { void z() { X *y; y->x(); } };",
+ "class X : public Y { void z() { X *y = 0; y->x(); } };",
CallOnVariableY));
EXPECT_TRUE(notMatches(
"class Y { public: void x(); };"
- "class X : public Y { void z() { unsigned long y; ((X*)y)->x(); } };",
+ "class X : public Y { void z() { unsigned long y = 0; ((X*)y)->x(); } };",
CallOnVariableY));
}
@@ -822,8 +824,8 @@ TEST_P(ASTMatchersTest, Matcher_ThisExpr) {
}
EXPECT_TRUE(
matches("struct X { int a; int f () { return a; } };", cxxThisExpr()));
- EXPECT_TRUE(
- notMatches("struct X { int f () { int a; return a; } };", cxxThisExpr()));
+ EXPECT_TRUE(notMatches("struct X { int f () { int a = 0; return a; } };",
+ cxxThisExpr()));
}
TEST_P(ASTMatchersTest, Matcher_BindTemporaryExpression) {
@@ -942,10 +944,10 @@ TEST_P(ASTMatchersTest, Matcher_DefaultArgument) {
return;
}
StatementMatcher Arg = cxxDefaultArgExpr();
- EXPECT_TRUE(matches("void x(int, int = 0) { int y; x(y); }", Arg));
+ EXPECT_TRUE(matches("void x(int, int = 0) { int y = 0; x(y); }", Arg));
EXPECT_TRUE(
- matches("class X { void x(int, int = 0) { int y; x(y); } };", Arg));
- EXPECT_TRUE(notMatches("void x(int, int = 0) { int y; x(y, 0); }", Arg));
+ matches("class X { void x(int, int = 0) { int y = 0; x(y); } };", Arg));
+ EXPECT_TRUE(notMatches("void x(int, int = 0) { int y = 0; x(y, 0); }", Arg));
}
TEST_P(ASTMatchersTest, StringLiteral) {
@@ -1117,7 +1119,7 @@ TEST_P(ASTMatchersTest, GenericSelectionExpr) {
}
TEST_P(ASTMatchersTest, AtomicExpr) {
- EXPECT_TRUE(matches("void foo() { int *ptr; __atomic_load_n(ptr, 1); }",
+ EXPECT_TRUE(matches("void foo() { int *ptr = 0; __atomic_load_n(ptr, 1); }",
atomicExpr()));
}
@@ -2421,8 +2423,9 @@ TEST_P(ASTMatchersTest, LambdaCaptureTest) {
if (!GetParam().isCXX11OrLater()) {
return;
}
- EXPECT_TRUE(matches("int main() { int cc; auto f = [cc](){ return cc; }; }",
- lambdaExpr(hasAnyCapture(lambdaCapture()))));
+ EXPECT_TRUE(
+ matches("int main() { int cc = 0; auto f = [cc](){ return cc; }; }",
+ lambdaExpr(hasAnyCapture(lambdaCapture()))));
}
TEST_P(ASTMatchersTest, LambdaCaptureTest_BindsToCaptureOfVarDecl) {
@@ -2431,14 +2434,14 @@ TEST_P(ASTMatchersTest, LambdaCaptureTest_BindsToCaptureOfVarDecl) {
}
auto matcher = lambdaExpr(
hasAnyCapture(lambdaCapture(capturesVar(varDecl(hasName("cc"))))));
- EXPECT_TRUE(matches("int main() { int cc; auto f = [cc](){ return cc; }; }",
- matcher));
- EXPECT_TRUE(matches("int main() { int cc; auto f = [&cc](){ return cc; }; }",
- matcher));
- EXPECT_TRUE(
- matches("int main() { int cc; auto f = [=](){ return cc; }; }", matcher));
- EXPECT_TRUE(
- matches("int main() { int cc; auto f = [&](){ return cc; }; }", matcher));
+ EXPECT_TRUE(matches(
+ "int main() { int cc = 0; auto f = [cc](){ return cc; }; }", matcher));
+ EXPECT_TRUE(matches(
+ "int main() { int cc = 0; auto f = [&cc](){ return cc; }; }", matcher));
+ EXPECT_TRUE(matches(
+ "int main() { int cc = 0; auto f = [=](){ return cc; }; }", matcher));
+ EXPECT_TRUE(matches(
+ "int main() { int cc = 0; auto f = [&](){ return cc; }; }", matcher));
EXPECT_TRUE(matches(
"void f(int a) { int cc[a]; auto f = [&](){ return cc;}; }", matcher));
}
diff --git a/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp b/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp
index d58e190923a1f..ad9e148dd739d 100644
--- a/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp
+++ b/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp
@@ -258,7 +258,7 @@ TEST_P(BuildSyntaxTreeTest, IfDecl) {
R"cpp(
void test() {
[[if (int a = 5) {}]]
- [[if (int a; a == 5) {}]]
+ [[if (int a = 0; a == 5) {}]]
}
)cpp",
{R"txt(
@@ -288,7 +288,10 @@ IfStatement Statement
| | |-'int'
| | `-DeclaratorList Declarators
| | `-SimpleDeclarator ListElement
-| | `-'a'
+| | |-'a'
+| | |-'='
+| | `-IntegerLiteralExpression
+| | `-'0' LiteralToken
| `-';'
|-ExpressionStatement Condition
| `-BinaryOperatorExpression Expression
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index b92dfacfbe1dc..61fb40e79ea91 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -60,7 +60,7 @@ class FlattenedSpelling {
N(Spelling.getValueAsString("Name")), OriginalSpelling(Spelling) {
assert(V != "GCC" && V != "Clang" &&
"Given a GCC spelling, which means this hasn't been flattened!");
- if (V == "CXX11" || V == "C23" || V == "Pragma" || V == "CXX26")
+ if (V == "CXX11" || V == "C23" || V == "Pragma")
NS = Spelling.getValueAsString("Namespace");
}
>From efffbefd50028769eace80758d4b84bbe99f602e Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 15 Apr 2026 01:26:42 +0800
Subject: [PATCH 3/3] Refine and added more test
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/include/clang/AST/APValue.h | 3 +-
clang/lib/AST/APValue.cpp | 4 +++
clang/lib/CodeGen/CGDecl.cpp | 32 ++++++++++++-------
clang/lib/CodeGen/CodeGenFunction.h | 5 +--
.../cxx26-indeterminate-attribute.cpp | 7 ++--
.../SemaCXX/cxx26-indeterminate-attribute.cpp | 5 +++
6 files changed, 39 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h
index 2ec51fc20a636..2e5ffc7177da8 100644
--- a/clang/include/clang/AST/APValue.h
+++ b/clang/include/clang/AST/APValue.h
@@ -497,7 +497,8 @@ class APValue {
bool isIndeterminate() const { return Kind == Indeterminate; }
bool isErroneous() const { return Kind == Erroneous; }
/// Whether the value is indeterminate or erroneous (i.e., not properly
- /// initialized). Use isErroneous() when the distinction matters.
+ /// initialized). Use isIndeterminate()/isErroneous() when the distinction
+ /// matters.
bool isUninit() const { return Kind == Indeterminate || Kind == Erroneous; }
bool hasValue() const { return Kind != None && Kind != Indeterminate; }
diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp
index a8481e3c6d87b..7296c2aecc88a 100644
--- a/clang/lib/AST/APValue.cpp
+++ b/clang/lib/AST/APValue.cpp
@@ -313,6 +313,7 @@ APValue::APValue(const APValue &RHS)
switch (RHS.getKind()) {
case None:
case Indeterminate:
+ case Erroneous:
Kind = RHS.getKind();
break;
case Int:
@@ -445,6 +446,7 @@ bool APValue::needsCleanup() const {
switch (getKind()) {
case None:
case Indeterminate:
+ case Erroneous:
case AddrLabelDiff:
return false;
case Struct:
@@ -505,6 +507,7 @@ void APValue::Profile(llvm::FoldingSetNodeID &ID) const {
switch (Kind) {
case None:
case Indeterminate:
+ case Erroneous:
return;
case AddrLabelDiff:
@@ -740,6 +743,7 @@ void APValue::printPretty(raw_ostream &Out, const PrintingPolicy &Policy,
return;
case APValue::Erroneous:
Out << "<erroneous>";
+ return;
case APValue::Int:
if (Ty->isBooleanType())
Out << (getInt().getBoolValue() ? "true" : "false");
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index d9b434112abd2..9afdc888730f9 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1833,10 +1833,10 @@ bool CodeGenFunction::isTrivialInitializer(const Expr *Init) {
return false;
}
-void CodeGenFunction::emitZeroOrPatternForAutoVarInit(QualType type,
- const VarDecl &D,
- Address Loc) {
- auto trivialAutoVarInit = getContext().getLangOpts().getTrivialAutoVarInit();
+void CodeGenFunction::emitZeroOrPatternForAutoVarInit(
+ QualType type, const VarDecl &D, Address Loc,
+ LangOptions::TrivialAutoVarInitKind Kind) {
+ auto trivialAutoVarInit = Kind;
auto trivialAutoVarInitMaxSize =
getContext().getLangOpts().TrivialAutoVarInitMaxSize;
CharUnits Size = getContext().getTypeSizeInChars(type);
@@ -1999,13 +1999,23 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
// Note: constexpr already initializes everything correctly.
// C++26 [[indeterminate]] attribute opts out of erroneous initialization,
// restoring indeterminate (undefined) behavior.
+ const bool OptsOutOfInit =
+ D.isConstexpr() || D.getAttr<UninitializedAttr>() ||
+ D.hasAttr<IndeterminateAttr>() ||
+ hasNoTrivialAutoVarInitAttr(type->getAsTagDecl()) ||
+ hasNoTrivialAutoVarInitAttr(CurFuncDecl);
LangOptions::TrivialAutoVarInitKind trivialAutoVarInit =
- ((D.isConstexpr() || D.getAttr<UninitializedAttr>() ||
- D.hasAttr<IndeterminateAttr>() ||
- hasNoTrivialAutoVarInitAttr(type->getAsTagDecl()) ||
- hasNoTrivialAutoVarInitAttr(CurFuncDecl))
- ? LangOptions::TrivialAutoVarInitKind::Uninitialized
- : getContext().getLangOpts().getTrivialAutoVarInit());
+ OptsOutOfInit ? LangOptions::TrivialAutoVarInitKind::Uninitialized
+ : getContext().getLangOpts().getTrivialAutoVarInit();
+ // C++26 [basic.indet]: reading an erroneously initialized object has
+ // erroneous behavior with an implementation-defined (but fixed) value.
+ // LLVM's `undef` doesn't model this, so default to a fixed pattern when
+ // the user has not requested a specific auto-var-init kind.
+ if (!OptsOutOfInit &&
+ trivialAutoVarInit ==
+ LangOptions::TrivialAutoVarInitKind::Uninitialized &&
+ getContext().getLangOpts().CPlusPlus26)
+ trivialAutoVarInit = LangOptions::TrivialAutoVarInitKind::Pattern;
auto initializeWhatIsTechnicallyUninitialized = [&](Address Loc) {
if (trivialAutoVarInit ==
@@ -2016,7 +2026,7 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
if (emission.IsEscapingByRef && !locIsByrefHeader)
Loc = emitBlockByrefAddress(Loc, &D, /*follow=*/false);
- return emitZeroOrPatternForAutoVarInit(type, D, Loc);
+ return emitZeroOrPatternForAutoVarInit(type, D, Loc, trivialAutoVarInit);
};
if (isTrivialInitializer(Init))
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index f06c216e0c746..ea4382c36ba61 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -5514,8 +5514,9 @@ class CodeGenFunction : public CodeGenTypeCache {
unsigned Type,
llvm::IntegerType *ResType);
- void emitZeroOrPatternForAutoVarInit(QualType type, const VarDecl &D,
- Address Loc);
+ void
+ emitZeroOrPatternForAutoVarInit(QualType type, const VarDecl &D, Address Loc,
+ LangOptions::TrivialAutoVarInitKind Kind);
public:
enum class EvaluationOrder {
diff --git a/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp b/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
index af8d0888d2959..0d2fc2caa9594 100644
--- a/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
+++ b/clang/test/CodeGenCXX/cxx26-indeterminate-attribute.cpp
@@ -28,11 +28,12 @@ void test_indeterminate_local_var() {
used(x);
}
-// Without [[indeterminate]], -ftrivial-auto-var-init should apply normally.
-// Default (no flag): no store.
+// Without [[indeterminate]], C++26 erroneous initialization applies:
+// the alloca is filled with a fixed pattern so reads have a well-defined
+// (if implementation-defined) value, rather than LLVM `undef`.
// CXX26-LABEL: @test_normal_local_var(
// CXX26: %y = alloca i32
-// CXX26-NOT: store
+// CXX26: store i32 -1431655766, ptr %y
// CXX26: call void @_Z4usedIiEvRT_(
// ZERO-LABEL: @test_normal_local_var(
// ZERO: %y = alloca i32
diff --git a/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp b/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
index 025fb7b57a8da..0818016d1bcd9 100644
--- a/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
+++ b/clang/test/SemaCXX/cxx26-indeterminate-attribute.cpp
@@ -37,6 +37,11 @@ void test_class_type() {
[[indeterminate]] S s; // OK - member x has indeterminate value
}
+struct S2 {
+ [[indeterminate]] int x; // expected-warning {{'indeterminate' attribute only applies to local variables or function parameters}}
+ S2() {}
+};
+
// constexpr context should error on reading indeterminate value
constexpr int test_constexpr() {
[[indeterminate]] int x;
More information about the cfe-commits
mailing list