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" <[email protected]> Date: Sat, 24 Jan 2026 00:53:21 +0800 Subject: [PATCH 1/2] [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..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@+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> >From b7b2ed34c137559c18f1469abd3cde5bd7cee48b Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Tue, 14 Apr 2026 08:13:51 -0700 Subject: [PATCH 2/2] [clang] Address review comments Signed-off-by: yronglin <[email protected]> --- 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@-1 {{type 'int' cannot be used prior to '::' because it has no members}} + L l(value); // #cwg1736-l + // since-cxx26-error@-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@-4 {{initialize the variable 'value' to silence this warning}} + // since-cxx11-error@-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@+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@-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"); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
