https://github.com/yronglin created https://github.com/llvm/llvm-project/pull/177614
This patch implement [P2795R5 'Erroneous behaviour for uninitialized reads'](https://wg21.link/p2795r5). In C++26, reading from an uninitialized automatic storage duration variable is no longer undefined behavior, since P2795R5, it's a defined erroneous behavior. - Add `[[indeterminate]]` standard attribute to opt-out of erroneous initialization (restore UB) - Add `APValue::Erroneous` to track erroneous values in constant expression evaluation - Reject erroneous values in constant expressions (same as indeterminate) - Add `-Werroneous-behavior` diagnostic group for runtime warnings - Support `[[indeterminate]]` on local variables and function parameters >From ee0a627a063e4d5aee16f2d1729edf77d96e1c18 Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" <[email protected]> Date: Sat, 24 Jan 2026 00:53:21 +0800 Subject: [PATCH] [C++26][clang] Implement P2795R5 'Erroneous behaviour for uninitialized reads' Signed-off-by: Wang, Yihan <[email protected]> --- 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..edf1452e25717 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 (P2795R5) +that can be applied to the definition of a block 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@+1 {{'indeterminate' attribute only applies to local variables or function parameters}} +[[indeterminate]] int global_var; + +void test_static() { + // expected-warning@+1 {{'indeterminate' attribute only applies to local variables or function parameters}} + [[indeterminate]] static int x; + // expected-warning@+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@-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> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
