https://github.com/filaka771 updated https://github.com/llvm/llvm-project/pull/226775
>From 15c92c19426f06b3b1a7a85015637380a0ca0ed5 Mon Sep 17 00:00:00 2001 From: Alex Filak <[email protected]> Date: Fri, 18 Sep 2026 22:56:35 +0300 Subject: [PATCH 1/3] [clang][Sema] Avoid -Wconstant-conversion in unselected conditional operands --- clang/include/clang/Sema/Sema.h | 5 +- clang/lib/Sema/SemaChecking.cpp | 186 +++++++++++++++++++++----------- 2 files changed, 128 insertions(+), 63 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index eb3b908351448d..7444661182266c 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2923,9 +2923,12 @@ class Sema final : public SemaBase { /// since all 1s are not contiguous. bool ValueIsRunOfOnes(CallExpr *TheCall, unsigned ArgNum); + /// Check the implicit conversion of E to T. IsKnownUnreachable indicates that + /// E is within an unselected operand of a constant conditional expression. void CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC, bool *ICContext = nullptr, - bool IsListInit = false); + bool IsListInit = false, + bool IsKnownUnreachable = false); /// Check for overflow behavior type related implicit conversion diagnostics. /// Returns true if OBT-related diagnostic was issued, false otherwise. diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 687af75c1496be..140b0be3b4448a 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -12365,7 +12365,8 @@ static bool IsSameFloatAfterCast(const APValue &value, } static void AnalyzeImplicitConversions(Sema &S, Expr *E, SourceLocation CC, - bool IsListInit = false); + bool IsListInit = false, + bool IsKnownUnreachable = false); static bool IsEnumConstOrFromMacro(Sema &S, const Expr *E) { // Suppress cases where we are comparing against an enum constant. @@ -12700,26 +12701,30 @@ static bool CheckTautologicalComparison(Sema &S, BinaryOperator *E, /// Analyze the operands of the given comparison. Implements the /// fallback case from AnalyzeComparison. -static void AnalyzeImpConvsInComparison(Sema &S, BinaryOperator *E) { - AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc()); - AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc()); +static void AnalyzeImpConvsInComparison(Sema &S, BinaryOperator *E, + bool IsKnownUnreachable) { + AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc(), + /*IsListInit=*/false, IsKnownUnreachable); + AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc(), + /*IsListInit=*/false, IsKnownUnreachable); } /// Implements -Wsign-compare. /// /// \param E the binary operator to check for warnings -static void AnalyzeComparison(Sema &S, BinaryOperator *E) { +static void AnalyzeComparison(Sema &S, BinaryOperator *E, + bool IsKnownUnreachable) { // The type the comparison is being performed in. QualType T = E->getLHS()->getType(); // Only analyze comparison operators where both sides have been converted to // the same type. if (!S.Context.hasSameUnqualifiedType(T, E->getRHS()->getType())) - return AnalyzeImpConvsInComparison(S, E); + return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable); // Don't analyze value-dependent comparisons directly. if (E->isValueDependent()) - return AnalyzeImpConvsInComparison(S, E); + return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable); Expr *LHS = E->getLHS(); Expr *RHS = E->getRHS(); @@ -12732,7 +12737,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) { // We don't care about expressions whose result is a constant. if (RHSValue && LHSValue) - return AnalyzeImpConvsInComparison(S, E); + return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable); // We only care about expressions where just one side is literal if ((bool)RHSValue ^ (bool)LHSValue) { @@ -12745,7 +12750,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) { // Check whether an integer constant comparison results in a value // of 'true' or 'false'. if (CheckTautologicalComparison(S, E, Const, Other, Value, RhsConstant)) - return AnalyzeImpConvsInComparison(S, E); + return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable); } } @@ -12753,7 +12758,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) { // We don't do anything special if this isn't an unsigned integral // comparison: we're only interested in integral comparisons, and // signed comparisons only happen in cases we don't care to warn about. - return AnalyzeImpConvsInComparison(S, E); + return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable); } LHS = LHS->IgnoreParenImpCasts(); @@ -12781,7 +12786,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) { signedOperand = RHS; unsignedOperand = LHS; } else { - return AnalyzeImpConvsInComparison(S, E); + return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable); } // Otherwise, calculate the effective range of the signed operand. @@ -12793,8 +12798,10 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) { // Go ahead and analyze implicit conversions in the operands. Note // that we skip the implicit conversions on both sides. - AnalyzeImplicitConversions(S, LHS, E->getOperatorLoc()); - AnalyzeImplicitConversions(S, RHS, E->getOperatorLoc()); + AnalyzeImplicitConversions(S, LHS, E->getOperatorLoc(), /*IsListInit=*/false, + IsKnownUnreachable); + AnalyzeImplicitConversions(S, RHS, E->getOperatorLoc(), /*IsListInit=*/false, + IsKnownUnreachable); // If the signed range is non-negative, -Wsign-compare won't fire. if (signedRange->NonNegative) @@ -12988,9 +12995,11 @@ static bool AnalyzeBitFieldAssignment(Sema &S, FieldDecl *Bitfield, Expr *Init, /// Analyze the given simple or compound assignment for warning-worthy /// operations. -static void AnalyzeAssignment(Sema &S, BinaryOperator *E) { +static void AnalyzeAssignment(Sema &S, BinaryOperator *E, + bool IsKnownUnreachable) { // Just recurse on the LHS. - AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc()); + AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc(), + /*IsListInit=*/false, IsKnownUnreachable); // We want to recurse on the RHS as normal unless we're assigning to // a bitfield. @@ -12998,8 +13007,9 @@ static void AnalyzeAssignment(Sema &S, BinaryOperator *E) { if (AnalyzeBitFieldAssignment(S, Bitfield, E->getRHS(), E->getOperatorLoc())) { // Recurse, ignoring any implicit conversions on the RHS. - return AnalyzeImplicitConversions(S, E->getRHS()->IgnoreParenImpCasts(), - E->getOperatorLoc()); + return AnalyzeImplicitConversions( + S, E->getRHS()->IgnoreParenImpCasts(), E->getOperatorLoc(), + /*IsListInit=*/false, IsKnownUnreachable); } } @@ -13008,7 +13018,8 @@ static void AnalyzeAssignment(Sema &S, BinaryOperator *E) { llvm::SaveAndRestore OBTAssignmentContext( S.InOverflowBehaviorAssignmentContext, true); - AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc()); + AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc(), + /*IsListInit=*/false, IsKnownUnreachable); // Diagnose implicitly sequentially-consistent atomic assignment. if (E->getLHS()->getType()->isAtomicType()) @@ -13160,12 +13171,15 @@ static void DiagnoseFloatingImpCast(Sema &S, const Expr *E, QualType T, /// Analyze the given compound assignment for the possible losing of /// floating-point precision. -static void AnalyzeCompoundAssignment(Sema &S, BinaryOperator *E) { +static void AnalyzeCompoundAssignment(Sema &S, BinaryOperator *E, + bool IsKnownUnreachable) { assert(isa<CompoundAssignOperator>(E) && "Must be compound assignment operation"); // Recurse on the LHS and RHS in here - AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc()); - AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc()); + AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc(), + /*IsListInit=*/false, IsKnownUnreachable); + AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc(), + /*IsListInit=*/false, IsKnownUnreachable); if (E->getLHS()->getType()->isAtomicType()) S.Diag(E->getOperatorLoc(), diag::warn_atomic_implicit_seq_cst); @@ -13442,7 +13456,8 @@ bool Sema::DiscardingCFIUncheckedCallee(QualType From, QualType To) const { } void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC, - bool *ICContext, bool IsListInit) { + bool *ICContext, bool IsListInit, + bool IsKnownUnreachable) { if (E->isTypeDependent() || E->isValueDependent()) return; const Type *Source = Context.getCanonicalType(E->getType()).getTypePtr(); @@ -13879,11 +13894,12 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC, std::string PrettySourceValue = toString(Value, 10); std::string PrettyTargetValue = PrettyPrintInRange(Value, TargetRange); - DiagRuntimeBehavior(E->getExprLoc(), E, - PDiag(diag::warn_impcast_integer_precision_constant) - << PrettySourceValue << PrettyTargetValue - << E->getType() << T << E->getSourceRange() - << SourceRange(CC)); + if (!IsKnownUnreachable) + DiagRuntimeBehavior(E->getExprLoc(), E, + PDiag(diag::warn_impcast_integer_precision_constant) + << PrettySourceValue << PrettyTargetValue + << E->getType() << T << E->getSourceRange() + << SourceRange(CC)); return; } @@ -13932,10 +13948,12 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC, std::string PrettySourceValue = toString(Value, 10); std::string PrettyTargetValue = PrettyPrintInRange(Value, TargetRange); - Diag(E->getExprLoc(), - PDiag(diag::warn_impcast_integer_precision_constant) - << PrettySourceValue << PrettyTargetValue << E->getType() << T - << E->getSourceRange() << SourceRange(CC)); + if (!IsKnownUnreachable && !isUnevaluatedContext() && + !currentEvaluationContext().isDiscardedStatementContext()) + Diag(E->getExprLoc(), + PDiag(diag::warn_impcast_integer_precision_constant) + << PrettySourceValue << PrettyTargetValue << E->getType() + << T << E->getSourceRange() << SourceRange(CC)); return; } } @@ -13997,34 +14015,65 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC, } static void CheckConditionalOperator(Sema &S, AbstractConditionalOperator *E, - SourceLocation CC, QualType T); + SourceLocation CC, QualType T, + bool IsKnownUnreachable); static void CheckConditionalOperand(Sema &S, Expr *E, QualType T, - SourceLocation CC, bool &ICContext) { + SourceLocation CC, bool &ICContext, + bool IsKnownUnreachable) { E = E->IgnoreParenImpCasts(); // Diagnose incomplete type for second or third operand in C. if (!S.getLangOpts().CPlusPlus && E->getType()->isRecordType()) S.RequireCompleteExprType(E, diag::err_incomplete_type); if (auto *CO = dyn_cast<AbstractConditionalOperator>(E)) - return CheckConditionalOperator(S, CO, CC, T); + return CheckConditionalOperator(S, CO, CC, T, IsKnownUnreachable); - AnalyzeImplicitConversions(S, E, CC); + AnalyzeImplicitConversions(S, E, CC, /*IsListInit=*/false, + IsKnownUnreachable); if (E->getType() != T) - return S.CheckImplicitConversion(E, T, CC, &ICContext); + return S.CheckImplicitConversion(E, T, CC, &ICContext, + /*IsListInit=*/false, IsKnownUnreachable); } static void CheckConditionalOperator(Sema &S, AbstractConditionalOperator *E, - SourceLocation CC, QualType T) { - AnalyzeImplicitConversions(S, E->getCond(), E->getQuestionLoc()); + SourceLocation CC, QualType T, + bool IsKnownUnreachable) { + AnalyzeImplicitConversions(S, E->getCond(), E->getQuestionLoc(), + /*IsListInit=*/false, IsKnownUnreachable); + + bool CondValue; + bool CondIsKnown = E->getCond()->EvaluateAsBooleanCondition( + CondValue, S.Context, S.isConstantEvaluatedContext()); + if (CondIsKnown && !S.isConstantEvaluatedContext()) { + // The condition may also be evaluated at compile time, for example in a + // constexpr function. Only prune an operand if both evaluations agree; + // __builtin_is_constant_evaluated() can produce different results. + bool ConstantCondValue; + CondIsKnown = + E->getCond()->EvaluateAsBooleanCondition(ConstantCondValue, S.Context, + /*InConstantContext=*/true) && + ConstantCondValue == CondValue; + } Expr *TrueExpr = E->getTrueExpr(); - if (auto *BCO = dyn_cast<BinaryConditionalOperator>(E)) + bool TrueIsKnownUnreachable = + IsKnownUnreachable || (CondIsKnown && !CondValue); + bool FalseIsKnownUnreachable = + IsKnownUnreachable || (CondIsKnown && CondValue); + + if (auto *BCO = dyn_cast<BinaryConditionalOperator>(E)) { + // The common expression supplies the condition as well as the true value, + // so its subexpressions are evaluated even when the condition is false. TrueExpr = BCO->getCommon(); + TrueIsKnownUnreachable = IsKnownUnreachable; + } bool Suspicious = false; - CheckConditionalOperand(S, TrueExpr, T, CC, Suspicious); - CheckConditionalOperand(S, E->getFalseExpr(), T, CC, Suspicious); + CheckConditionalOperand(S, TrueExpr, T, CC, Suspicious, + TrueIsKnownUnreachable); + CheckConditionalOperand(S, E->getFalseExpr(), T, CC, Suspicious, + FalseIsKnownUnreachable); if (T->isBooleanType()) DiagnoseIntInBoolContext(S, E); @@ -14043,10 +14092,12 @@ static void CheckConditionalOperator(Sema &S, AbstractConditionalOperator *E, Suspicious = false; S.CheckImplicitConversion(TrueExpr->IgnoreParenImpCasts(), E->getType(), CC, - &Suspicious); + &Suspicious, /*IsListInit=*/false, + TrueIsKnownUnreachable); if (!Suspicious) S.CheckImplicitConversion(E->getFalseExpr()->IgnoreParenImpCasts(), - E->getType(), CC, &Suspicious); + E->getType(), CC, &Suspicious, + /*IsListInit=*/false, FalseIsKnownUnreachable); } /// Check conversion of given expression to boolean. @@ -14067,18 +14118,22 @@ struct AnalyzeImplicitConversionsWorkItem { Expr *E; SourceLocation CC; bool IsListInit; + // Track unselected conditional operands without skipping other diagnostics + // that do not depend on whether the expression is evaluated. + bool IsKnownUnreachable; }; } static void CheckCommaOperand( Sema &S, Expr *E, QualType T, SourceLocation CC, - bool ExtraCheckForImplicitConversion, + bool ExtraCheckForImplicitConversion, bool IsKnownUnreachable, llvm::SmallVectorImpl<AnalyzeImplicitConversionsWorkItem> &WorkList) { E = E->IgnoreParenImpCasts(); - WorkList.push_back({E, CC, false}); + WorkList.push_back({E, CC, false, IsKnownUnreachable}); if (ExtraCheckForImplicitConversion && E->getType() != T) - S.CheckImplicitConversion(E, T, CC); + S.CheckImplicitConversion(E, T, CC, /*ICContext=*/nullptr, + /*IsListInit=*/false, IsKnownUnreachable); } /// Data recursive variant of AnalyzeImplicitConversions. Subexpressions @@ -14156,9 +14211,11 @@ static void AnalyzeImplicitConversions( /// how CheckConditionalOperand behaves; it's as-if the correct operand /// were directly used for the implicit conversion check. CheckCommaOperand(S, BO->getLHS(), T, BO->getOperatorLoc(), - /*ExtraCheckForImplicitConversion=*/false, WorkList); + /*ExtraCheckForImplicitConversion=*/false, + Item.IsKnownUnreachable, WorkList); CheckCommaOperand(S, BO->getRHS(), T, BO->getOperatorLoc(), - /*ExtraCheckForImplicitConversion=*/true, WorkList); + /*ExtraCheckForImplicitConversion=*/true, + Item.IsKnownUnreachable, WorkList); return; } } @@ -14166,7 +14223,7 @@ static void AnalyzeImplicitConversions( // For conditional operators, we analyze the arguments as if they // were being fed directly into the output. if (auto *CO = dyn_cast<AbstractConditionalOperator>(SourceExpr)) { - CheckConditionalOperator(S, CO, CC, T); + CheckConditionalOperator(S, CO, CC, T, Item.IsKnownUnreachable); return; } @@ -14178,7 +14235,8 @@ static void AnalyzeImplicitConversions( // The non-canonical typecheck is just an optimization; // CheckImplicitConversion will filter out dead implicit conversions. if (SourceExpr->getType() != T) - S.CheckImplicitConversion(SourceExpr, T, CC, nullptr, IsListInit); + S.CheckImplicitConversion(SourceExpr, T, CC, nullptr, IsListInit, + Item.IsKnownUnreachable); // Now continue drilling into this expression. @@ -14188,7 +14246,8 @@ static void AnalyzeImplicitConversions( // FIXME: Use a more uniform representation for this. for (auto *SE : POE->semantics()) if (auto *OVE = dyn_cast<OpaqueValueExpr>(SE)) - WorkList.push_back({OVE->getSourceExpr(), CC, IsListInit}); + WorkList.push_back( + {OVE->getSourceExpr(), CC, IsListInit, Item.IsKnownUnreachable}); } // Skip past explicit casts. @@ -14208,33 +14267,35 @@ static void AnalyzeImplicitConversions( E = E->IgnoreParenImpCasts(); if (!CE->getType()->isVoidType() && E->getType()->isAtomicType()) S.Diag(E->getBeginLoc(), diag::warn_atomic_implicit_seq_cst); - WorkList.push_back({E, CC, IsListInit}); + WorkList.push_back({E, CC, IsListInit, Item.IsKnownUnreachable}); return; } if (auto *OutArgE = dyn_cast<HLSLOutArgExpr>(E)) { - WorkList.push_back({OutArgE->getArgLValue(), CC, IsListInit}); + WorkList.push_back( + {OutArgE->getArgLValue(), CC, IsListInit, Item.IsKnownUnreachable}); // The base expression is only used to initialize the parameter for // arguments to `inout` parameters, so we only traverse down the base // expression for `inout` cases. if (OutArgE->isInOut()) - WorkList.push_back( - {OutArgE->getCastedTemporary()->getSourceExpr(), CC, IsListInit}); - WorkList.push_back({OutArgE->getWritebackCast(), CC, IsListInit}); + WorkList.push_back({OutArgE->getCastedTemporary()->getSourceExpr(), CC, + IsListInit, Item.IsKnownUnreachable}); + WorkList.push_back( + {OutArgE->getWritebackCast(), CC, IsListInit, Item.IsKnownUnreachable}); return; } if (BinaryOperator *BO = dyn_cast<BinaryOperator>(E)) { // Do a somewhat different check with comparison operators. if (BO->isComparisonOp()) - return AnalyzeComparison(S, BO); + return AnalyzeComparison(S, BO, Item.IsKnownUnreachable); // And with simple assignments. if (BO->getOpcode() == BO_Assign) - return AnalyzeAssignment(S, BO); + return AnalyzeAssignment(S, BO, Item.IsKnownUnreachable); // And with compound assignments. if (BO->isAssignmentOp()) - return AnalyzeCompoundAssignment(S, BO); + return AnalyzeCompoundAssignment(S, BO, Item.IsKnownUnreachable); } // These break the otherwise-useful invariant below. Fortunately, @@ -14267,7 +14328,7 @@ static void AnalyzeImplicitConversions( // Ignore checking string literals that are in logical and operators. // This is a common pattern for asserts. continue; - WorkList.push_back({ChildExpr, CC, IsListInit}); + WorkList.push_back({ChildExpr, CC, IsListInit, Item.IsKnownUnreachable}); } if (BO && BO->isLogicalOp()) { @@ -14295,9 +14356,10 @@ static void AnalyzeImplicitConversions( /// implicit conversions in the given expression. There are a couple /// of competing diagnostics here, -Wconversion and -Wsign-compare. static void AnalyzeImplicitConversions(Sema &S, Expr *OrigE, SourceLocation CC, - bool IsListInit/*= false*/) { + bool IsListInit /*= false*/, + bool IsKnownUnreachable /*= false*/) { llvm::SmallVector<AnalyzeImplicitConversionsWorkItem, 16> WorkList; - WorkList.push_back({OrigE, CC, IsListInit}); + WorkList.push_back({OrigE, CC, IsListInit, IsKnownUnreachable}); while (!WorkList.empty()) AnalyzeImplicitConversions(S, WorkList.pop_back_val(), WorkList); } >From a196ab3ef9518b80af0aa21f8d9304bfc076ff9b Mon Sep 17 00:00:00 2001 From: Alex Filak <[email protected]> Date: Fri, 18 Sep 2026 22:57:58 +0300 Subject: [PATCH 2/3] [clang][Sema] Add tests for unselected constant conversions --- clang/test/Sema/constant-conversion.c | 115 ++++++++++++ clang/test/SemaCXX/constant-conversion.cpp | 200 ++++++++++++++++++++- 2 files changed, 314 insertions(+), 1 deletion(-) diff --git a/clang/test/Sema/constant-conversion.c b/clang/test/Sema/constant-conversion.c index adba3315cc9b41..45aa15f574830d 100644 --- a/clang/test/Sema/constant-conversion.c +++ b/clang/test/Sema/constant-conversion.c @@ -69,6 +69,121 @@ void test5(void) { a.b = 100; } +// GH223923: Do not diagnose conversions in unselected operands of constant +// conditional expressions. +// Exercise both selected and unselected operands in array initializers. +static const signed char conditional_array[] __attribute__((unused)) = { + 0 ? 128 : 1, + 1 ? 1 : 128, + 1 ? 128 : 1, // expected-warning {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + 0 ? 1 : 128, // expected-warning {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + // An enclosing unselected operand also suppresses nested conversions. + 0 ? (1 ? 128 : 1) : 1, + 1 ? 1 : (0 ? 1 : 128), +}; + +void GH223923(int condition) { + signed char signed_char_dead = 0 ? 128 : -1; + signed char signed_char_live = 1 ? 128 : -1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char signed_char_maybe = condition ? 128 : -1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_operand_dead = 1 ? -1 : 128; + signed char false_operand_live = 0 ? -1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_operand_maybe = condition ? -1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + + short short_dead = 0 ? 32768 : 1; + short short_live = 1 ? 32768 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'short' changes value from 32768 to -32768}} + + int int_dead = 0 ? 2147483648LL : 1; + int int_live = 1 ? 2147483648LL : 1; + // expected-warning@-1 {{implicit conversion from 'long long' to 'int' changes value from 2147483648 to -2147483648}} + + signed char truncation_dead = 0 ? 256 : 1; + signed char truncation_live = 1 ? 256 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 256 to 0}} + + signed char sink; + int assignment_dead = 0 ? (sink = 128) : 1; + int assignment_live = 1 ? (sink = 128) : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + + signed char binary_dead = 1 ?: 128; + signed char binary_live = 0 ?: 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +#define CONDITION_FALSE 0 +#define CONDITION_TRUE 1 +#define IS_ZERO(x) ((x) == 0) + +void GH223923_macro_conditions(int condition) { + signed char object_true_dead = CONDITION_FALSE ? 128 : 1; + signed char object_false_dead = CONDITION_TRUE ? 1 : 128; + signed char object_true_live = CONDITION_TRUE ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char object_false_live = CONDITION_FALSE ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + + signed char function_true_dead = IS_ZERO(1) ? 128 : 1; + signed char function_false_dead = IS_ZERO(2 - 2) ? 1 : 128; + signed char function_true_live = IS_ZERO(0) ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char function_false_live = IS_ZERO(1 + 1) ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + + // A macro argument need not be constant: either operand can be selected. + signed char function_true_maybe = IS_ZERO(condition) ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char function_false_maybe = IS_ZERO(condition) ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +#undef IS_ZERO +#undef CONDITION_TRUE +#undef CONDITION_FALSE + +void GH223923_static_const_conditions(void) { + // These conditions can be folded even though the variables are not integer + // constant expressions in C. + static const int zero = 0; + static const int one = 1; + signed char true_dead = zero ? 128 : 1; + signed char false_dead = one ? 1 : 128; + signed char true_live = one ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_live = zero ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +void GH223923_generic_conditions(int i, double d) { + // Selection depends on the type, not the runtime value of the argument. + signed char true_dead = _Generic(d, int: 1, default: 0) ? 128 : 1; + signed char false_dead = _Generic(i, int: 1, default: 0) ? 1 : 128; + signed char true_live = _Generic(i, int: 1, default: 0) ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_live = _Generic(d, int: 1, default: 0) ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +void GH223923_algebraic_conditions(int x) { + // These conditions have fixed results for int, but conversion analysis + // remains conservative: neither operand is marked unreachable here. + signed char multiply_true = x * 0 ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char multiply_false = x * 0 ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char compare_true = x == x ? 128 : 1; + // expected-warning@-1 {{self-comparison always evaluates to true}} + // expected-warning@-2 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char compare_false = x == x ? 1 : 128; + // expected-warning@-1 {{self-comparison always evaluates to true}} + // expected-warning@-2 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + void test6(void) { // Test that unreachable code doesn't trigger the truncation warning. unsigned char x = 0 ? 65535 : 1; // no-warning diff --git a/clang/test/SemaCXX/constant-conversion.cpp b/clang/test/SemaCXX/constant-conversion.cpp index 9be8b139e79e22..fa9de748cff135 100644 --- a/clang/test/SemaCXX/constant-conversion.cpp +++ b/clang/test/SemaCXX/constant-conversion.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-apple-darwin %s +// RUN: %clang_cc1 -fsyntax-only -Wbool-operation -verify -triple x86_64-apple-darwin %s +// RUN: %clang_cc1 -fsyntax-only -Wbool-operation -verify -triple x86_64-apple-darwin -fexperimental-new-constant-interpreter %s // This file tests -Wconstant-conversion, a subcategory of -Wconversion // which is on by default. @@ -23,6 +24,203 @@ void too_big_for_char(int param) { char ok4 = true ? 0 : nines() + 1; } +namespace GH223923 { + +void static_const_conditions() { + static const int zero = 0; + static const int one = 1; + signed char true_dead = zero ? 128 : 1; + signed char false_dead = one ? 1 : 128; + signed char true_live = one ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_live = zero ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +constexpr bool is_zero(int value) { return value == 0; } + +void constexpr_conditions(int condition) { + // Test constexpr conditions independently of constexpr destinations. + constexpr bool never = false; + constexpr bool always = true; + signed char variable_true_dead = never ? 128 : 1; + signed char variable_false_dead = always ? 1 : 128; + signed char variable_true_live = always ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char variable_false_live = never ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + + signed char function_true_dead = is_zero(1) ? 128 : 1; + signed char function_false_dead = is_zero(2 - 2) ? 1 : 128; + signed char function_true_live = is_zero(0) ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char function_false_live = is_zero(1 + 1) ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + + // A constexpr function can also be called with a runtime argument. + signed char function_true_maybe = is_zero(condition) ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char function_false_maybe = is_zero(condition) ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +void local_initializers(bool condition) { + constexpr signed char constexpr_dead = false ? 128 : 1; + constexpr signed char constexpr_live = true ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}} + + short short_dead = false ? 32768 : 1; + int int_dead = false ? 2147483648LL : 1; + + short short_live = true ? 32768 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'short' changes value from 32768 to -32768}} + int int_maybe = condition ? 2147483648LL : 1; + // expected-warning@-1 {{implicit conversion from 'long long' to 'int' changes value from 2147483648 to -2147483648}} + + signed char nested_dead = true ? 1 : ((true ? 128 : 1) + 0); + signed char nested_live = false ? 1 : ((true ? 128 : 1) + 0); + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +constexpr signed char global_dead = false ? 128 : 1; +constexpr signed char global_live = true ? 128 : 1; +// expected-warning@-1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}} + +struct MemberInitializers { + signed char member_dead = false ? 128 : 1; + signed char member_live = true ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + + static const signed char static_dead = false ? 128 : 1; + static const signed char static_live = true ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}} +}; + +void default_arg_dead(signed char = false ? 128 : 1); +void default_arg_live(signed char = true ? 128 : 1); +// expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + +// The larger truncation path must also respect the unselected operand, even +// in contexts where there is no function body to analyze for reachability. +void default_arg_truncation_dead(signed char = false ? 256 : 1); +void default_arg_truncation_live(signed char = true ? 256 : 1); +// expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 256 to 0}} + +int consume(signed char); +signed char sink; +int total; + +// Preserve the outer conditional's reachability when recursing through +// calls, comparisons, assignments, and compound assignments. +void call_dead(int = false ? consume(128) : 1); +void call_live(int = true ? consume(128) : 1); +// expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +void comparison_dead(bool = false ? (consume(128) == 0) : false); +void comparison_live(bool = true ? (consume(128) == 0) : false); +// expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +void assignment_dead(int = false ? (sink = 128) : 1); +void assignment_live(int = true ? (sink = 128) : 1); +// expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +void compound_assignment_dead(int = false ? (total += consume(128)) : 1); +void compound_assignment_live(int = true ? (total += consume(128)) : 1); +// expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + +struct NestedInitializers { + bool comparison_dead = false ? (consume(128) == 0) : false; + int assignment_dead = false ? (sink = 128) : 1; + int compound_assignment_dead = false ? (total += consume(128)) : 1; + signed char truncation_dead = false ? 256 : 1; + signed char truncation_live = true ? 256 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 256 to 0}} +}; + +constexpr bool in_constant_evaluation() { + return __builtin_is_constant_evaluated(); +} + +// Runtime reachability must not suppress conversions that take place during +// constant evaluation. +constexpr signed char constant_global_live = in_constant_evaluation() ? 128 : 1; +// expected-warning@-1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}} +constexpr signed char constant_global_dead = in_constant_evaluation() ? 1 : 128; +static_assert(constant_global_live == -128, ""); +static_assert(constant_global_dead == 1, ""); + +void constant_initializers() { + constexpr signed char live = in_constant_evaluation() ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}} + constexpr signed char dead = in_constant_evaluation() ? 1 : 128; + static_assert(live == -128, ""); + static_assert(dead == 1, ""); +} + +// Neither operand can be ruled out when the same function can be evaluated +// both at runtime and at compile time. +constexpr signed char constant_function() { + return in_constant_evaluation() ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} +constexpr signed char runtime_function() { + return in_constant_evaluation() ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} +static_assert(constant_function() == -128, ""); +static_assert(runtime_function() == 1, ""); + +constexpr int zero(signed char) { return 0; } + +void binary_conditionals() { + // The common expression of GNU's x ?: y is evaluated even when it is zero. + int common_live = zero(128) ?: 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + int common_dead = false ? (zero(128) ?: 1) : 1; + + signed char true_live = 128 ?: 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_live = 0 ?: 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_dead = 1 ?: 128; +} + +template <bool Condition> void template_conditional() { + signed char value = Condition ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +template <bool Condition> void template_false_operand() { + signed char value = Condition ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +template <typename T> void sizeof_condition() { + signed char true_operand = sizeof(T) == 1 ? 128 : 1; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} + signed char false_operand = sizeof(T) == 1 ? 1 : 128; + // expected-warning@-1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}} +} + +void instantiate_conditionals() { + template_conditional<false>(); + template_conditional<true>(); + // expected-note@-1 {{in instantiation of function template specialization 'GH223923::template_conditional<true>' requested here}} + template_false_operand<true>(); + template_false_operand<false>(); + // expected-note@-1 {{in instantiation of function template specialization 'GH223923::template_false_operand<false>' requested here}} + sizeof_condition<char>(); + // expected-note@-1 {{in instantiation of function template specialization 'GH223923::sizeof_condition<char>' requested here}} + sizeof_condition<int>(); + // expected-note@-1 {{in instantiation of function template specialization 'GH223923::sizeof_condition<int>' requested here}} +} + +// Suppressing constant-conversion diagnostics must not skip other checks in +// unselected operands. +int unrelated_diagnostic(bool b) { + return false ? ~b : 0; + // expected-warning@-1 {{bitwise negation of a boolean expression}} +} + +} // namespace GH223923 + void test_bitfield() { struct S { int one_bit : 1; >From 21421b1f003793fc00059e5c13d20e1c3d553c5a Mon Sep 17 00:00:00 2001 From: Alex Filak <[email protected]> Date: Fri, 18 Sep 2026 22:59:21 +0300 Subject: [PATCH 3/3] [clang][docs] Document the conditional conversion warning fix --- clang/docs/ReleaseNotes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 3c6acf353f93f6..9a3962c6224139 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -503,6 +503,10 @@ features cannot lower the translation-unit ABI level; - Fixed a missing `-Wconstant-conversion` diagnostic for signed `char` arrays. +- Fixed false-positive `-Wconstant-conversion` diagnostics for values in + unreachable branches of conditional expressions, including signed `char` + array initializers. (#GH223923) + - Clang now diagnoses passing wrong vector type as a mask to `__builtin_shufflevector`. (#GH218132) - `-Wdelete-abstract-non-virtual-dtor` and `-Wdelete-non-abstract-non-virtual-dtor` _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
