[clang] [llvm] [analyzer] Model concrete floating-point values (PR #214098)

John Paul Jepko via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 16 12:03:11 PDT 2026


https://github.com/jpjepko updated https://github.com/llvm/llvm-project/pull/214098

>From 83dc459c2e52fa0933807a61f40ba646d3cfb08c Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 9 Jul 2026 16:56:04 +0200
Subject: [PATCH 01/17] Add concrete float support in clangSA

Currently float literals resolve to UnknownSVal in the analyzer. This
commit introduces a ConcreteFloat SVal that wraps LLVM's APFloat to make
the analyzer aware of concrete floating-point values, and teaches it
some basic casting rule, like float -> int.
---
 .../Core/PathSensitive/APFloatPtr.h           | 52 ++++++++++++
 .../Core/PathSensitive/BasicValueFactory.h    |  6 ++
 .../Core/PathSensitive/SValBuilder.h          | 13 +++
 .../Core/PathSensitive/SVals.def              |  1 +
 .../StaticAnalyzer/Core/PathSensitive/SVals.h | 13 +++
 .../StaticAnalyzer/Core/BasicValueFactory.cpp | 20 +++++
 clang/lib/StaticAnalyzer/Core/Environment.cpp |  1 +
 clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp |  8 +-
 clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 35 ++++++++
 clang/lib/StaticAnalyzer/Core/SVals.cpp       | 39 ++++++++-
 .../Core/SimpleConstraintManager.cpp          |  6 ++
 .../StaticAnalyzer/Core/SimpleSValBuilder.cpp | 10 +++
 clang/test/Analysis/constant-float-literals.c | 83 +++++++++++++++++++
 clang/test/Analysis/operator-calls.cpp        |  6 +-
 14 files changed, 288 insertions(+), 5 deletions(-)
 create mode 100644 clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h
 create mode 100644 clang/test/Analysis/constant-float-literals.c

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h
new file mode 100644
index 0000000000000..69a8a66653756
--- /dev/null
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h
@@ -0,0 +1,52 @@
+//== APFloatPtr.h - Wrapper for APFloat objects owned separately -*- C++ -*--=//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_APFLOATPTR_H
+#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_APFLOATPTR_H
+
+#include "llvm/ADT/APFloat.h"
+#include "llvm/Support/Compiler.h"
+
+namespace clang::ento {
+
+/// A safe wrapper around APFloat objects allocated and owned by
+/// \c BasicValueFactory. This just wraps a common llvm::APFloat.
+class APFloatPtr {
+  using APFloat = llvm::APFloat;
+
+public:
+  APFloatPtr() = delete;
+  APFloatPtr(const APFloatPtr &) = default;
+  APFloatPtr &operator=(const APFloatPtr &) & = default;
+  ~APFloatPtr() = default;
+
+  /// You should not use this API.
+  /// If do, ensure that the \p Ptr is not going to dangle.
+  /// Prefer using \c BasicValueFactory::getFloatValue() to get an APFloatPtr
+  /// object.
+  static APFloatPtr unsafeConstructor(const APFloat *Ptr) {
+    return APFloatPtr(Ptr);
+  }
+
+  LLVM_ATTRIBUTE_RETURNS_NONNULL
+  const APFloat *get() const { return Ptr; }
+  /*implicit*/ operator const APFloat &() const { return *get(); }
+
+  const APFloat &operator*() const { return *Ptr; }
+  const APFloat *operator->() const { return Ptr; }
+
+private:
+  explicit APFloatPtr(const APFloat *Ptr) : Ptr(Ptr) {}
+
+  /// Owned by \c BasicValueFactory.
+  const APFloat *Ptr;
+};
+
+} // namespace clang::ento
+
+#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_APFLOATPTR_H
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h
index 38eaabf74dd34..65f6362f041fd 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h
@@ -18,6 +18,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/Type.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntPtr.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
@@ -114,11 +115,14 @@ class PointerToMemberData : public llvm::FoldingSetNode {
 class BasicValueFactory {
   using APSIntSetTy =
       llvm::FoldingSet<llvm::FoldingSetNodeWrapper<llvm::APSInt>>;
+  using APFloatSetTy =
+      llvm::FoldingSet<llvm::FoldingSetNodeWrapper<llvm::APFloat>>;
 
   ASTContext &Ctx;
   llvm::BumpPtrAllocator& BPAlloc;
 
   APSIntSetTy APSIntSet;
+  APFloatSetTy APFloatSet;
   void *PersistentSVals = nullptr;
   void *PersistentSValPairs = nullptr;
 
@@ -145,6 +149,8 @@ class BasicValueFactory {
   APSIntPtr getValue(const llvm::APInt &X, bool isUnsigned);
   APSIntPtr getValue(uint64_t X, QualType T);
 
+  APFloatPtr getFloatValue(const llvm::APFloat &X);
+
   /// Returns the type of the APSInt used to store values of the given QualType.
   APSIntType getAPSIntType(QualType T) const {
     // For the purposes of the analysis and constraints, we treat atomics
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
index a92acfea8f702..5c2675023993e 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
@@ -110,6 +110,11 @@ class SValBuilder {
   /// that value is returned. Otherwise, returns NULL.
   virtual const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal val) = 0;
 
+  /// If the SVal represents a concrete floating-point value, returns a pointer
+  /// to that value. Otherwise, returns NULL.
+  virtual const llvm::APFloat *getKnownFloatValue(ProgramStateRef state,
+                                                  SVal val) = 0;
+
   /// Tries to get the minimal possible (integer) value of a given SVal. This
   /// always returns the value of a ConcreteInt, but may return NULL if the
   /// value is symbolic and the constraint manager cannot provide a useful
@@ -275,6 +280,14 @@ class SValBuilder {
                      integer->getType()->isUnsignedIntegerOrEnumerationType()));
   }
 
+  nonloc::ConcreteFloat makeFloatVal(const FloatingLiteral *F) {
+    return nonloc::ConcreteFloat(BasicVals.getFloatValue(F->getValue()));
+  }
+
+  nonloc::ConcreteFloat makeFloatVal(const llvm::APFloat &F) {
+    return nonloc::ConcreteFloat(BasicVals.getFloatValue(F));
+  }
+
   nonloc::ConcreteInt makeBoolVal(const ObjCBoolLiteralExpr *boolean) {
     return makeTruthVal(boolean->getValue(), boolean->getType());
   }
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def
index 36d2425d155a9..1dbd375afe0fa 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def
@@ -58,6 +58,7 @@ ABSTRACT_SVAL(DefinedOrUnknownSVal, SVal)
       SVAL_RANGE(Loc, ConcreteInt, MemRegionVal)
     ABSTRACT_SVAL(NonLoc, DefinedSVal)
       NONLOC_SVAL(CompoundVal, NonLoc)
+      NONLOC_SVAL(ConcreteFloat, NonLoc)
       NONLOC_SVAL(ConcreteInt, NonLoc)
       NONLOC_SVAL(LazyCompoundVal, NonLoc)
       NONLOC_SVAL(LocAsInteger, NonLoc)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
index 0561a2b8d1d77..a6835c1303765 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
@@ -17,6 +17,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/Type.h"
 #include "clang/Basic/LLVM.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntPtr.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
 #include "llvm/ADT/APSInt.h"
@@ -302,6 +303,18 @@ class SymbolVal : public NonLoc {
   static bool classof(SVal V) { return V.getKind() == SymbolValKind; }
 };
 
+/// Value representing floating-point constant.
+class ConcreteFloat : public NonLoc {
+public:
+  explicit ConcreteFloat(APFloatPtr V) : NonLoc(ConcreteFloatKind, V.get()) {}
+
+  APFloatPtr getValue() const {
+    return APFloatPtr::unsafeConstructor(castDataAs<llvm::APFloat>());
+  }
+
+  static bool classof(SVal V) { return V.getKind() == ConcreteFloatKind; }
+};
+
 /// Value representing integer constant.
 class ConcreteInt : public NonLoc {
 public:
diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
index 8fd8727194310..b0a668f350ce4 100644
--- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
@@ -83,6 +83,9 @@ BasicValueFactory::~BasicValueFactory() {
   for (const auto &I : APSIntSet)
     I.getValue().~APSInt();
 
+  for (const auto &I : APFloatSet)
+    I.getValue().~APFloat();
+
   delete (PersistentSValsTy*) PersistentSVals;
   delete (PersistentSValPairsTy*) PersistentSValPairs;
 }
@@ -121,6 +124,23 @@ APSIntPtr BasicValueFactory::getValue(uint64_t X, QualType T) {
   return getValue(getAPSIntType(T).getValue(X));
 }
 
+APFloatPtr BasicValueFactory::getFloatValue(const llvm::APFloat &X) {
+  llvm::FoldingSetNodeID ID;
+  void *InsertPos;
+
+  using FoldNodeTy = llvm::FoldingSetNodeWrapper<llvm::APFloat>;
+
+  X.Profile(ID);
+  FoldNodeTy *P = APFloatSet.FindNodeOrInsertPos(ID, InsertPos);
+
+  if (!P) {
+    P = new (BPAlloc) FoldNodeTy(X);
+    APFloatSet.InsertNode(P, InsertPos);
+  }
+
+  return APFloatPtr::unsafeConstructor(&P->getValue());
+}
+
 const CompoundValData*
 BasicValueFactory::getCompoundValData(QualType T,
                                       llvm::ImmutableList<SVal> Vals) {
diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp
index 12f61c0416a13..911d28a464064 100644
--- a/clang/lib/StaticAnalyzer/Core/Environment.cpp
+++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp
@@ -95,6 +95,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry,
   case Stmt::CharacterLiteralClass:
   case Stmt::CXXBoolLiteralExprClass:
   case Stmt::CXXScalarValueInitExprClass:
+  case Stmt::FloatingLiteralClass:
   case Stmt::ImplicitValueInitExprClass:
   case Stmt::IntegerLiteralClass:
   case Stmt::ObjCBoolLiteralExprClass:
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index d89c80ca01754..9d69fb5178712 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -274,8 +274,12 @@ void ExprEngine::VisitCastExpr(const CastExpr *CastE, ExplodedNode *Pred,
 
       if (const MemRegion *MR = State->getSVal(Ex, SF).getAsRegion()) {
         SVal OrigV = State->getSVal(MR);
-        CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV),
-                                       CastE->getType(), Ex->getType());
+        // __builtin_bit_cast reinterprets raw bits. We cannot model this
+        // for floating-point values because evalCast performs a value
+        // conversion, not a bit reinterpretation.
+        if (!OrigV.getAs<nonloc::ConcreteFloat>())
+          CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV),
+                                         CastE->getType(), Ex->getType());
       }
       Dst.insert(Engine.makeNodeWithBinding(Node, CastE, CastedV));
     }
diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
index 38dd446f2f9e1..633edbcb17c5c 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -385,6 +385,9 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
   case Stmt::IntegerLiteralClass:
     return makeIntVal(cast<IntegerLiteral>(E));
 
+  case Stmt::FloatingLiteralClass:
+    return makeFloatVal(cast<FloatingLiteral>(E));
+
   case Stmt::ObjCBoolLiteralExprClass:
     return makeBoolVal(cast<ObjCBoolLiteralExpr>(E));
 
@@ -873,6 +876,38 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
     // Compound to whatever.
     return UnknownVal();
   }
+  SVal VisitConcreteFloat(nonloc::ConcreteFloat V) {
+    // Float to float.
+    if (CastTy->isRealFloatingType()) {
+      const llvm::fltSemantics &TargetSem =
+          VB.getContext().getFloatTypeSemantics(CastTy);
+      llvm::APFloat Value = *V.getValue();
+      bool LosesInfo = false;
+      Value.convert(TargetSem, llvm::APFloat::rmNearestTiesToEven, &LosesInfo);
+      if (!LosesInfo)
+        return VB.makeFloatVal(Value);
+      return UnknownVal();
+    }
+
+    // Float to integer.
+    if (CastTy->isIntegralOrEnumerationType()) {
+      APSIntType ResultType = VB.getBasicValueFactory().getAPSIntType(CastTy);
+      llvm::APSInt Result = ResultType.getValue(0);
+      llvm::APFloat Value = *V.getValue();
+      bool IsExact;
+      llvm::APFloat::opStatus Status =
+          Value.convertToInteger(Result, llvm::APFloat::rmTowardZero, &IsExact);
+      if (Status == llvm::APFloat::opOK || Status == llvm::APFloat::opInexact)
+        return VB.makeIntVal(Result);
+      return UnknownVal();
+    }
+
+    // Float to bool.
+    if (CastTy->isBooleanType())
+      return VB.makeTruthVal(!V.getValue()->isZero(), CastTy);
+
+    return UnknownVal();
+  }
   SVal VisitConcreteInt(nonloc::ConcreteInt V) {
     auto CastedValue = [V, this]() {
       llvm::APSInt Value = V.getValue();
diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp
index 483e62d4a9a7e..32c8968aea395 100644
--- a/clang/lib/StaticAnalyzer/Core/SVals.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp
@@ -147,6 +147,20 @@ class TypeRetrievingVisitor
       return Context.BoolTy;
     return Context.getIntTypeForBitwidth(Value.getBitWidth(), Value.isSigned());
   }
+  QualType VisitConcreteFloat(nonloc::ConcreteFloat CF) {
+    const llvm::fltSemantics &Sem = CF.getValue()->getSemantics();
+    if (&Sem == &llvm::APFloat::IEEEsingle())
+      return Context.FloatTy;
+    if (&Sem == &llvm::APFloat::IEEEdouble())
+      return Context.DoubleTy;
+    if (&Sem == &llvm::APFloat::x87DoubleExtended())
+      return Context.LongDoubleTy;
+    if (&Sem == &llvm::APFloat::IEEEhalf())
+      return Context.Float16Ty;
+    if (&Sem == &llvm::APFloat::IEEEquad())
+      return Context.Float128Ty;
+    return QualType{};
+  }
   QualType VisitLocAsInteger(nonloc::LocAsInteger LI) {
     QualType NestedType = Visit(LI.getLoc());
     if (NestedType.isNull())
@@ -243,7 +257,8 @@ nonloc::PointerToMember::iterator nonloc::PointerToMember::end() const {
 //===----------------------------------------------------------------------===//
 
 bool SVal::isConstant() const {
-  return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>();
+  return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>() ||
+         getAs<nonloc::ConcreteFloat>();
 }
 
 bool SVal::isConstant(int I) const {
@@ -255,6 +270,8 @@ bool SVal::isConstant(int I) const {
 }
 
 bool SVal::isZeroConstant() const {
+  if (std::optional<nonloc::ConcreteFloat> FV = getAs<nonloc::ConcreteFloat>())
+    return FV->getValue()->isZero();
   return isConstant(0);
 }
 
@@ -312,6 +329,26 @@ void SVal::dumpToStream(raw_ostream &os) const {
 
 void NonLoc::dumpToStream(raw_ostream &os) const {
   switch (getKind()) {
+  case nonloc::ConcreteFloatKind: {
+    const llvm::APFloat &Value = *castAs<nonloc::ConcreteFloat>().getValue();
+    llvm::SmallString<16> Str;
+    Value.toString(Str);
+    os << Str << ' ';
+    const auto &Sem = Value.getSemantics();
+    if (&Sem == &llvm::APFloat::IEEEhalf())
+      os << "IEEEhalf";
+    else if (&Sem == &llvm::APFloat::IEEEsingle())
+      os << "IEEEsingle";
+    else if (&Sem == &llvm::APFloat::IEEEdouble())
+      os << "IEEEdouble";
+    else if (&Sem == &llvm::APFloat::IEEEquad())
+      os << "IEEEquad";
+    else if (&Sem == &llvm::APFloat::x87DoubleExtended())
+      os << "x87DoubleExtended";
+    else
+      os << "unknown";
+    break;
+  }
   case nonloc::ConcreteIntKind: {
     APSIntPtr Value = castAs<nonloc::ConcreteInt>().getValue();
     os << Value << ' ' << (Value->isSigned() ? 'S' : 'U')
diff --git a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp
index dd5e374f52a91..b5d3ef1366d1c 100644
--- a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp
@@ -73,6 +73,12 @@ ProgramStateRef SimpleConstraintManager::assumeAux(ProgramStateRef State,
     return assumeSym(State, Sym, Assumption);
   }
 
+  case nonloc::ConcreteFloatKind: {
+    bool b = !Cond.castAs<nonloc::ConcreteFloat>().getValue()->isZero();
+    bool isFeasible = b ? Assumption : !Assumption;
+    return isFeasible ? State : nullptr;
+  }
+
   case nonloc::ConcreteIntKind: {
     bool b = *Cond.castAs<nonloc::ConcreteInt>().getValue() != 0;
     bool isFeasible = b ? Assumption : !Assumption;
diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
index 7d154cbc840ac..ace03e0df7ac0 100644
--- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
@@ -81,6 +81,9 @@ class SimpleSValBuilder : public SValBuilder {
   /// (integer) value, that value is returned. Otherwise, returns NULL.
   const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal V) override;
 
+  const llvm::APFloat *getKnownFloatValue(ProgramStateRef state,
+                                          SVal V) override;
+
   /// Evaluates a given SVal by recursively evaluating and simplifying the
   /// children SVals, then returns its minimal possible (integer) value. If the
   /// constraint manager cannot provide a meaningful answer, this returns NULL.
@@ -1223,6 +1226,13 @@ const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state,
   return getConstValue(state, simplifySVal(state, V));
 }
 
+const llvm::APFloat *
+SimpleSValBuilder::getKnownFloatValue(ProgramStateRef state, SVal V) {
+  if (auto X = V.getAs<nonloc::ConcreteFloat>())
+    return X->getValue().get();
+  return nullptr;
+}
+
 const llvm::APSInt *SimpleSValBuilder::getMinValue(ProgramStateRef state,
                                                    SVal V) {
   V = simplifySVal(state, V);
diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c
new file mode 100644
index 0000000000000..88a4a800a163c
--- /dev/null
+++ b/clang/test/Analysis/constant-float-literals.c
@@ -0,0 +1,83 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify \
+// RUN:   -analyzer-config eagerly-assume=false %s
+
+void clang_analyzer_dump_float(float);
+void clang_analyzer_dump_double(double);
+void clang_analyzer_eval(int);
+
+//===----------------------------------------------------------------------===//
+// Floating-point literals are modeled as ConcreteFloat SVals.
+//===----------------------------------------------------------------------===//
+
+void testFloatLiterals(void) {
+  clang_analyzer_dump_float(0.0f);   // expected-warning{{0 IEEEsingle}}
+  clang_analyzer_dump_float(1.0f);   // expected-warning{{1 IEEEsingle}}
+  clang_analyzer_dump_float(3.14f);  // expected-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dump_double(0.0);   // expected-warning{{0 IEEEdouble}}
+  clang_analyzer_dump_double(1.0);   // expected-warning{{1 IEEEdouble}}
+  clang_analyzer_dump_double(3.14);  // expected-warning{{3.1400000000000001 IEEEdouble}}
+}
+
+//===----------------------------------------------------------------------===//
+// Variables assigned from literals retain the ConcreteFloat value.
+//===----------------------------------------------------------------------===//
+
+void testVariables(void) {
+  float f = 1.5f;
+  double d = 2.5;
+  clang_analyzer_dump_float(f);   // expected-warning{{1.5 IEEEsingle}}
+  clang_analyzer_dump_double(d);  // expected-warning{{2.5 IEEEdouble}}
+}
+
+//===----------------------------------------------------------------------===//
+// Float-to-integer casts (truncation).
+//===----------------------------------------------------------------------===//
+
+void testFloatToInt(void) {
+  float f = 1.9f;
+  double d = 2.7;
+  int i = (int)f;
+  int j = (int)d;
+  clang_analyzer_eval(i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(j == 2); // expected-warning{{TRUE}}
+}
+
+//===----------------------------------------------------------------------===//
+// Float-to-bool casts.
+//===----------------------------------------------------------------------===//
+
+void testFloatToBool(void) {
+  float zero = 0.0f;
+  float nonzero = 1.0f;
+  clang_analyzer_eval((int)((_Bool)zero) == 0);    // expected-warning{{TRUE}}
+  clang_analyzer_eval((int)((_Bool)nonzero) == 1); // expected-warning{{TRUE}}
+}
+
+//===----------------------------------------------------------------------===//
+// Float-to-float casts (precision change without loss).
+//===----------------------------------------------------------------------===//
+
+void testFloatUpcast(void) {
+  float f = 1.5f;
+  double d = f;
+  // 1.5 is exactly representable in both, so no loss.
+  clang_analyzer_dump_double(d); // expected-warning{{1.5 IEEEdouble}}
+}
+
+//===----------------------------------------------------------------------===//
+// Unknown float values (parameters, arithmetic results).
+//===----------------------------------------------------------------------===//
+
+void testUnknown(float f) {
+  clang_analyzer_dump_float(f);        // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(f + 1.0f); // expected-warning{{Unknown}}
+}
+
+//===----------------------------------------------------------------------===//
+// Division by zero detection with concrete floats.
+//===----------------------------------------------------------------------===//
+
+float testDivByZeroFloat(void) {
+  float x = 0.0f;
+  return 1.0f / x; // expected-warning{{Division by zero}}
+}
diff --git a/clang/test/Analysis/operator-calls.cpp b/clang/test/Analysis/operator-calls.cpp
index 57da7cdc16923..dde21f164cee8 100644
--- a/clang/test/Analysis/operator-calls.cpp
+++ b/clang/test/Analysis/operator-calls.cpp
@@ -69,15 +69,17 @@ namespace RValues {
     }
   };
 
+  float getUnknownFloat();
+
   SmallOpaque getSmallOpaque() {
     SmallOpaque obj;
-    obj.x = 1.0;
+    obj.x = getUnknownFloat();
     return obj;
   }
 
   LargeOpaque getLargeOpaque() {
     LargeOpaque obj = LargeOpaque();
-    obj.x[0] = 1.0;
+    obj.x[0] = getUnknownFloat();
     return obj;
   }
 

>From e41abcf515a24ece23e7b86ecfa834e68ff6956c Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 9 Jul 2026 21:54:52 +0200
Subject: [PATCH 02/17] Refactor and add target-specific tests

---
 clang/lib/StaticAnalyzer/Core/SVals.cpp       | 58 +++++++++------
 clang/test/Analysis/constant-float-literals.c | 71 ++++++++++++++-----
 2 files changed, 88 insertions(+), 41 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp
index 32c8968aea395..b13019d74e883 100644
--- a/clang/lib/StaticAnalyzer/Core/SVals.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp
@@ -34,6 +34,27 @@
 using namespace clang;
 using namespace ento;
 
+/// Returns a human-readable name for the most common floating-point semantics.
+/// Anything else is reported as "unknown."
+static StringRef getFloatSemanticsName(const llvm::fltSemantics &Sem) {
+  switch (llvm::APFloat::SemanticsToEnum(Sem)) {
+  case llvm::APFloat::S_IEEEhalf:
+    return "IEEEhalf";
+  case llvm::APFloat::S_BFloat:
+    return "BFloat";
+  case llvm::APFloat::S_IEEEsingle:
+    return "IEEEsingle";
+  case llvm::APFloat::S_IEEEdouble:
+    return "IEEEdouble";
+  case llvm::APFloat::S_IEEEquad:
+    return "IEEEquad";
+  case llvm::APFloat::S_x87DoubleExtended:
+    return "x87DoubleExtended";
+  default:
+    return "unknown";
+  }
+}
+
 //===----------------------------------------------------------------------===//
 // Symbol iteration within an SVal.
 //===----------------------------------------------------------------------===//
@@ -148,18 +169,22 @@ class TypeRetrievingVisitor
     return Context.getIntTypeForBitwidth(Value.getBitWidth(), Value.isSigned());
   }
   QualType VisitConcreteFloat(nonloc::ConcreteFloat CF) {
-    const llvm::fltSemantics &Sem = CF.getValue()->getSemantics();
-    if (&Sem == &llvm::APFloat::IEEEsingle())
+    switch (llvm::APFloat::SemanticsToEnum(CF.getValue()->getSemantics())) {
+    case llvm::APFloat::S_IEEEhalf:
+      return Context.Float16Ty;
+    case llvm::APFloat::S_BFloat:
+      return Context.BFloat16Ty;
+    case llvm::APFloat::S_IEEEsingle:
       return Context.FloatTy;
-    if (&Sem == &llvm::APFloat::IEEEdouble())
+    case llvm::APFloat::S_IEEEdouble:
       return Context.DoubleTy;
-    if (&Sem == &llvm::APFloat::x87DoubleExtended())
-      return Context.LongDoubleTy;
-    if (&Sem == &llvm::APFloat::IEEEhalf())
-      return Context.Float16Ty;
-    if (&Sem == &llvm::APFloat::IEEEquad())
+    case llvm::APFloat::S_IEEEquad:
       return Context.Float128Ty;
-    return QualType{};
+    case llvm::APFloat::S_x87DoubleExtended:
+      return Context.LongDoubleTy;
+    default:
+      return QualType{};
+    }
   }
   QualType VisitLocAsInteger(nonloc::LocAsInteger LI) {
     QualType NestedType = Visit(LI.getLoc());
@@ -333,20 +358,7 @@ void NonLoc::dumpToStream(raw_ostream &os) const {
     const llvm::APFloat &Value = *castAs<nonloc::ConcreteFloat>().getValue();
     llvm::SmallString<16> Str;
     Value.toString(Str);
-    os << Str << ' ';
-    const auto &Sem = Value.getSemantics();
-    if (&Sem == &llvm::APFloat::IEEEhalf())
-      os << "IEEEhalf";
-    else if (&Sem == &llvm::APFloat::IEEEsingle())
-      os << "IEEEsingle";
-    else if (&Sem == &llvm::APFloat::IEEEdouble())
-      os << "IEEEdouble";
-    else if (&Sem == &llvm::APFloat::IEEEquad())
-      os << "IEEEquad";
-    else if (&Sem == &llvm::APFloat::x87DoubleExtended())
-      os << "x87DoubleExtended";
-    else
-      os << "unknown";
+    os << Str << ' ' << getFloatSemanticsName(Value.getSemantics());
     break;
   }
   case nonloc::ConcreteIntKind: {
diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c
index 88a4a800a163c..f1405a187c6e2 100644
--- a/clang/test/Analysis/constant-float-literals.c
+++ b/clang/test/Analysis/constant-float-literals.c
@@ -1,8 +1,19 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify \
-// RUN:   -analyzer-config eagerly-assume=false %s
+// Semantics of long double differ depending on target, which is why we run on
+// multiple targets.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify=common,x87 %s
+// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify=common,quad %s
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify=common,ldbl64 %s
 
 void clang_analyzer_dump_float(float);
 void clang_analyzer_dump_double(double);
+void clang_analyzer_dump_longdouble(long double);
 void clang_analyzer_eval(int);
 
 //===----------------------------------------------------------------------===//
@@ -10,12 +21,24 @@ void clang_analyzer_eval(int);
 //===----------------------------------------------------------------------===//
 
 void testFloatLiterals(void) {
-  clang_analyzer_dump_float(0.0f);   // expected-warning{{0 IEEEsingle}}
-  clang_analyzer_dump_float(1.0f);   // expected-warning{{1 IEEEsingle}}
-  clang_analyzer_dump_float(3.14f);  // expected-warning{{3.1400001 IEEEsingle}}
-  clang_analyzer_dump_double(0.0);   // expected-warning{{0 IEEEdouble}}
-  clang_analyzer_dump_double(1.0);   // expected-warning{{1 IEEEdouble}}
-  clang_analyzer_dump_double(3.14);  // expected-warning{{3.1400000000000001 IEEEdouble}}
+  clang_analyzer_dump_float(0.0f);  // common-warning{{0 IEEEsingle}}
+  clang_analyzer_dump_float(1.0f);  // common-warning{{1 IEEEsingle}}
+  clang_analyzer_dump_float(3.14f); // common-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dump_double(0.0);  // common-warning{{0 IEEEdouble}}
+  clang_analyzer_dump_double(1.0);  // common-warning{{1 IEEEdouble}}
+  clang_analyzer_dump_double(3.14); // common-warning{{3.1400000000000001 IEEEdouble}}
+}
+
+//===----------------------------------------------------------------------===//
+// long double is modeled with the target's floating-point semantics.
+//===----------------------------------------------------------------------===//
+
+void testLongDoubleLiterals(void) {
+  // 0.0 and 1.0 are representable exactly in all formats so only semantic name
+  // differs on different targets.
+  clang_analyzer_dump_longdouble(0.0L); // x87-warning{{0 x87DoubleExtended}}
+quad-warning{{0 IEEEquad}} ldbl64-warning{{0 IEEEdouble}}
+  clang_analyzer_dump_longdouble(1.0L); // x87-warning{{1 x87DoubleExtended}} quad-warning{{1 IEEEquad}} ldbl64-warning{{1 IEEEdouble}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -25,8 +48,8 @@ void testFloatLiterals(void) {
 void testVariables(void) {
   float f = 1.5f;
   double d = 2.5;
-  clang_analyzer_dump_float(f);   // expected-warning{{1.5 IEEEsingle}}
-  clang_analyzer_dump_double(d);  // expected-warning{{2.5 IEEEdouble}}
+  clang_analyzer_dump_float(f);   // common-warning{{1.5 IEEEsingle}}
+  clang_analyzer_dump_double(d);  // common-warning{{2.5 IEEEdouble}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -38,8 +61,8 @@ void testFloatToInt(void) {
   double d = 2.7;
   int i = (int)f;
   int j = (int)d;
-  clang_analyzer_eval(i == 1); // expected-warning{{TRUE}}
-  clang_analyzer_eval(j == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(i == 1);  // common-warning{{TRUE}}
+  clang_analyzer_eval(j == 2);  // common-warning{{TRUE}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -49,8 +72,8 @@ void testFloatToInt(void) {
 void testFloatToBool(void) {
   float zero = 0.0f;
   float nonzero = 1.0f;
-  clang_analyzer_eval((int)((_Bool)zero) == 0);    // expected-warning{{TRUE}}
-  clang_analyzer_eval((int)((_Bool)nonzero) == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval((int)((_Bool)zero) == 0);     // common-warning{{TRUE}}
+  clang_analyzer_eval((int)((_Bool)nonzero) == 1);  // common-warning{{TRUE}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -61,7 +84,19 @@ void testFloatUpcast(void) {
   float f = 1.5f;
   double d = f;
   // 1.5 is exactly representable in both, so no loss.
-  clang_analyzer_dump_double(d); // expected-warning{{1.5 IEEEdouble}}
+  clang_analyzer_dump_double(d);  // common-warning{{1.5 IEEEdouble}}
+}
+
+//===----------------------------------------------------------------------===//
+// Float-to-float casts (inexact narrowing stays Unknown).
+//===----------------------------------------------------------------------===//
+
+void testFloatNarrowing(void) {
+  double d = 3.14;
+  float f = (float)d;
+  // 3.14 is not exactly representable in float, and rounding direction is
+  // implementation-defined, so we don't model here.
+  clang_analyzer_dump_float(f); // common-warning{{Unknown}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -69,8 +104,8 @@ void testFloatUpcast(void) {
 //===----------------------------------------------------------------------===//
 
 void testUnknown(float f) {
-  clang_analyzer_dump_float(f);        // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(f + 1.0f); // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(f);         // common-warning{{Unknown}}
+  clang_analyzer_dump_float(f + 1.0f);  // common-warning{{Unknown}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -79,5 +114,5 @@ void testUnknown(float f) {
 
 float testDivByZeroFloat(void) {
   float x = 0.0f;
-  return 1.0f / x; // expected-warning{{Division by zero}}
+  return 1.0f / x;  // common-warning{{Division by zero}}
 }

>From cccf17aee410371d5ce3501e298f6ca449052fc5 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 13 Jul 2026 17:18:55 +0200
Subject: [PATCH 03/17] Add modeling support for comparisons and arithmetic

---
 .../StaticAnalyzer/Core/SimpleSValBuilder.cpp | 72 ++++++++++++++++++-
 clang/test/Analysis/constant-float-literals.c | 42 ++++++++++-
 2 files changed, 110 insertions(+), 4 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
index ace03e0df7ac0..48aec507a85a9 100644
--- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
@@ -440,7 +440,9 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
     rhs = *simplifiedRhsAsNonLoc;
 
   // Handle trivial case where left-side and right-side are the same.
-  if (lhs == rhs)
+  // Deliberately exclude floating-point values since x - x isn't necessarily 0
+  // (e.g., inf - inf), and x == x is false when x is NaN.
+  if (lhs == rhs && !lhs.getAs<nonloc::ConcreteFloat>())
     switch (op) {
       default:
         break;
@@ -526,6 +528,74 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
           }
         }
     }
+    case nonloc::ConcreteFloatKind: {
+      // Only fold operations between concrete floats that have the same
+      // semantics; normally implicit casts are inserted in the AST so they
+      // match, but check anyway for robustness.
+      std::optional<nonloc::ConcreteFloat> RHSFloat =
+          rhs.getAs<nonloc::ConcreteFloat>();
+      if (!RHSFloat)
+        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+
+      const llvm::APFloat &L = *lhs.castAs<nonloc::ConcreteFloat>().getValue();
+      const llvm::APFloat &R = *RHSFloat->getValue();
+      if (&L.getSemantics() != &R.getSemantics())
+        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+
+      // We can model comparisons between floats since they are defined for
+      // every value regardless of rounding mode or excess precision.
+      llvm::APFloat::cmpResult Cmp = L.compare(R);
+      switch (op) {
+      case BO_EQ:
+        return makeTruthVal(Cmp == llvm::APFloat::cmpEqual, resultTy);
+      case BO_NE:
+        return makeTruthVal(Cmp != llvm::APFloat::cmpEqual, resultTy);
+      case BO_LT:
+        return makeTruthVal(Cmp == llvm::APFloat::cmpLessThan, resultTy);
+      case BO_GT:
+        return makeTruthVal(Cmp == llvm::APFloat::cmpGreaterThan, resultTy);
+      case BO_LE:
+        return makeTruthVal(Cmp == llvm::APFloat::cmpLessThan ||
+                                Cmp == llvm::APFloat::cmpEqual,
+                            resultTy);
+      case BO_GE:
+        return makeTruthVal(Cmp == llvm::APFloat::cmpGreaterThan ||
+                                Cmp == llvm::APFloat::cmpEqual,
+                            resultTy);
+      default:
+        break;
+      }
+
+      // We can model arithmetic (operators +, -, *, /) only when both operands
+      // are finite and result is exact (which needs no rounding). Inexact,
+      // non-finite, or exceptions (like overflow or div by zero) is unmodeled.
+      if (!L.isFinite() || !R.isFinite())
+        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+
+      llvm::APFloat Result = L;
+      llvm::APFloat::opStatus Status;
+      switch (op) {
+      case BO_Add:
+        Status = Result.add(R, llvm::APFloat::rmNearestTiesToEven);
+        break;
+      case BO_Sub:
+        Status = Result.subtract(R, llvm::APFloat::rmNearestTiesToEven);
+        break;
+      case BO_Mul:
+        Status = Result.multiply(R, llvm::APFloat::rmNearestTiesToEven);
+        break;
+      case BO_Div:
+        Status = Result.divide(R, llvm::APFloat::rmNearestTiesToEven);
+        break;
+      default:
+        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+      }
+
+      if (Status == llvm::APFloat::opOK)
+        return makeFloatVal(Result);
+
+      return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+    }
     case nonloc::ConcreteIntKind: {
       llvm::APSInt LHSValue = lhs.castAs<nonloc::ConcreteInt>().getValue();
 
diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c
index f1405a187c6e2..431adca6121e1 100644
--- a/clang/test/Analysis/constant-float-literals.c
+++ b/clang/test/Analysis/constant-float-literals.c
@@ -36,9 +36,14 @@ void testFloatLiterals(void) {
 void testLongDoubleLiterals(void) {
   // 0.0 and 1.0 are representable exactly in all formats so only semantic name
   // differs on different targets.
-  clang_analyzer_dump_longdouble(0.0L); // x87-warning{{0 x87DoubleExtended}}
-quad-warning{{0 IEEEquad}} ldbl64-warning{{0 IEEEdouble}}
-  clang_analyzer_dump_longdouble(1.0L); // x87-warning{{1 x87DoubleExtended}} quad-warning{{1 IEEEquad}} ldbl64-warning{{1 IEEEdouble}}
+  clang_analyzer_dump_longdouble(0.0L);
+  // x87-warning at -1{{0 x87DoubleExtended}}
+  // quad-warning at -2{{0 IEEEquad}}
+  // ldbl64-warning at -3{{0 IEEEdouble}}
+  clang_analyzer_dump_longdouble(1.0L);
+  // x87-warning at -1{{1 x87DoubleExtended}}
+  // quad-warning at -2{{1 IEEEquad}}
+  // ldbl64-warning at -3{{1 IEEEdouble}}
 }
 
 //===----------------------------------------------------------------------===//
@@ -108,6 +113,37 @@ void testUnknown(float f) {
   clang_analyzer_dump_float(f + 1.0f);  // common-warning{{Unknown}}
 }
 
+//===----------------------------------------------------------------------===//
+// Arithmetic between concrete floats is folded only when the result is exact.
+//===----------------------------------------------------------------------===//
+
+void testExactArithmetic(void) {
+  // All of these have exactly representable results, so they are independent
+  // of rounding mode and evaluation precision.
+  clang_analyzer_dump_float(1.0f + 2.0f);  // common-warning{{3 IEEEsingle}}
+  clang_analyzer_dump_float(5.0f - 1.5f);  // common-warning{{3.5 IEEEsingle}}
+  clang_analyzer_dump_float(1.5f * 2.0f);  // common-warning{{3 IEEEsingle}}
+  clang_analyzer_dump_float(3.0f / 4.0f);  // common-warning{{0.75 IEEEsingle}}
+  clang_analyzer_dump_double(0.5 + 0.25);  // common-warning{{0.75 IEEEdouble}}
+}
+
+void testInexactArithmetic(void) {
+  // 0.1f + 0.2f is not exactly representable in single precision; the rounded
+  // result depends on the rounding mode / evaluation precision, so we do not
+  // model it.
+  clang_analyzer_dump_float(0.1f + 0.2f);  // common-warning{{Unknown}}
+  // 1.0f / 3.0f is inexact.
+  clang_analyzer_dump_float(1.0f / 3.0f);  // common-warning{{Unknown}}
+}
+
+void testComparisons(void) {
+  clang_analyzer_eval(1.0f < 2.0f);   // common-warning{{TRUE}}
+  clang_analyzer_eval(2.0f < 1.0f);   // common-warning{{FALSE}}
+  clang_analyzer_eval(1.5 == 1.5);    // common-warning{{TRUE}}
+  clang_analyzer_eval(1.5 != 2.5);    // common-warning{{TRUE}}
+  clang_analyzer_eval(2.0f >= 2.0f);  // common-warning{{TRUE}}
+}
+
 //===----------------------------------------------------------------------===//
 // Division by zero detection with concrete floats.
 //===----------------------------------------------------------------------===//

>From 525eb1a69cc9df3668fff80e5ccac2ecbeb6ff36 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Tue, 14 Jul 2026 17:38:45 +0200
Subject: [PATCH 04/17] Add unary negation support

---
 clang/lib/StaticAnalyzer/Core/SValBuilder.cpp |  7 +++++++
 clang/test/Analysis/constant-float-literals.c | 13 +++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
index 633edbcb17c5c..10d1d799f1e16 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -469,6 +469,13 @@ SVal SValBuilder::evalMinus(NonLoc X) {
   switch (X.getKind()) {
   case nonloc::ConcreteIntKind:
     return makeIntVal(-X.castAs<nonloc::ConcreteInt>().getValue());
+  case nonloc::ConcreteFloatKind: {
+    // Negation only flips the sign bit and is well-defined for all
+    // floating-point values regardless of semantics, so model it.
+    llvm::APFloat Value = *X.castAs<nonloc::ConcreteFloat>().getValue();
+    Value.changeSign();
+    return makeFloatVal(Value);
+  }
   case nonloc::SymbolValKind:
     return makeNonLoc(X.castAs<nonloc::SymbolVal>().getSymbol(), UO_Minus,
                       X.getType(Context));
diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c
index 431adca6121e1..7e3dd38d78f07 100644
--- a/clang/test/Analysis/constant-float-literals.c
+++ b/clang/test/Analysis/constant-float-literals.c
@@ -144,6 +144,19 @@ void testComparisons(void) {
   clang_analyzer_eval(2.0f >= 2.0f);  // common-warning{{TRUE}}
 }
 
+//===----------------------------------------------------------------------===//
+// Unary negation is always exact (a sign-bit flip).
+//===----------------------------------------------------------------------===//
+
+void testNegation(void) {
+  float f = 1.5f;
+  double d = 2.5;
+  clang_analyzer_dump_float(-f);       // common-warning{{-1.5 IEEEsingle}}
+  clang_analyzer_dump_double(-d);      // common-warning{{-2.5 IEEEdouble}}
+  clang_analyzer_dump_float(-(-f));    // common-warning{{1.5 IEEEsingle}}
+  clang_analyzer_eval(-f < 0.0f);      // common-warning{{TRUE}}
+}
+
 //===----------------------------------------------------------------------===//
 // Division by zero detection with concrete floats.
 //===----------------------------------------------------------------------===//

>From d750c2394d761b4a711872edfce35fb76366d1af Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Wed, 15 Jul 2026 21:11:47 +0200
Subject: [PATCH 05/17] Add concrete float to analyzerExplain and relnotes

---
 clang/docs/ReleaseNotes.md                    |  6 +++++
 .../StaticAnalyzer/Checkers/SValExplainer.h   | 10 +++++++++
 clang/test/Analysis/constant-float-literals.c | 22 +++++++++++++++----
 clang/test/Analysis/explain-svals.c           |  7 ++++++
 4 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f37bcda179c95..39a35163f767e 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -871,6 +871,12 @@ features cannot lower the translation-unit ABI level;
 - The lock-order-reversal check in ``alpha.unix.PthreadLock`` is now disabled by default.
   It can be re-enabled with the ``WarnOnLockOrderReversal`` option.
 
+- The analyzer now models concrete floating-point values. Floating-point
+  literals, simple arithmetic operations, and casts between floating-point types
+  are tracked as concrete values instead of being treated as unknown. Only
+  results that are exact (independent of rounding mode and evaluation precision)
+  are modeled. Fixes #GH82910.
+
 #### Moved checkers
 
 The `alpha.cplusplus.UseAfterLifetimeEnd` checker was renamed to `alpha.core.UseAfterLifetimeEnd`.
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
index 6c1025ecc7f4d..ac4c65d575073 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
@@ -107,6 +107,16 @@ class SValExplainer : public FullSValVisitor<SValExplainer, std::string> {
     return Str;
   }
 
+  std::string VisitConcreteFloat(nonloc::ConcreteFloat V) {
+    const llvm::APFloat &F = *V.getValue();
+    std::string Str;
+    llvm::raw_string_ostream OS(Str);
+    llvm::SmallString<16> Buf;
+    F.toString(Buf);
+    OS << "concrete floating-point value '" << Buf << "'";
+    return Str;
+  }
+
   std::string VisitLazyCompoundVal(nonloc::LazyCompoundVal V) {
     return "lazily frozen compound value of " + Visit(V.getRegion());
   }
diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c
index 7e3dd38d78f07..09a57e55a42a6 100644
--- a/clang/test/Analysis/constant-float-literals.c
+++ b/clang/test/Analysis/constant-float-literals.c
@@ -151,10 +151,24 @@ void testComparisons(void) {
 void testNegation(void) {
   float f = 1.5f;
   double d = 2.5;
-  clang_analyzer_dump_float(-f);       // common-warning{{-1.5 IEEEsingle}}
-  clang_analyzer_dump_double(-d);      // common-warning{{-2.5 IEEEdouble}}
-  clang_analyzer_dump_float(-(-f));    // common-warning{{1.5 IEEEsingle}}
-  clang_analyzer_eval(-f < 0.0f);      // common-warning{{TRUE}}
+  clang_analyzer_dump_float(-f);    // common-warning{{-1.5 IEEEsingle}}
+  clang_analyzer_dump_double(-d);   // common-warning{{-2.5 IEEEdouble}}
+  clang_analyzer_dump_float(-(-f)); // common-warning{{1.5 IEEEsingle}}
+  clang_analyzer_eval(-f < 0.0f);   // common-warning{{TRUE}}
+}
+
+//===----------------------------------------------------------------------===//
+// Infinity from an overflowing literal is concrete, but arithmetic on it is
+// not folded and casting it to int is not modeled (both would depend on
+// IEC 60559 semantics / are undefined in C), while comparisons still work.
+//===----------------------------------------------------------------------===//
+
+void testInfinity(void) {
+  float big = 1e400f; // common-warning{{magnitude of floating-point constant too large}}
+  clang_analyzer_dump_float(big);             // common-warning{{+Inf IEEEsingle}}
+  clang_analyzer_dump_float(big + 1.0f);      // common-warning{{Unknown}}
+  clang_analyzer_eval(big > 1.0f);            // common-warning{{TRUE}}
+  clang_analyzer_dump_float((float)(int)big); // common-warning{{Unknown}}
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/test/Analysis/explain-svals.c b/clang/test/Analysis/explain-svals.c
index 4e095efbab777..e16f87fd04ff7 100644
--- a/clang/test/Analysis/explain-svals.c
+++ b/clang/test/Analysis/explain-svals.c
@@ -11,6 +11,8 @@ struct S {
 void clang_analyzer_explain_int(int);
 void clang_analyzer_explain_voidp(void *);
 void clang_analyzer_explain_S(struct S);
+void clang_analyzer_explain_float(float);
+void clang_analyzer_explain_double(double);
 
 int glob;
 
@@ -32,6 +34,11 @@ void test_3(int param) {
   clang_analyzer_explain_voidp(&param); // expected-warning-re{{{{^pointer to parameter 'param'$}}}}
 }
 
+void test_float(void) {
+  clang_analyzer_explain_float(1.5f); // expected-warning-re{{{{^concrete floating-point value '1.5'$}}}}
+  clang_analyzer_explain_double(2.5); // expected-warning-re{{{{^concrete floating-point value '2.5'$}}}}
+}
+
 void test_non_top_level(int param) {
   clang_analyzer_explain_voidp(&param); // expected-warning-re{{{{^pointer to parameter 'param'$}}}}
 }

>From 69916778ce55fc3f67b350f1962ad7fc990104eb Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Fri, 7 Aug 2026 19:41:11 +0200
Subject: [PATCH 06/17] Restrict ConcreteFloat to finite normal values and add
 int conversions

---
 clang/docs/ReleaseNotes.md                    |  12 +-
 .../Core/PathSensitive/SValBuilder.h          |  22 ++-
 .../Checkers/DivZeroChecker.cpp               |   5 +
 .../StaticAnalyzer/Core/BasicValueFactory.cpp |   3 +
 clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp |  28 ++-
 clang/lib/StaticAnalyzer/Core/SValBuilder.cpp |  60 ++++--
 clang/lib/StaticAnalyzer/Core/SVals.cpp       |   9 +-
 .../StaticAnalyzer/Core/SimpleSValBuilder.cpp |  29 +--
 clang/test/Analysis/constant-float-16bit.c    |  39 ++++
 .../Analysis/constant-float-32bit-double.c    |  25 +++
 clang/test/Analysis/constant-float-cxx.cpp    |  48 +++++
 clang/test/Analysis/constant-float-literals.c | 181 ------------------
 .../Analysis/constant-float-long-double.c     |  78 ++++++++
 clang/test/Analysis/constant-float.c          | 172 +++++++++++++++++
 clang/test/Analysis/inline.cpp                |   8 +-
 clang/test/Analysis/operator-calls.cpp        |   1 +
 16 files changed, 482 insertions(+), 238 deletions(-)
 create mode 100644 clang/test/Analysis/constant-float-16bit.c
 create mode 100644 clang/test/Analysis/constant-float-32bit-double.c
 create mode 100644 clang/test/Analysis/constant-float-cxx.cpp
 delete mode 100644 clang/test/Analysis/constant-float-literals.c
 create mode 100644 clang/test/Analysis/constant-float-long-double.c
 create mode 100644 clang/test/Analysis/constant-float.c

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 39a35163f767e..a1f81716f7c1b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -872,10 +872,14 @@ features cannot lower the translation-unit ABI level;
   It can be re-enabled with the ``WarnOnLockOrderReversal`` option.
 
 - The analyzer now models concrete floating-point values. Floating-point
-  literals, simple arithmetic operations, and casts between floating-point types
-  are tracked as concrete values instead of being treated as unknown. Only
-  results that are exact (independent of rounding mode and evaluation precision)
-  are modeled. Fixes #GH82910.
+  literals, simple arithmetic operations, and casts, including to and from
+  integer types, are tracked as concrete values instead of being treated as
+  unknown. Only results that are exact (independent of rounding mode and
+  evaluation precision) are modeled. Infinities and NaNs remain unknown, because
+  a NaN's bit pattern is non-deterministic. Subnormals remain unknown because
+  their semantics are controlled by factors the analyzer cannot see.
+  ``__ibm128`` is not modeled, since LLVM implements several of its operations
+  through an inaccurate fallback format. Fixes #GH82910.
 
 #### Moved checkers
 
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
index 5c2675023993e..70f7591ecf297 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
@@ -110,11 +110,6 @@ class SValBuilder {
   /// that value is returned. Otherwise, returns NULL.
   virtual const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal val) = 0;
 
-  /// If the SVal represents a concrete floating-point value, returns a pointer
-  /// to that value. Otherwise, returns NULL.
-  virtual const llvm::APFloat *getKnownFloatValue(ProgramStateRef state,
-                                                  SVal val) = 0;
-
   /// Tries to get the minimal possible (integer) value of a given SVal. This
   /// always returns the value of a ConcreteInt, but may return NULL if the
   /// value is symbolic and the constraint manager cannot provide a useful
@@ -280,6 +275,23 @@ class SValBuilder {
                      integer->getType()->isUnsignedIntegerOrEnumerationType()));
   }
 
+  /// Whether a nonloc::ConcreteFloat may hold \p V.
+  ///
+  /// Infinities and NaNs are not modeled: the semantics of which depend on
+  /// IEC 60559 conformance which is not readily available to the analyzer, so
+  /// we leave these as unknowns. NaNs we can never model, since LangRef
+  /// dictates their bit patterns are non-deterministic. Subnormals are not
+  /// modeled because their semantics depend on hardware and compiler denormal
+  /// modes which the analyzer cannot see. IBM double-double is also not modeled
+  /// because some of its operations are inaccurately emulated.
+  static bool isModeledFloatValue(const llvm::APFloat &V) {
+    return V.isFinite() && !V.isDenormal() &&
+           llvm::APFloat::SemanticsToEnum(V.getSemantics()) !=
+               llvm::APFloat::S_PPCDoubleDouble;
+  }
+
+  /// Create a concrete floating-point value. The value must satisfy
+  /// \c isModeledFloatValue.
   nonloc::ConcreteFloat makeFloatVal(const FloatingLiteral *F) {
     return nonloc::ConcreteFloat(BasicVals.getFloatValue(F->getValue()));
   }
diff --git a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
index ab90615f63182..3741cc41c5a97 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
@@ -91,6 +91,11 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
   if (!B->getRHS()->getType()->isScalarType())
     return;
 
+  // Floating-point divide by zero is defined when floating semantics conform
+  // to IEC 60559.
+  if (B->getRHS()->getType()->isRealFloatingType())
+    return;
+
   SVal Denom = C.getSVal(B->getRHS());
   std::optional<DefinedSVal> DV = Denom.getAs<DefinedSVal>();
 
diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
index b0a668f350ce4..b3d9f8b21b6fa 100644
--- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
@@ -130,6 +130,9 @@ APFloatPtr BasicValueFactory::getFloatValue(const llvm::APFloat &X) {
 
   using FoldNodeTy = llvm::FoldingSetNodeWrapper<llvm::APFloat>;
 
+  // ID must be unique to differentiate between nodes. Unlike integers, bit
+  // size and pattern are not sufficient, so add semantics to the ID as well.
+  ID.AddInteger(llvm::APFloat::SemanticsToEnum(X.getSemantics()));
   X.Profile(ID);
   FoldNodeTy *P = APFloatSet.FindNodeOrInsertPos(ID, InsertPos);
 
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index 9d69fb5178712..ebff557f8a3bc 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -274,9 +274,8 @@ void ExprEngine::VisitCastExpr(const CastExpr *CastE, ExplodedNode *Pred,
 
       if (const MemRegion *MR = State->getSVal(Ex, SF).getAsRegion()) {
         SVal OrigV = State->getSVal(MR);
-        // __builtin_bit_cast reinterprets raw bits. We cannot model this
-        // for floating-point values because evalCast performs a value
-        // conversion, not a bit reinterpretation.
+        // evalCast converts the value, but we are doing a bitcast here, which
+        // is unmodeled for floats.
         if (!OrigV.getAs<nonloc::ConcreteFloat>())
           CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV),
                                          CastE->getType(), Ex->getType());
@@ -956,12 +955,21 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred,
           if (std::optional<Loc> LV = V.getAs<Loc>()) {
           Loc X = svalBuilder.makeNullWithType(Ex->getType());
           Result = evalBinOp(state, BO_EQ, *LV, X, U->getType());
+          } else if (Ex->getType()->isRealFloatingType()) {
+            // Create a zero with matching semantics to the floating point.
+            DefinedOrUnknownSVal X = svalBuilder.makeZeroVal(Ex->getType());
+            if (std::optional<NonLoc> ZeroNL = X.getAs<NonLoc>())
+              Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), *ZeroNL,
+                                 U->getType());
+            else
+              Result = UnknownVal();
           } else if (Ex->getType()->isFloatingType()) {
-          // FIXME: handle floating point types.
-          Result = UnknownVal();
+            // FIXME: handle complex floating point types.
+            Result = UnknownVal();
           } else {
-          nonloc::ConcreteInt X(getBasicVals().getValue(0, Ex->getType()));
-          Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), X, U->getType());
+            nonloc::ConcreteInt X(getBasicVals().getValue(0, Ex->getType()));
+            Result =
+                evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), X, U->getType());
           }
 
           state = state->BindExpr(U, SF, Result);
@@ -1040,6 +1048,12 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U,
       RHS = svalBuilder.makeArrayIndex(1);
     else if (U->getType()->isIntegralOrEnumerationType())
       RHS = svalBuilder.makeIntVal(1, U->getType());
+    else if (U->getType()->isRealFloatingType())
+      // C99 6.5.3.1: ++E is equivalent to (E += 1). Then the usual arithmetic
+      // conversions convert the 1 to E's type, so just build it as that type
+      // here.
+      RHS = svalBuilder.makeFloatVal(llvm::APFloat::getOne(
+          getContext().getFloatTypeSemantics(U->getType())));
     else
       RHS = UnknownVal();
 
diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
index 10d1d799f1e16..377902fa6024f 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -66,11 +66,18 @@ DefinedOrUnknownSVal SValBuilder::makeZeroVal(QualType type) {
   if (type->isIntegralOrEnumerationType())
     return makeIntVal(0, type);
 
+  if (type->isRealFloatingType()) {
+    llvm::APFloat Zero =
+        llvm::APFloat::getZero(Context.getFloatTypeSemantics(type));
+    if (!isModeledFloatValue(Zero))
+      return UnknownVal();
+    return makeFloatVal(Zero);
+  }
+
   if (type->isArrayType() || type->isRecordType() || type->isVectorType() ||
       type->isAnyComplexType())
     return makeCompoundVal(type, BasicVals.getEmptySValList());
 
-  // FIXME: Handle floats.
   return UnknownVal();
 }
 
@@ -220,8 +227,8 @@ DefinedSVal SValBuilder::getConjuredHeapSymbolVal(ConstCFGElementRef elem,
   assert(Loc::isLocType(type));
   assert(SymbolManager::canSymbolicate(type));
   if (type->isNullPtrType()) {
-    // makeZeroVal() returns UnknownVal only in case of FP number, which
-    // is not the case.
+    // The assert above establishes this is a Loc type, for which makeZeroVal()
+    // always returns a defined value.
     return makeZeroVal(type).castAs<DefinedSVal>();
   }
 
@@ -385,8 +392,12 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
   case Stmt::IntegerLiteralClass:
     return makeIntVal(cast<IntegerLiteral>(E));
 
-  case Stmt::FloatingLiteralClass:
-    return makeFloatVal(cast<FloatingLiteral>(E));
+  case Stmt::FloatingLiteralClass: {
+    const auto *FL = cast<FloatingLiteral>(E);
+    if (!isModeledFloatValue(FL->getValue()))
+      return UnknownVal();
+    return makeFloatVal(FL);
+  }
 
   case Stmt::ObjCBoolLiteralExprClass:
     return makeBoolVal(cast<ObjCBoolLiteralExpr>(E));
@@ -406,6 +417,8 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
       break;
     case CK_ArrayToPointerDecay:
     case CK_IntegralToPointer:
+    case CK_IntegralToFloating:
+    case CK_FloatingCast:
     case CK_NoOp:
     case CK_BitCast: {
       const Expr *SE = CE->getSubExpr();
@@ -470,8 +483,8 @@ SVal SValBuilder::evalMinus(NonLoc X) {
   case nonloc::ConcreteIntKind:
     return makeIntVal(-X.castAs<nonloc::ConcreteInt>().getValue());
   case nonloc::ConcreteFloatKind: {
-    // Negation only flips the sign bit and is well-defined for all
-    // floating-point values regardless of semantics, so model it.
+    // Negation is well-defined regardless of floating-point semantics (it's
+    // just a sign bit flip).
     llvm::APFloat Value = *X.castAs<nonloc::ConcreteFloat>().getValue();
     Value.changeSign();
     return makeFloatVal(Value);
@@ -884,19 +897,28 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
     return UnknownVal();
   }
   SVal VisitConcreteFloat(nonloc::ConcreteFloat V) {
-    // Float to float.
+    // Float to float. Modeled only when the conversion is exact, which needs no
+    // rounding and so does not depend on the rounding mode in effect.
     if (CastTy->isRealFloatingType()) {
       const llvm::fltSemantics &TargetSem =
           VB.getContext().getFloatTypeSemantics(CastTy);
       llvm::APFloat Value = *V.getValue();
       bool LosesInfo = false;
       Value.convert(TargetSem, llvm::APFloat::rmNearestTiesToEven, &LosesInfo);
-      if (!LosesInfo)
+      if (!LosesInfo && SValBuilder::isModeledFloatValue(Value))
         return VB.makeFloatVal(Value);
       return UnknownVal();
     }
 
-    // Float to integer.
+    // Float to bool. Must precede integral case below since bool is also an
+    // integral but needs special handling.
+    if (CastTy->isBooleanType())
+      return VB.makeTruthVal(!V.getValue()->isZero(), CastTy);
+
+    // Float to integer. Since only finite floats are modeled, the only
+    // possible failure is if the float doesn't fit in the target, which opOK
+    // helps us catch. opInexact catches whether truncation toward zero
+    // happened, which is defined behavior, thus we model.
     if (CastTy->isIntegralOrEnumerationType()) {
       APSIntType ResultType = VB.getBasicValueFactory().getAPSIntType(CastTy);
       llvm::APSInt Result = ResultType.getValue(0);
@@ -909,10 +931,6 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
       return UnknownVal();
     }
 
-    // Float to bool.
-    if (CastTy->isBooleanType())
-      return VB.makeTruthVal(!V.getValue()->isZero(), CastTy);
-
     return UnknownVal();
   }
   SVal VisitConcreteInt(nonloc::ConcreteInt V) {
@@ -934,6 +952,20 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
     if (Loc::isLocType(CastTy))
       return VB.makeIntLocVal(CastedValue());
 
+    // Integer to float. Modeled only when the conversion is exact.
+    if (CastTy->isRealFloatingType()) {
+      const llvm::fltSemantics &TargetSem =
+          VB.getContext().getFloatTypeSemantics(CastTy);
+      llvm::APSInt Value = V.getValue();
+      llvm::APFloat Result(TargetSem);
+      llvm::APFloat::opStatus Status = Result.convertFromAPInt(
+          Value, Value.isSigned(), llvm::APFloat::rmNearestTiesToEven);
+      if (Status == llvm::APFloat::opOK &&
+          SValBuilder::isModeledFloatValue(Result))
+        return VB.makeFloatVal(Result);
+      return UnknownVal();
+    }
+
     // Pointer to whatever else.
     return UnknownVal();
   }
diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp
index b13019d74e883..afcf81db949d6 100644
--- a/clang/lib/StaticAnalyzer/Core/SVals.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp
@@ -48,6 +48,8 @@ static StringRef getFloatSemanticsName(const llvm::fltSemantics &Sem) {
     return "IEEEdouble";
   case llvm::APFloat::S_IEEEquad:
     return "IEEEquad";
+  case llvm::APFloat::S_PPCDoubleDouble:
+    return "PPCDoubleDouble";
   case llvm::APFloat::S_x87DoubleExtended:
     return "x87DoubleExtended";
   default:
@@ -180,6 +182,8 @@ class TypeRetrievingVisitor
       return Context.DoubleTy;
     case llvm::APFloat::S_IEEEquad:
       return Context.Float128Ty;
+    case llvm::APFloat::S_PPCDoubleDouble:
+      return Context.Ibm128Ty;
     case llvm::APFloat::S_x87DoubleExtended:
       return Context.LongDoubleTy;
     default:
@@ -282,8 +286,7 @@ nonloc::PointerToMember::iterator nonloc::PointerToMember::end() const {
 //===----------------------------------------------------------------------===//
 
 bool SVal::isConstant() const {
-  return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>() ||
-         getAs<nonloc::ConcreteFloat>();
+  return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>();
 }
 
 bool SVal::isConstant(int I) const {
@@ -295,8 +298,6 @@ bool SVal::isConstant(int I) const {
 }
 
 bool SVal::isZeroConstant() const {
-  if (std::optional<nonloc::ConcreteFloat> FV = getAs<nonloc::ConcreteFloat>())
-    return FV->getValue()->isZero();
   return isConstant(0);
 }
 
diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
index 48aec507a85a9..17672a26b2b8b 100644
--- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
@@ -81,9 +81,6 @@ class SimpleSValBuilder : public SValBuilder {
   /// (integer) value, that value is returned. Otherwise, returns NULL.
   const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal V) override;
 
-  const llvm::APFloat *getKnownFloatValue(ProgramStateRef state,
-                                          SVal V) override;
-
   /// Evaluates a given SVal by recursively evaluating and simplifying the
   /// children SVals, then returns its minimal possible (integer) value. If the
   /// constraint manager cannot provide a meaningful answer, this returns NULL.
@@ -439,9 +436,9 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
   if (auto simplifiedRhsAsNonLoc = simplifiedRhs.getAs<NonLoc>())
     rhs = *simplifiedRhsAsNonLoc;
 
-  // Handle trivial case where left-side and right-side are the same.
-  // Deliberately exclude floating-point values since x - x isn't necessarily 0
-  // (e.g., inf - inf), and x == x is false when x is NaN.
+  // Handle trivial case where left-side and right-side are the same. Exclude
+  // floating points: the ConcreteFloat case below folds these correctly
+  // instead.
   if (lhs == rhs && !lhs.getAs<nonloc::ConcreteFloat>())
     switch (op) {
       default:
@@ -566,11 +563,12 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
         break;
       }
 
-      // We can model arithmetic (operators +, -, *, /) only when both operands
-      // are finite and result is exact (which needs no rounding). Inexact,
-      // non-finite, or exceptions (like overflow or div by zero) is unmodeled.
-      if (!L.isFinite() || !R.isFinite())
-        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+      // We can model arithmetic (operators +, -, *, /) only when the result is
+      // exact (which needs no rounding). The opOK guard ensures rounding or
+      // exceptional conditions (e.g., overflows and div by zero) are also not
+      // modeled.
+      assert(L.isFinite() && R.isFinite() &&
+             "A concrete float is always finite");
 
       llvm::APFloat Result = L;
       llvm::APFloat::opStatus Status;
@@ -591,7 +589,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
         return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
       }
 
-      if (Status == llvm::APFloat::opOK)
+      if (Status == llvm::APFloat::opOK && isModeledFloatValue(Result))
         return makeFloatVal(Result);
 
       return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
@@ -1296,13 +1294,6 @@ const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state,
   return getConstValue(state, simplifySVal(state, V));
 }
 
-const llvm::APFloat *
-SimpleSValBuilder::getKnownFloatValue(ProgramStateRef state, SVal V) {
-  if (auto X = V.getAs<nonloc::ConcreteFloat>())
-    return X->getValue().get();
-  return nullptr;
-}
-
 const llvm::APSInt *SimpleSValBuilder::getMinValue(ProgramStateRef state,
                                                    SVal V) {
   V = simplifySVal(state, V);
diff --git a/clang/test/Analysis/constant-float-16bit.c b/clang/test/Analysis/constant-float-16bit.c
new file mode 100644
index 0000000000000..999a036e1e7c9
--- /dev/null
+++ b/clang/test/Analysis/constant-float-16bit.c
@@ -0,0 +1,39 @@
+// _Float16 and __bf16 are both 16 bits wide but have different semantics, so
+// the same bit pattern denotes different values in each.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify %s
+// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify %s
+
+void clang_analyzer_dump_half(_Float16);
+void clang_analyzer_dump_bfloat(__bf16);
+void clang_analyzer_dumpSvalType_half(_Float16);
+void clang_analyzer_dumpSvalType_bfloat(__bf16);
+void clang_analyzer_eval(int);
+
+void test16BitValues(void) {
+  clang_analyzer_dump_half((_Float16)1.5f);         // expected-warning{{1.5 IEEEhalf}}
+  clang_analyzer_dump_bfloat((__bf16)1.5f);         // expected-warning{{1.5 BFloat}}
+  clang_analyzer_dumpSvalType_half((_Float16)1.5f); // expected-warning{{_Float16}}
+  clang_analyzer_dumpSvalType_bfloat((__bf16)1.5f); // expected-warning{{__bf16}}
+}
+
+// 0x3C00 is 1.0 as an IEEEhalf and 0.0078125 as a BFloat. Ensure conversions
+// between them are respected.
+void testSameBitsDifferentSemantics(void) {
+  clang_analyzer_dump_bfloat((__bf16)0.0078125f); // expected-warning{{0.007813 BFloat}}
+  _Float16 a = (_Float16)1.0f;
+  clang_analyzer_dump_half(a);                    // expected-warning{{1 IEEEhalf}}
+  clang_analyzer_dump_half(a + a);                // expected-warning{{2 IEEEhalf}}
+  clang_analyzer_eval(a + a == (_Float16)2.0f);   // expected-warning{{TRUE}}
+}
+
+// 1 + 2^-9 needs 10 mantissa bits, which fits an IEEEhalf but not a BFloat, so
+// result depends on rounding semantics => unmodeled.
+void testExactnessIsPerSemantics(void) {
+  clang_analyzer_dump_half((_Float16)1.001953125f); // expected-warning{{1.002 IEEEhalf}}
+  clang_analyzer_dump_bfloat((__bf16)1.001953125f); // expected-warning{{Unknown}}
+}
diff --git a/clang/test/Analysis/constant-float-32bit-double.c b/clang/test/Analysis/constant-float-32bit-double.c
new file mode 100644
index 0000000000000..607a55e065443
--- /dev/null
+++ b/clang/test/Analysis/constant-float-32bit-double.c
@@ -0,0 +1,25 @@
+// We can't assume a double will be wider than a float.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -mdouble=32 \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify %s
+
+void clang_analyzer_dump_float(float);
+void clang_analyzer_dump_double(double);
+void clang_analyzer_dumpSvalType_double(double);
+void clang_analyzer_eval(int);
+
+void testDoubleIsSingle(void) {
+  clang_analyzer_dump_double(3.14);         // expected-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dumpSvalType_double(3.14); // expected-warning{{float}}
+}
+
+void testConversionsAreExact(void) {
+  clang_analyzer_dump_float((float)3.14);   // expected-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEsingle}}
+  clang_analyzer_eval(3.14 == 3.14f);       // expected-warning{{TRUE}}
+}
+
+void testInexactStillUnmodeled(void) {
+  clang_analyzer_dump_double(0.1 + 0.2);  // expected-warning{{Unknown}}
+}
diff --git a/clang/test/Analysis/constant-float-cxx.cpp b/clang/test/Analysis/constant-float-cxx.cpp
new file mode 100644
index 0000000000000..9b14ab559b263
--- /dev/null
+++ b/clang/test/Analysis/constant-float-cxx.cpp
@@ -0,0 +1,48 @@
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify %s
+
+void clang_analyzer_dump_float(float);
+void clang_analyzer_dump_double(double);
+void clang_analyzer_eval(int);
+
+template <typename T> T twice(T x) { return x + x; }
+
+struct Vec {
+  float v;
+  Vec operator+(Vec o) const { return Vec{v + o.v}; }
+};
+
+constexpr double half(double x) { return x / 2.0; }
+
+double defaultArg(double i = 42) { return i; }
+float narrowingDefaultArg(float i = 1.5) { return i; }
+
+void testTemplate() {
+  clang_analyzer_dump_float(twice(1.5f)); // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump_double(twice(2.5)); // expected-warning{{5 IEEEdouble}}
+}
+
+void testOverloadedOperator() {
+  Vec a{1.5f}, b{2.5f};
+  clang_analyzer_dump_float((a + b).v); // expected-warning{{4 IEEEsingle}}
+}
+
+void testConstexpr() {
+  clang_analyzer_dump_double(half(3.0));  // expected-warning{{1.5 IEEEdouble}}
+  constexpr double c = 1.25;
+  clang_analyzer_dump_double(c);          // expected-warning{{1.25 IEEEdouble}}
+}
+
+// A default argument is not evaluated through the CFG, rather it is folded by
+// getConstantVal.
+void testDefaultArgument() {
+  clang_analyzer_dump_double(defaultArg());         // expected-warning{{42 IEEEdouble}}
+  clang_analyzer_dump_float(narrowingDefaultArg()); // expected-warning{{1.5 IEEEsingle}}
+}
+
+// A negative value does not fit an unsigned integer; the conversion should be
+// unmodeled.
+void testUnsignedFromNegative() {
+  clang_analyzer_eval((unsigned)-1.5f == 0);  // expected-warning{{UNKNOWN}}
+}
diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c
deleted file mode 100644
index 09a57e55a42a6..0000000000000
--- a/clang/test/Analysis/constant-float-literals.c
+++ /dev/null
@@ -1,181 +0,0 @@
-// Semantics of long double differ depending on target, which is why we run on
-// multiple targets.
-//
-// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify=common,x87 %s
-// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify=common,quad %s
-// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify=common,ldbl64 %s
-
-void clang_analyzer_dump_float(float);
-void clang_analyzer_dump_double(double);
-void clang_analyzer_dump_longdouble(long double);
-void clang_analyzer_eval(int);
-
-//===----------------------------------------------------------------------===//
-// Floating-point literals are modeled as ConcreteFloat SVals.
-//===----------------------------------------------------------------------===//
-
-void testFloatLiterals(void) {
-  clang_analyzer_dump_float(0.0f);  // common-warning{{0 IEEEsingle}}
-  clang_analyzer_dump_float(1.0f);  // common-warning{{1 IEEEsingle}}
-  clang_analyzer_dump_float(3.14f); // common-warning{{3.1400001 IEEEsingle}}
-  clang_analyzer_dump_double(0.0);  // common-warning{{0 IEEEdouble}}
-  clang_analyzer_dump_double(1.0);  // common-warning{{1 IEEEdouble}}
-  clang_analyzer_dump_double(3.14); // common-warning{{3.1400000000000001 IEEEdouble}}
-}
-
-//===----------------------------------------------------------------------===//
-// long double is modeled with the target's floating-point semantics.
-//===----------------------------------------------------------------------===//
-
-void testLongDoubleLiterals(void) {
-  // 0.0 and 1.0 are representable exactly in all formats so only semantic name
-  // differs on different targets.
-  clang_analyzer_dump_longdouble(0.0L);
-  // x87-warning at -1{{0 x87DoubleExtended}}
-  // quad-warning at -2{{0 IEEEquad}}
-  // ldbl64-warning at -3{{0 IEEEdouble}}
-  clang_analyzer_dump_longdouble(1.0L);
-  // x87-warning at -1{{1 x87DoubleExtended}}
-  // quad-warning at -2{{1 IEEEquad}}
-  // ldbl64-warning at -3{{1 IEEEdouble}}
-}
-
-//===----------------------------------------------------------------------===//
-// Variables assigned from literals retain the ConcreteFloat value.
-//===----------------------------------------------------------------------===//
-
-void testVariables(void) {
-  float f = 1.5f;
-  double d = 2.5;
-  clang_analyzer_dump_float(f);   // common-warning{{1.5 IEEEsingle}}
-  clang_analyzer_dump_double(d);  // common-warning{{2.5 IEEEdouble}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-integer casts (truncation).
-//===----------------------------------------------------------------------===//
-
-void testFloatToInt(void) {
-  float f = 1.9f;
-  double d = 2.7;
-  int i = (int)f;
-  int j = (int)d;
-  clang_analyzer_eval(i == 1);  // common-warning{{TRUE}}
-  clang_analyzer_eval(j == 2);  // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-bool casts.
-//===----------------------------------------------------------------------===//
-
-void testFloatToBool(void) {
-  float zero = 0.0f;
-  float nonzero = 1.0f;
-  clang_analyzer_eval((int)((_Bool)zero) == 0);     // common-warning{{TRUE}}
-  clang_analyzer_eval((int)((_Bool)nonzero) == 1);  // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-float casts (precision change without loss).
-//===----------------------------------------------------------------------===//
-
-void testFloatUpcast(void) {
-  float f = 1.5f;
-  double d = f;
-  // 1.5 is exactly representable in both, so no loss.
-  clang_analyzer_dump_double(d);  // common-warning{{1.5 IEEEdouble}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-float casts (inexact narrowing stays Unknown).
-//===----------------------------------------------------------------------===//
-
-void testFloatNarrowing(void) {
-  double d = 3.14;
-  float f = (float)d;
-  // 3.14 is not exactly representable in float, and rounding direction is
-  // implementation-defined, so we don't model here.
-  clang_analyzer_dump_float(f); // common-warning{{Unknown}}
-}
-
-//===----------------------------------------------------------------------===//
-// Unknown float values (parameters, arithmetic results).
-//===----------------------------------------------------------------------===//
-
-void testUnknown(float f) {
-  clang_analyzer_dump_float(f);         // common-warning{{Unknown}}
-  clang_analyzer_dump_float(f + 1.0f);  // common-warning{{Unknown}}
-}
-
-//===----------------------------------------------------------------------===//
-// Arithmetic between concrete floats is folded only when the result is exact.
-//===----------------------------------------------------------------------===//
-
-void testExactArithmetic(void) {
-  // All of these have exactly representable results, so they are independent
-  // of rounding mode and evaluation precision.
-  clang_analyzer_dump_float(1.0f + 2.0f);  // common-warning{{3 IEEEsingle}}
-  clang_analyzer_dump_float(5.0f - 1.5f);  // common-warning{{3.5 IEEEsingle}}
-  clang_analyzer_dump_float(1.5f * 2.0f);  // common-warning{{3 IEEEsingle}}
-  clang_analyzer_dump_float(3.0f / 4.0f);  // common-warning{{0.75 IEEEsingle}}
-  clang_analyzer_dump_double(0.5 + 0.25);  // common-warning{{0.75 IEEEdouble}}
-}
-
-void testInexactArithmetic(void) {
-  // 0.1f + 0.2f is not exactly representable in single precision; the rounded
-  // result depends on the rounding mode / evaluation precision, so we do not
-  // model it.
-  clang_analyzer_dump_float(0.1f + 0.2f);  // common-warning{{Unknown}}
-  // 1.0f / 3.0f is inexact.
-  clang_analyzer_dump_float(1.0f / 3.0f);  // common-warning{{Unknown}}
-}
-
-void testComparisons(void) {
-  clang_analyzer_eval(1.0f < 2.0f);   // common-warning{{TRUE}}
-  clang_analyzer_eval(2.0f < 1.0f);   // common-warning{{FALSE}}
-  clang_analyzer_eval(1.5 == 1.5);    // common-warning{{TRUE}}
-  clang_analyzer_eval(1.5 != 2.5);    // common-warning{{TRUE}}
-  clang_analyzer_eval(2.0f >= 2.0f);  // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Unary negation is always exact (a sign-bit flip).
-//===----------------------------------------------------------------------===//
-
-void testNegation(void) {
-  float f = 1.5f;
-  double d = 2.5;
-  clang_analyzer_dump_float(-f);    // common-warning{{-1.5 IEEEsingle}}
-  clang_analyzer_dump_double(-d);   // common-warning{{-2.5 IEEEdouble}}
-  clang_analyzer_dump_float(-(-f)); // common-warning{{1.5 IEEEsingle}}
-  clang_analyzer_eval(-f < 0.0f);   // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Infinity from an overflowing literal is concrete, but arithmetic on it is
-// not folded and casting it to int is not modeled (both would depend on
-// IEC 60559 semantics / are undefined in C), while comparisons still work.
-//===----------------------------------------------------------------------===//
-
-void testInfinity(void) {
-  float big = 1e400f; // common-warning{{magnitude of floating-point constant too large}}
-  clang_analyzer_dump_float(big);             // common-warning{{+Inf IEEEsingle}}
-  clang_analyzer_dump_float(big + 1.0f);      // common-warning{{Unknown}}
-  clang_analyzer_eval(big > 1.0f);            // common-warning{{TRUE}}
-  clang_analyzer_dump_float((float)(int)big); // common-warning{{Unknown}}
-}
-
-//===----------------------------------------------------------------------===//
-// Division by zero detection with concrete floats.
-//===----------------------------------------------------------------------===//
-
-float testDivByZeroFloat(void) {
-  float x = 0.0f;
-  return 1.0f / x;  // common-warning{{Division by zero}}
-}
diff --git a/clang/test/Analysis/constant-float-long-double.c b/clang/test/Analysis/constant-float-long-double.c
new file mode 100644
index 0000000000000..87e56eb444196
--- /dev/null
+++ b/clang/test/Analysis/constant-float-long-double.c
@@ -0,0 +1,78 @@
+// Multiple targets needed because long double semantics differ on them.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify=x87 %s
+// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify=quad %s
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify=ldbl64 %s
+// RUN: %clang_analyze_cc1 -triple powerpc64le-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify=ibm128 %s
+
+void clang_analyzer_dump_longdouble(long double);
+void clang_analyzer_dumpSvalType_longdouble(long double);
+void clang_analyzer_eval(int);
+
+void testVariables(void) {
+  long double ld = 1.5L;
+  clang_analyzer_dump_longdouble(ld);
+  // x87-warning at -1{{1.5 x87DoubleExtended}}
+  // quad-warning at -2{{1.5 IEEEquad}}
+  // ldbl64-warning at -3{{1.5 IEEEdouble}}
+  // ibm128-warning at -4{{Unknown}}
+}
+
+// long double has different semantics depending on target. IBM double-double
+// should not be modeled.
+void testLongDouble(void) {
+  clang_analyzer_dump_longdouble(1.0L);
+  // x87-warning at -1{{1 x87DoubleExtended}}
+  // quad-warning at -2{{1 IEEEquad}}
+  // ldbl64-warning at -3{{1 IEEEdouble}}
+  // ibm128-warning at -4{{Unknown}}
+  clang_analyzer_dumpSvalType_longdouble(1.0L);
+  // x87-warning at -1{{long double}}
+  // quad-warning at -2{{__float128}}
+  // ldbl64-warning at -3{{double}}
+  // ibm128-warning at -4{{NULL TYPE}}
+}
+
+void testLongDoubleArithmetic(void) {
+  clang_analyzer_dump_longdouble(1.0L + 2.0L);
+  // x87-warning at -1{{3 x87DoubleExtended}}
+  // quad-warning at -2{{3 IEEEquad}}
+  // ldbl64-warning at -3{{3 IEEEdouble}}
+  // ibm128-warning at -4{{Unknown}}
+  clang_analyzer_dump_longdouble(3.0L / 4.0L);
+  // x87-warning at -1{{0.75 x87DoubleExtended}}
+  // quad-warning at -2{{0.75 IEEEquad}}
+  // ldbl64-warning at -3{{0.75 IEEEdouble}}
+  // ibm128-warning at -4{{Unknown}}
+  clang_analyzer_eval(1.0L < 2.0L);
+  // x87-warning at -1{{TRUE}}
+  // quad-warning at -2{{TRUE}}
+  // ldbl64-warning at -3{{TRUE}}
+  // ibm128-warning at -4{{UNKNOWN}}
+}
+
+void testIntToFloat(void) {
+  clang_analyzer_dump_longdouble((long double)1);
+  // x87-warning at -1{{1 x87DoubleExtended}}
+  // quad-warning at -2{{1 IEEEquad}}
+  // ldbl64-warning at -3{{1 IEEEdouble}}
+  // ibm128-warning at -4{{Unknown}}
+}
+
+// IBM double-double should not be created on the makeZeroVal build path.
+void testZeroInitialized(void) {
+  static long double s;
+  clang_analyzer_dump_longdouble(s);
+  // x87-warning at -1{{0 x87DoubleExtended}}
+  // quad-warning at -2{{0 IEEEquad}}
+  // ldbl64-warning at -3{{0 IEEEdouble}}
+  // ibm128-warning at -4{{Unknown}}
+}
diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c
new file mode 100644
index 0000000000000..6c91df894a21f
--- /dev/null
+++ b/clang/test/Analysis/constant-float.c
@@ -0,0 +1,172 @@
+// Disable -Wliteral-range since we intentionally induce inf.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
+// RUN:   -analyzer-config eagerly-assume=false -verify %s
+//
+// Only exact results are modeled so the analyzer's behavior should not change
+// under a dynamic rounding mode.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -frounding-math \
+// RUN:   -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
+// RUN:   -analyzer-config eagerly-assume=false -verify %s
+
+void clang_analyzer_dump_float(float);
+void clang_analyzer_dump_double(double);
+void clang_analyzer_eval(int);
+
+void testLiterals(void) {
+  clang_analyzer_dump_float(0.0f);  // expected-warning{{0 IEEEsingle}}
+  clang_analyzer_dump_float(3.14f); // expected-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dump_double(3.14); // expected-warning{{3.1400000000000001 IEEEdouble}}
+}
+
+void testVariables(void) {
+  float f = 1.5f;
+  clang_analyzer_dump_float(f); // expected-warning{{1.5 IEEEsingle}}
+}
+
+void testUnknown(float f) {
+  clang_analyzer_dump_float(f);         // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(f + 1.0f);  // expected-warning{{Unknown}}
+}
+
+// Exactly representable results are independent of rounding mode and
+// evaluation precision.
+void testArithmetic(void) {
+  clang_analyzer_dump_float(1.0f + 2.0f); // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump_float(5.0f - 1.5f); // expected-warning{{3.5 IEEEsingle}}
+  clang_analyzer_dump_float(1.5f * 2.0f); // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump_float(3.0f / 4.0f); // expected-warning{{0.75 IEEEsingle}}
+  clang_analyzer_dump_double(0.5 + 0.25); // expected-warning{{0.75 IEEEdouble}}
+  clang_analyzer_dump_float(0.1f + 0.2f); // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(1.0f / 3.0f); // expected-warning{{Unknown}}
+}
+
+// Comparisons are exact for every value, so all six predicates should fold.
+void testComparisons(void) {
+  clang_analyzer_eval(1.0f < 2.0f);   // expected-warning{{TRUE}}
+  clang_analyzer_eval(1.0f > 2.0f);   // expected-warning{{FALSE}}
+  clang_analyzer_eval(2.0f <= 2.0f);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(2.0f >= 2.0f);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(1.5 == 1.5);    // expected-warning{{TRUE}}
+  clang_analyzer_eval(1.5 != 2.5);    // expected-warning{{TRUE}}
+}
+
+// Unary negation can be modeled because of sign-bit.
+void testNegation(void) {
+  clang_analyzer_dump_float(-1.5f);     // expected-warning{{-1.5 IEEEsingle}}
+  clang_analyzer_dump_float(-(-1.5f));  // expected-warning{{1.5 IEEEsingle}}
+  clang_analyzer_dump_float(-0.0f);     // expected-warning{{-0 IEEEsingle}}
+  clang_analyzer_eval(0.0f == -0.0f);   // expected-warning{{TRUE}}
+}
+
+// Conversions between floating points should be modeled only when they are
+// exact. 3.14 stored in a double sets mantissa bits a float cannot hold, and
+// rounding direction is implementation-specific.
+void testFloatConversions(void) {
+  clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEdouble}}
+  clang_analyzer_dump_float((float)3.14);   // expected-warning{{Unknown}}
+}
+
+// Casts to bool is defined as a comparison to zero.
+void testFloatToBool(void) {
+  clang_analyzer_eval((_Bool)0.5f);   // expected-warning{{TRUE}}
+  clang_analyzer_eval((_Bool)2.0f);   // expected-warning{{TRUE}}
+  clang_analyzer_eval((_Bool)-3.0f);  // expected-warning{{TRUE}}
+  clang_analyzer_eval((_Bool)0.0f);   // expected-warning{{FALSE}}
+  clang_analyzer_eval((_Bool)-0.0f);  // expected-warning{{FALSE}}
+}
+
+// !E is defined as (0 == E) with the zero converted to the type of expr. E.
+void testLogicalNot(void) {
+  float nonzero = 0.5f, zero = 0.0f, negzero = -0.0f;
+  clang_analyzer_eval(!nonzero);  // expected-warning{{FALSE}}
+  clang_analyzer_eval(!zero);     // expected-warning{{TRUE}}
+  clang_analyzer_eval(!negzero);  // expected-warning{{TRUE}}
+}
+
+// Casts to integers discard fractional bits, which should be unmodeled when
+// the result is out of range.
+void testFloatToInt(void) {
+  clang_analyzer_eval((int)1.9f == 1);          // expected-warning{{TRUE}}
+  clang_analyzer_eval((int)-1.9 == -1);         // expected-warning{{TRUE}}
+  clang_analyzer_dump_float((float)(int)1e30f); // expected-warning{{Unknown}}
+}
+
+// Infinities and NaNs should not be modeled.
+void testNonFiniteIsUnmodeled(void) {
+  clang_analyzer_dump_float(1e400f);                // expected-warning{{Unknown}}
+  clang_analyzer_dump_float((float)1e300);          // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(__FLT_MAX__ * 2.0f);    // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(__builtin_inff());      // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(__builtin_huge_valf()); // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(__builtin_nanf(""));    // expected-warning{{Unknown}}
+  clang_analyzer_dump_double(0.0 / 0.0);            // expected-warning{{Unknown}}
+  clang_analyzer_dump_double(1.0 / 0.0);            // expected-warning{{Unknown}}
+}
+
+void testSelfArithmetic(void) {
+  float f = 1.5f;
+  clang_analyzer_dump_float(f - f); // expected-warning{{0 IEEEsingle}}
+  clang_analyzer_eval(f == f);      // expected-warning{{TRUE}}
+}
+
+// Subnormals should not be modeled.
+void testSubnormals(void) {
+  clang_analyzer_dump_float(__FLT_DENORM_MIN__);         // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(__FLT_MIN__ / 2.0f);         // expected-warning{{Unknown}}
+  clang_analyzer_dump_float(__FLT_MIN__ * __FLT_MIN__);  // expected-warning{{Unknown}}
+  clang_analyzer_dump_float((float)(double)__FLT_DENORM_MIN__);
+  // expected-warning at -1{{Unknown}}
+  clang_analyzer_dump_float(__FLT_MIN__);
+  // expected-warning at -1{{1.17549435E-38 IEEEsingle}}
+}
+
+// Integer to float conversions should only be modeled when the conversion is
+// exact.
+void testIntToFloat(void) {
+  float f = 1;
+  clang_analyzer_dump_float(f);                // expected-warning{{1 IEEEsingle}}
+  clang_analyzer_dump_float(1.0f + 1);         // expected-warning{{2 IEEEsingle}}
+  clang_analyzer_dump_float((float)-3);        // expected-warning{{-3 IEEEsingle}}
+  clang_analyzer_dump_float((float)16777216);  // expected-warning{{16777216 IEEEsingle}}
+  clang_analyzer_dump_float((float)16777217);  // expected-warning{{Unknown}}
+
+  // 2^26 can be represented, 2^26 + 1 cannot.
+  long yes = 1L << 26;
+  long no = yes + 1L;
+  clang_analyzer_dump_float((float)yes);  // expected-warning{{67108864}}
+  clang_analyzer_dump_float((float)no);   // expected-warning{{Unknown}}
+}
+
+// Complex floating-point types are not modeled.
+void testComplexIsUnmodeled(void) {
+  _Complex float z = 1.5f;
+  clang_analyzer_dump_float(__real__ z); // expected-warning{{Unknown}}
+  clang_analyzer_eval(!z);               // expected-warning{{UNKNOWN}}
+}
+
+// Inc/decrement operators compute through the same path as += and -=
+// respectively in the analyzer.
+void testIncrementDecrement(void) {
+  float g = 2.0f;
+  g += 1.0f;
+  clang_analyzer_dump_float(g);   // expected-warning{{3 IEEEsingle}}
+  --g;
+  clang_analyzer_dump_float(g);   // expected-warning{{2 IEEEsingle}}
+  clang_analyzer_dump_float(g++); // expected-warning{{2 IEEEsingle}}
+  clang_analyzer_dump_float(g);   // expected-warning{{3 IEEEsingle}}
+
+  float small = 0.5f;
+  ++small;
+  clang_analyzer_dump_float(small); // expected-warning{{1.5 IEEEsingle}}
+
+  float inexact = 0.1f;
+  ++inexact;
+  clang_analyzer_dump_float(inexact); // expected-warning{{Unknown}}
+
+  float max = __FLT_MAX__;
+  ++max;
+  clang_analyzer_dump_float(max); // expected-warning{{Unknown}}
+}
diff --git a/clang/test/Analysis/inline.cpp b/clang/test/Analysis/inline.cpp
index 2b31460330e44..f718e6d8ae4c5 100644
--- a/clang/test/Analysis/inline.cpp
+++ b/clang/test/Analysis/inline.cpp
@@ -285,11 +285,11 @@ namespace DefaultArgs {
   }
 
   void testFloatReference() {
-    clang_analyzer_eval(defaultFloatReference(1) == -1); // expected-warning{{UNKNOWN}}
-    clang_analyzer_eval(defaultFloatReference() == -42); // expected-warning{{UNKNOWN}}
+    clang_analyzer_eval(defaultFloatReference(1) == -1); // expected-warning{{TRUE}}
+    clang_analyzer_eval(defaultFloatReference() == -42); // expected-warning{{TRUE}}
 
-    clang_analyzer_eval(defaultFloatReferenceZero(1) == -1); // expected-warning{{UNKNOWN}}
-    clang_analyzer_eval(defaultFloatReferenceZero() == 0); // expected-warning{{UNKNOWN}}
+    clang_analyzer_eval(defaultFloatReferenceZero(1) == -1); // expected-warning{{TRUE}}
+    clang_analyzer_eval(defaultFloatReferenceZero() == 0); // expected-warning{{TRUE}}
   }
 
   char defaultString(const char *s = "abc") {
diff --git a/clang/test/Analysis/operator-calls.cpp b/clang/test/Analysis/operator-calls.cpp
index dde21f164cee8..9825dc5ebf888 100644
--- a/clang/test/Analysis/operator-calls.cpp
+++ b/clang/test/Analysis/operator-calls.cpp
@@ -69,6 +69,7 @@ namespace RValues {
     }
   };
 
+  // 1.0 is no longer unknown. This function forces an unknown float.
   float getUnknownFloat();
 
   SmallOpaque getSmallOpaque() {

>From 426971a4ecfcdf75a9c9d4ea0705de6f8e813876 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Fri, 7 Aug 2026 21:54:53 +0200
Subject: [PATCH 07/17] Gate ConcreteFloat invariant in makeFloatVal, update
 test comments

---
 .../clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h     | 3 +++
 clang/test/Analysis/constant-float.c                          | 4 ++--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
index 70f7591ecf297..466926a3474e0 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
@@ -293,10 +293,13 @@ class SValBuilder {
   /// Create a concrete floating-point value. The value must satisfy
   /// \c isModeledFloatValue.
   nonloc::ConcreteFloat makeFloatVal(const FloatingLiteral *F) {
+    assert(isModeledFloatValue(F->getValue()) &&
+           "ConcreteFloat must be normal and finite");
     return nonloc::ConcreteFloat(BasicVals.getFloatValue(F->getValue()));
   }
 
   nonloc::ConcreteFloat makeFloatVal(const llvm::APFloat &F) {
+    assert(isModeledFloatValue(F) && "ConcreteFloat must be normal and finite");
     return nonloc::ConcreteFloat(BasicVals.getFloatValue(F));
   }
 
diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c
index 6c91df894a21f..3dd52d5838285 100644
--- a/clang/test/Analysis/constant-float.c
+++ b/clang/test/Analysis/constant-float.c
@@ -63,7 +63,7 @@ void testNegation(void) {
 
 // Conversions between floating points should be modeled only when they are
 // exact. 3.14 stored in a double sets mantissa bits a float cannot hold, and
-// rounding direction is implementation-specific.
+// rounding direction can change at runtime.
 void testFloatConversions(void) {
   clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEdouble}}
   clang_analyzer_dump_float((float)3.14);   // expected-warning{{Unknown}}
@@ -136,7 +136,7 @@ void testIntToFloat(void) {
   // 2^26 can be represented, 2^26 + 1 cannot.
   long yes = 1L << 26;
   long no = yes + 1L;
-  clang_analyzer_dump_float((float)yes);  // expected-warning{{67108864}}
+  clang_analyzer_dump_float((float)yes);  // expected-warning{{67108864 IEEEsingle}}
   clang_analyzer_dump_float((float)no);   // expected-warning{{Unknown}}
 }
 

>From de7a37d0adf522ebfe913769262242c7ed6d9d8f Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Sat, 8 Aug 2026 05:20:46 +0200
Subject: [PATCH 08/17] Fix type punning bug

Ensure that type punning involving floats are not modeled. Found on a
downstream 16-bit target, but reproduces on any target.
---
 clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 14 ++++++++++++++
 clang/test/Analysis/constant-float.c          | 10 ++++++++++
 2 files changed, 24 insertions(+)

diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
index 377902fa6024f..4956f4bd82c80 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -897,6 +897,17 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
     return UnknownVal();
   }
   SVal VisitConcreteFloat(nonloc::ConcreteFloat V) {
+    // A null original type occurs when trying to read a region as CastTy, as
+    // in the case of type punning. Only model when trying to read a float back
+    // as its original format (otherwise bits may be interpreted differently).
+    if (OriginalTy.isNull()) {
+      if (CastTy->isRealFloatingType() &&
+          &VB.getContext().getFloatTypeSemantics(CastTy) ==
+              &V.getValue()->getSemantics())
+        return V;
+      return UnknownVal();
+    }
+
     // Float to float. Modeled only when the conversion is exact, which needs no
     // rounding and so does not depend on the rounding mode in effect.
     if (CastTy->isRealFloatingType()) {
@@ -954,6 +965,9 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
 
     // Integer to float. Modeled only when the conversion is exact.
     if (CastTy->isRealFloatingType()) {
+      // Do not model type punning.
+      if (OriginalTy.isNull())
+        return UnknownVal();
       const llvm::fltSemantics &TargetSem =
           VB.getContext().getFloatTypeSemantics(CastTy);
       llvm::APSInt Value = V.getValue();
diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c
index 3dd52d5838285..678dcaa84e2e4 100644
--- a/clang/test/Analysis/constant-float.c
+++ b/clang/test/Analysis/constant-float.c
@@ -13,6 +13,7 @@
 
 void clang_analyzer_dump_float(float);
 void clang_analyzer_dump_double(double);
+void clang_analyzer_dump_int(int);
 void clang_analyzer_eval(int);
 
 void testLiterals(void) {
@@ -69,6 +70,15 @@ void testFloatConversions(void) {
   clang_analyzer_dump_float((float)3.14);   // expected-warning{{Unknown}}
 }
 
+// Type punning reinterprets the bits, whereas we only model conversions of the
+// value, so decline it in both directions.
+void testTypePunning(void) {
+  float f = 1.5f;
+  clang_analyzer_dump_int(*(int *)&f);     // expected-warning{{Unknown}}
+  int i = 5;
+  clang_analyzer_dump_float(*(float *)&i); // expected-warning{{Unknown}}
+}
+
 // Casts to bool is defined as a comparison to zero.
 void testFloatToBool(void) {
   clang_analyzer_eval((_Bool)0.5f);   // expected-warning{{TRUE}}

>From 492e2fde8cbac05f4a7d258daba99b5d37d4e91e Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 24 Aug 2026 18:32:03 +0200
Subject: [PATCH 09/17] Formatting and other NFC changes

This commit is restricted to formatting, comment changes, and NFC
refactoring.
---
 clang/docs/ReleaseNotes.md                    |  7 ++---
 .../StaticAnalyzer/Checkers/SValExplainer.h   | 16 +++++-----
 .../Core/PathSensitive/APFloatPtr.h           | 19 +++++++-----
 .../StaticAnalyzer/Core/PathSensitive/SVals.h |  7 ++++-
 .../Checkers/DivZeroChecker.cpp               |  5 ++--
 .../StaticAnalyzer/Core/BasicValueFactory.cpp |  2 +-
 clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 17 ++++++-----
 clang/lib/StaticAnalyzer/Core/SValBuilder.cpp |  6 ++--
 clang/lib/StaticAnalyzer/Core/SVals.cpp       |  6 +---
 .../StaticAnalyzer/Core/SimpleSValBuilder.cpp | 30 +++++++++++--------
 10 files changed, 60 insertions(+), 55 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index a1f81716f7c1b..c0ec6e9a25896 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -875,11 +875,8 @@ features cannot lower the translation-unit ABI level;
   literals, simple arithmetic operations, and casts, including to and from
   integer types, are tracked as concrete values instead of being treated as
   unknown. Only results that are exact (independent of rounding mode and
-  evaluation precision) are modeled. Infinities and NaNs remain unknown, because
-  a NaN's bit pattern is non-deterministic. Subnormals remain unknown because
-  their semantics are controlled by factors the analyzer cannot see.
-  ``__ibm128`` is not modeled, since LLVM implements several of its operations
-  through an inaccurate fallback format. Fixes #GH82910.
+  evaluation precision) are modeled. Infinities, NaNs, subnormals remain
+  unknown. (#GH82910)
 
 #### Moved checkers
 
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
index ac4c65d575073..29f50186d9328 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
@@ -98,22 +98,20 @@ class SValExplainer : public FullSValVisitor<SValExplainer, std::string> {
     return Visit(V.getSymbol());
   }
 
-  std::string VisitConcreteInt(nonloc::ConcreteInt V) {
-    const llvm::APSInt &I = V.getValue();
+  std::string VisitConcreteFloat(nonloc::ConcreteFloat V) {
+    const llvm::APFloat &F = *V.getValue();
     std::string Str;
     llvm::raw_string_ostream OS(Str);
-    OS << (I.isSigned() ? "signed " : "unsigned ") << I.getBitWidth()
-       << "-bit integer '" << I << "'";
+    OS << "concrete floating-point value '" << F << "'";
     return Str;
   }
 
-  std::string VisitConcreteFloat(nonloc::ConcreteFloat V) {
-    const llvm::APFloat &F = *V.getValue();
+  std::string VisitConcreteInt(nonloc::ConcreteInt V) {
+    const llvm::APSInt &I = V.getValue();
     std::string Str;
     llvm::raw_string_ostream OS(Str);
-    llvm::SmallString<16> Buf;
-    F.toString(Buf);
-    OS << "concrete floating-point value '" << Buf << "'";
+    OS << (I.isSigned() ? "signed " : "unsigned ") << I.getBitWidth()
+       << "-bit integer '" << I << "'";
     return Str;
   }
 
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h
index 69a8a66653756..7f66fb386ccc7 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h
@@ -14,6 +14,11 @@
 
 namespace clang::ento {
 
+class BasicValueFactory;
+namespace nonloc {
+class ConcreteFloat;
+} // namespace nonloc
+
 /// A safe wrapper around APFloat objects allocated and owned by
 /// \c BasicValueFactory. This just wraps a common llvm::APFloat.
 class APFloatPtr {
@@ -25,14 +30,6 @@ class APFloatPtr {
   APFloatPtr &operator=(const APFloatPtr &) & = default;
   ~APFloatPtr() = default;
 
-  /// You should not use this API.
-  /// If do, ensure that the \p Ptr is not going to dangle.
-  /// Prefer using \c BasicValueFactory::getFloatValue() to get an APFloatPtr
-  /// object.
-  static APFloatPtr unsafeConstructor(const APFloat *Ptr) {
-    return APFloatPtr(Ptr);
-  }
-
   LLVM_ATTRIBUTE_RETURNS_NONNULL
   const APFloat *get() const { return Ptr; }
   /*implicit*/ operator const APFloat &() const { return *get(); }
@@ -41,6 +38,12 @@ class APFloatPtr {
   const APFloat *operator->() const { return Ptr; }
 
 private:
+  /// \p Ptr is owned by \c BasicValueFactory, and \c nonloc::ConcreteFloat
+  /// rewraps a pointer from that factory. Everyone else should use
+  /// \c BasicValueFactory::getFloatValue() to get an APFloatPtr object.
+  friend class BasicValueFactory;
+  friend class nonloc::ConcreteFloat;
+
   explicit APFloatPtr(const APFloat *Ptr) : Ptr(Ptr) {}
 
   /// Owned by \c BasicValueFactory.
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
index a6835c1303765..79752610c74fd 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
@@ -304,12 +304,17 @@ class SymbolVal : public NonLoc {
 };
 
 /// Value representing floating-point constant.
+///
+/// The value wrapped here will always satisfy
+/// \c SValBuilder::isModeledFloatValue() such that it is finite and
+/// non-subnormal. Since infinities and NaNs are unmodeled, testing such values
+/// with \c isZero alone is sufficient to evaluate them in boolean contexts.
 class ConcreteFloat : public NonLoc {
 public:
   explicit ConcreteFloat(APFloatPtr V) : NonLoc(ConcreteFloatKind, V.get()) {}
 
   APFloatPtr getValue() const {
-    return APFloatPtr::unsafeConstructor(castDataAs<llvm::APFloat>());
+    return APFloatPtr(castDataAs<llvm::APFloat>());
   }
 
   static bool classof(SVal V) { return V.getKind() == ConcreteFloatKind; }
diff --git a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
index 3741cc41c5a97..763b1db418f52 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
@@ -91,8 +91,9 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
   if (!B->getRHS()->getType()->isScalarType())
     return;
 
-  // Floating-point divide by zero is defined when floating semantics conform
-  // to IEC 60559.
+  // Now that concrete floats are modeled (and thus can be reasoned about),
+  // bail on floating-point values, since division by zero is well-defined when
+  // semantics conform to IEC 60559.
   if (B->getRHS()->getType()->isRealFloatingType())
     return;
 
diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
index b3d9f8b21b6fa..b0c410c4cde93 100644
--- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
@@ -141,7 +141,7 @@ APFloatPtr BasicValueFactory::getFloatValue(const llvm::APFloat &X) {
     APFloatSet.InsertNode(P, InsertPos);
   }
 
-  return APFloatPtr::unsafeConstructor(&P->getValue());
+  return APFloatPtr(&P->getValue());
 }
 
 const CompoundValData*
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index ebff557f8a3bc..00b65715af022 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -276,9 +276,10 @@ void ExprEngine::VisitCastExpr(const CastExpr *CastE, ExplodedNode *Pred,
         SVal OrigV = State->getSVal(MR);
         // evalCast converts the value, but we are doing a bitcast here, which
         // is unmodeled for floats.
-        if (!OrigV.getAs<nonloc::ConcreteFloat>())
+        if (!OrigV.getAs<nonloc::ConcreteFloat>()) {
           CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV),
                                          CastE->getType(), Ex->getType());
+        }
       }
       Dst.insert(Engine.makeNodeWithBinding(Node, CastE, CastedV));
     }
@@ -958,11 +959,12 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred,
           } else if (Ex->getType()->isRealFloatingType()) {
             // Create a zero with matching semantics to the floating point.
             DefinedOrUnknownSVal X = svalBuilder.makeZeroVal(Ex->getType());
-            if (std::optional<NonLoc> ZeroNL = X.getAs<NonLoc>())
+            if (std::optional<NonLoc> ZeroNL = X.getAs<NonLoc>()) {
               Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), *ZeroNL,
                                  U->getType());
-            else
+            } else {
               Result = UnknownVal();
+            }
           } else if (Ex->getType()->isFloatingType()) {
             // FIXME: handle complex floating point types.
             Result = UnknownVal();
@@ -1044,18 +1046,19 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U,
     SVal RHS;
     SVal Result;
 
-    if (U->getType()->isAnyPointerType())
+    if (U->getType()->isAnyPointerType()) {
       RHS = svalBuilder.makeArrayIndex(1);
-    else if (U->getType()->isIntegralOrEnumerationType())
+    } else if (U->getType()->isIntegralOrEnumerationType()) {
       RHS = svalBuilder.makeIntVal(1, U->getType());
-    else if (U->getType()->isRealFloatingType())
+    } else if (U->getType()->isRealFloatingType()) {
       // C99 6.5.3.1: ++E is equivalent to (E += 1). Then the usual arithmetic
       // conversions convert the 1 to E's type, so just build it as that type
       // here.
       RHS = svalBuilder.makeFloatVal(llvm::APFloat::getOne(
           getContext().getFloatTypeSemantics(U->getType())));
-    else
+    } else {
       RHS = UnknownVal();
+    }
 
     // The use of an operand of type bool with the ++ operators is deprecated
     // but valid until C++17. And if the operand of the ++ operator is of type
diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
index 4956f4bd82c80..6870e27b5b23c 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -227,8 +227,6 @@ DefinedSVal SValBuilder::getConjuredHeapSymbolVal(ConstCFGElementRef elem,
   assert(Loc::isLocType(type));
   assert(SymbolManager::canSymbolicate(type));
   if (type->isNullPtrType()) {
-    // The assert above establishes this is a Loc type, for which makeZeroVal()
-    // always returns a defined value.
     return makeZeroVal(type).castAs<DefinedSVal>();
   }
 
@@ -483,8 +481,8 @@ SVal SValBuilder::evalMinus(NonLoc X) {
   case nonloc::ConcreteIntKind:
     return makeIntVal(-X.castAs<nonloc::ConcreteInt>().getValue());
   case nonloc::ConcreteFloatKind: {
-    // Negation is well-defined regardless of floating-point semantics (it's
-    // just a sign bit flip).
+    // Negation is well-defined regardless of floating-point semantics
+    // (it's just a sign bit flip).
     llvm::APFloat Value = *X.castAs<nonloc::ConcreteFloat>().getValue();
     Value.changeSign();
     return makeFloatVal(Value);
diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp
index afcf81db949d6..f380554c073e7 100644
--- a/clang/lib/StaticAnalyzer/Core/SVals.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp
@@ -34,8 +34,6 @@
 using namespace clang;
 using namespace ento;
 
-/// Returns a human-readable name for the most common floating-point semantics.
-/// Anything else is reported as "unknown."
 static StringRef getFloatSemanticsName(const llvm::fltSemantics &Sem) {
   switch (llvm::APFloat::SemanticsToEnum(Sem)) {
   case llvm::APFloat::S_IEEEhalf:
@@ -357,9 +355,7 @@ void NonLoc::dumpToStream(raw_ostream &os) const {
   switch (getKind()) {
   case nonloc::ConcreteFloatKind: {
     const llvm::APFloat &Value = *castAs<nonloc::ConcreteFloat>().getValue();
-    llvm::SmallString<16> Str;
-    Value.toString(Str);
-    os << Str << ' ' << getFloatSemanticsName(Value.getSemantics());
+    os << Value << ' ' << getFloatSemanticsName(Value.getSemantics());
     break;
   }
   case nonloc::ConcreteIntKind: {
diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
index 17672a26b2b8b..b5333298ff14b 100644
--- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
@@ -427,6 +427,11 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
   NonLoc InputLHS = lhs;
   NonLoc InputRHS = rhs;
 
+  // Default handler for when we give up on folding.
+  auto AsNN = [&] {
+    return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+  };
+
   // Constraints may have changed since the creation of a bound SVal. Check if
   // the values can be simplified based on those new constraints.
   SVal simplifiedLhs = simplifySVal(state, lhs);
@@ -436,9 +441,9 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
   if (auto simplifiedRhsAsNonLoc = simplifiedRhs.getAs<NonLoc>())
     rhs = *simplifiedRhsAsNonLoc;
 
-  // Handle trivial case where left-side and right-side are the same. Exclude
-  // floating points: the ConcreteFloat case below folds these correctly
-  // instead.
+  // Handle trivial case where left-side and right-side are the same.
+  // Exclude floating points: the ConcreteFloat case below folds these
+  // correctly instead.
   if (lhs == rhs && !lhs.getAs<nonloc::ConcreteFloat>())
     switch (op) {
       default:
@@ -521,7 +526,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
               return makeTruthVal(true, resultTy);
             default:
               // This case also handles pointer arithmetic.
-              return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+              return AsNN();
           }
         }
     }
@@ -529,15 +534,14 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
       // Only fold operations between concrete floats that have the same
       // semantics; normally implicit casts are inserted in the AST so they
       // match, but check anyway for robustness.
-      std::optional<nonloc::ConcreteFloat> RHSFloat =
-          rhs.getAs<nonloc::ConcreteFloat>();
+      auto RHSFloat = rhs.getAs<nonloc::ConcreteFloat>();
       if (!RHSFloat)
-        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+        return AsNN();
 
       const llvm::APFloat &L = *lhs.castAs<nonloc::ConcreteFloat>().getValue();
       const llvm::APFloat &R = *RHSFloat->getValue();
       if (&L.getSemantics() != &R.getSemantics())
-        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+        return AsNN();
 
       // We can model comparisons between floats since they are defined for
       // every value regardless of rounding mode or excess precision.
@@ -586,13 +590,13 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
         Status = Result.divide(R, llvm::APFloat::rmNearestTiesToEven);
         break;
       default:
-        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+        return AsNN();
       }
 
       if (Status == llvm::APFloat::opOK && isModeledFloatValue(Result))
         return makeFloatVal(Result);
 
-      return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+      return AsNN();
     }
     case nonloc::ConcreteIntKind: {
       llvm::APSInt LHSValue = lhs.castAs<nonloc::ConcreteInt>().getValue();
@@ -664,7 +668,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
         // 0<<a and 0>>a
         if (LHSValue == 0)
           return evalCast(lhs, resultTy, QualType{});
-        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+        return AsNN();
       case BO_Div:
         // 0 / x == 0
       case BO_Rem:
@@ -673,7 +677,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
           return makeZeroVal(resultTy);
         [[fallthrough]];
       default:
-        return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+        return AsNN();
       }
     }
     case nonloc::SymbolValKind: {
@@ -785,7 +789,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
         return *V;
 
       // Give up -- this is not a symbolic expression we can handle.
-      return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
+      return AsNN();
     }
     }
   }

>From c20bccfe32737f93f0238187bd69ce5bc7a0cf63 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Tue, 25 Aug 2026 00:35:41 +0200
Subject: [PATCH 10/17] Wrap return of isModeledFloatValue with std::optional

---
 .../Core/PathSensitive/SValBuilder.h          | 15 ++++-----
 clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp |  7 ++--
 clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 33 ++++++++++---------
 .../StaticAnalyzer/Core/SimpleSValBuilder.cpp |  6 ++--
 4 files changed, 34 insertions(+), 27 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
index 466926a3474e0..df3ec865ad5d3 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
@@ -290,16 +290,15 @@ class SValBuilder {
                llvm::APFloat::S_PPCDoubleDouble;
   }
 
-  /// Create a concrete floating-point value. The value must satisfy
-  /// \c isModeledFloatValue.
-  nonloc::ConcreteFloat makeFloatVal(const FloatingLiteral *F) {
-    assert(isModeledFloatValue(F->getValue()) &&
-           "ConcreteFloat must be normal and finite");
-    return nonloc::ConcreteFloat(BasicVals.getFloatValue(F->getValue()));
+  /// Create a concrete floating-point value. If the value \p F does not
+  /// satisfy \c isModeledFloatValue, then create \c std::nullopt.
+  std::optional<nonloc::ConcreteFloat> makeFloatVal(const FloatingLiteral *F) {
+    return makeFloatVal(F->getValue());
   }
 
-  nonloc::ConcreteFloat makeFloatVal(const llvm::APFloat &F) {
-    assert(isModeledFloatValue(F) && "ConcreteFloat must be normal and finite");
+  std::optional<nonloc::ConcreteFloat> makeFloatVal(const llvm::APFloat &F) {
+    if (!isModeledFloatValue(F))
+      return std::nullopt;
     return nonloc::ConcreteFloat(BasicVals.getFloatValue(F));
   }
 
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index 00b65715af022..2ab54c8f72803 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -1054,8 +1054,11 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U,
       // C99 6.5.3.1: ++E is equivalent to (E += 1). Then the usual arithmetic
       // conversions convert the 1 to E's type, so just build it as that type
       // here.
-      RHS = svalBuilder.makeFloatVal(llvm::APFloat::getOne(
-          getContext().getFloatTypeSemantics(U->getType())));
+      if (auto One = svalBuilder.makeFloatVal(llvm::APFloat::getOne(
+              getContext().getFloatTypeSemantics(U->getType()))))
+        RHS = *One;
+      else
+        RHS = UnknownVal();
     } else {
       RHS = UnknownVal();
     }
diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
index 6870e27b5b23c..20578e0a0b5c2 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -67,11 +67,10 @@ DefinedOrUnknownSVal SValBuilder::makeZeroVal(QualType type) {
     return makeIntVal(0, type);
 
   if (type->isRealFloatingType()) {
-    llvm::APFloat Zero =
-        llvm::APFloat::getZero(Context.getFloatTypeSemantics(type));
-    if (!isModeledFloatValue(Zero))
-      return UnknownVal();
-    return makeFloatVal(Zero);
+    if (auto Zero = makeFloatVal(
+            llvm::APFloat::getZero(Context.getFloatTypeSemantics(type))))
+      return *Zero;
+    return UnknownVal();
   }
 
   if (type->isArrayType() || type->isRecordType() || type->isVectorType() ||
@@ -391,10 +390,9 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
     return makeIntVal(cast<IntegerLiteral>(E));
 
   case Stmt::FloatingLiteralClass: {
-    const auto *FL = cast<FloatingLiteral>(E);
-    if (!isModeledFloatValue(FL->getValue()))
-      return UnknownVal();
-    return makeFloatVal(FL);
+    if (auto V = makeFloatVal(cast<FloatingLiteral>(E)))
+      return *V;
+    return UnknownVal();
   }
 
   case Stmt::ObjCBoolLiteralExprClass:
@@ -485,7 +483,9 @@ SVal SValBuilder::evalMinus(NonLoc X) {
     // (it's just a sign bit flip).
     llvm::APFloat Value = *X.castAs<nonloc::ConcreteFloat>().getValue();
     Value.changeSign();
-    return makeFloatVal(Value);
+    if (auto Negated = makeFloatVal(Value))
+      return *Negated;
+    return UnknownVal();
   }
   case nonloc::SymbolValKind:
     return makeNonLoc(X.castAs<nonloc::SymbolVal>().getSymbol(), UO_Minus,
@@ -914,8 +914,10 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
       llvm::APFloat Value = *V.getValue();
       bool LosesInfo = false;
       Value.convert(TargetSem, llvm::APFloat::rmNearestTiesToEven, &LosesInfo);
-      if (!LosesInfo && SValBuilder::isModeledFloatValue(Value))
-        return VB.makeFloatVal(Value);
+      if (!LosesInfo) {
+        if (auto Converted = VB.makeFloatVal(Value))
+          return *Converted;
+      }
       return UnknownVal();
     }
 
@@ -972,9 +974,10 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> {
       llvm::APFloat Result(TargetSem);
       llvm::APFloat::opStatus Status = Result.convertFromAPInt(
           Value, Value.isSigned(), llvm::APFloat::rmNearestTiesToEven);
-      if (Status == llvm::APFloat::opOK &&
-          SValBuilder::isModeledFloatValue(Result))
-        return VB.makeFloatVal(Result);
+      if (Status == llvm::APFloat::opOK) {
+        if (auto Converted = VB.makeFloatVal(Result))
+          return *Converted;
+      }
       return UnknownVal();
     }
 
diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
index b5333298ff14b..3f4b2f56bd9b4 100644
--- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
@@ -593,8 +593,10 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
         return AsNN();
       }
 
-      if (Status == llvm::APFloat::opOK && isModeledFloatValue(Result))
-        return makeFloatVal(Result);
+      if (Status == llvm::APFloat::opOK) {
+        if (auto Folded = makeFloatVal(Result))
+          return *Folded;
+      }
 
       return AsNN();
     }

>From 6947bd14563aa837fd38935963baf6f44aa5a05b Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Wed, 26 Aug 2026 17:46:56 +0200
Subject: [PATCH 11/17] Fix how we treat bit casts involving floats

Strengthen the guard to check both to and from cast types in VisitCast.
This guard is needed because otherwise it will try to use evalCast and
convert the value when doing a bit cast which is wrong.
---
 clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp |  7 ++++---
 clang/test/Analysis/builtin_bitcast.cpp       | 18 ++++++++++++++++--
 2 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index 2ab54c8f72803..3172ec72b8385 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -274,9 +274,10 @@ void ExprEngine::VisitCastExpr(const CastExpr *CastE, ExplodedNode *Pred,
 
       if (const MemRegion *MR = State->getSVal(Ex, SF).getAsRegion()) {
         SVal OrigV = State->getSVal(MR);
-        // evalCast converts the value, but we are doing a bitcast here, which
-        // is unmodeled for floats.
-        if (!OrigV.getAs<nonloc::ConcreteFloat>()) {
+        // evalCast converts the value, but we are doing a bitcast here,
+        // which coincide only if no floats are involved.
+        if (!Ex->getType()->isFloatingType() &&
+            !CastE->getType()->isFloatingType()) {
           CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV),
                                          CastE->getType(), Ex->getType());
         }
diff --git a/clang/test/Analysis/builtin_bitcast.cpp b/clang/test/Analysis/builtin_bitcast.cpp
index bcaec9ecc3096..ebac68264c554 100644
--- a/clang/test/Analysis/builtin_bitcast.cpp
+++ b/clang/test/Analysis/builtin_bitcast.cpp
@@ -13,8 +13,8 @@ void test(int i) {
 
   float f = 42;
 
-  // Loading from a floating point value results in unknown,
-  // which later materializes as a conjured value.
+  // A bit cast involving a float is unknown, which later materializes as a
+  // conjured value.
   auto g = __builtin_bit_cast(unsigned int, f);
   clang_analyzer_dump(g);
   // expected-warning-re at -1 {{{{^conj_\$[0-9]+{unsigned int,}}}}
@@ -32,6 +32,20 @@ void test(int i) {
   // expected-warning at -1 {{&i [as 64 bit integer]}}
 }
 
+// Bit casts involving floats should be declined in both directions. Dumping to
+// avoid the conjured value.
+void test_float_bitcast(int i) {
+  int one = 1;
+  clang_analyzer_dump(__builtin_bit_cast(float, one));
+  // expected-warning at -1 {{Unknown}}
+  clang_analyzer_dump(__builtin_bit_cast(float, i));
+  // expected-warning at -1 {{Unknown}}
+
+  float f = 1.5f;
+  clang_analyzer_dump(__builtin_bit_cast(unsigned int, f));
+  // expected-warning at -1 {{Unknown}}
+}
+
 struct A {
   int n;
   void set(int x) {

>From 3186a36102ba498f9070ba349a86feb1c324624d Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 31 Aug 2026 19:28:47 +0200
Subject: [PATCH 12/17] Improve and clean tests, add negative float->uint test

---
 clang/test/Analysis/constant-float-16bit.c    |  39 ----
 clang/test/Analysis/constant-float-16bit.cpp  |  35 ++++
 .../Analysis/constant-float-32bit-double.c    |  25 ---
 .../Analysis/constant-float-32bit-double.cpp  |  23 +++
 clang/test/Analysis/constant-float-cxx.cpp    |  35 ++--
 ...ouble.c => constant-float-long-double.cpp} |  38 ++--
 clang/test/Analysis/constant-float.c          | 182 ------------------
 clang/test/Analysis/constant-float.cpp        | 180 +++++++++++++++++
 8 files changed, 272 insertions(+), 285 deletions(-)
 delete mode 100644 clang/test/Analysis/constant-float-16bit.c
 create mode 100644 clang/test/Analysis/constant-float-16bit.cpp
 delete mode 100644 clang/test/Analysis/constant-float-32bit-double.c
 create mode 100644 clang/test/Analysis/constant-float-32bit-double.cpp
 rename clang/test/Analysis/{constant-float-long-double.c => constant-float-long-double.cpp} (58%)
 delete mode 100644 clang/test/Analysis/constant-float.c
 create mode 100644 clang/test/Analysis/constant-float.cpp

diff --git a/clang/test/Analysis/constant-float-16bit.c b/clang/test/Analysis/constant-float-16bit.c
deleted file mode 100644
index 999a036e1e7c9..0000000000000
--- a/clang/test/Analysis/constant-float-16bit.c
+++ /dev/null
@@ -1,39 +0,0 @@
-// _Float16 and __bf16 are both 16 bits wide but have different semantics, so
-// the same bit pattern denotes different values in each.
-//
-// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify %s
-// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify %s
-
-void clang_analyzer_dump_half(_Float16);
-void clang_analyzer_dump_bfloat(__bf16);
-void clang_analyzer_dumpSvalType_half(_Float16);
-void clang_analyzer_dumpSvalType_bfloat(__bf16);
-void clang_analyzer_eval(int);
-
-void test16BitValues(void) {
-  clang_analyzer_dump_half((_Float16)1.5f);         // expected-warning{{1.5 IEEEhalf}}
-  clang_analyzer_dump_bfloat((__bf16)1.5f);         // expected-warning{{1.5 BFloat}}
-  clang_analyzer_dumpSvalType_half((_Float16)1.5f); // expected-warning{{_Float16}}
-  clang_analyzer_dumpSvalType_bfloat((__bf16)1.5f); // expected-warning{{__bf16}}
-}
-
-// 0x3C00 is 1.0 as an IEEEhalf and 0.0078125 as a BFloat. Ensure conversions
-// between them are respected.
-void testSameBitsDifferentSemantics(void) {
-  clang_analyzer_dump_bfloat((__bf16)0.0078125f); // expected-warning{{0.007813 BFloat}}
-  _Float16 a = (_Float16)1.0f;
-  clang_analyzer_dump_half(a);                    // expected-warning{{1 IEEEhalf}}
-  clang_analyzer_dump_half(a + a);                // expected-warning{{2 IEEEhalf}}
-  clang_analyzer_eval(a + a == (_Float16)2.0f);   // expected-warning{{TRUE}}
-}
-
-// 1 + 2^-9 needs 10 mantissa bits, which fits an IEEEhalf but not a BFloat, so
-// result depends on rounding semantics => unmodeled.
-void testExactnessIsPerSemantics(void) {
-  clang_analyzer_dump_half((_Float16)1.001953125f); // expected-warning{{1.002 IEEEhalf}}
-  clang_analyzer_dump_bfloat((__bf16)1.001953125f); // expected-warning{{Unknown}}
-}
diff --git a/clang/test/Analysis/constant-float-16bit.cpp b/clang/test/Analysis/constant-float-16bit.cpp
new file mode 100644
index 0000000000000..d81ac1c1df338
--- /dev/null
+++ b/clang/test/Analysis/constant-float-16bit.cpp
@@ -0,0 +1,35 @@
+// _Float16 and __bf16 are both 16 bits wide but have different semantics, so
+// the same bit pattern denotes different values in each.
+//
+// DEFINE: %{analyze}= %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection
+//
+// RUN: %{analyze} -triple x86_64-unknown-linux-gnu -verify %s
+// RUN: %{analyze} -triple aarch64-unknown-linux-gnu -verify %s
+
+template <typename T> void clang_analyzer_dump(T);
+template <typename T> void clang_analyzer_dumpSvalType(T);
+void clang_analyzer_eval(bool);
+
+void test16BitValues(void) {
+  clang_analyzer_dump((_Float16)1.5f);          // expected-warning{{1.5 IEEEhalf}}
+  clang_analyzer_dump((__bf16)1.5f);            // expected-warning{{1.5 BFloat}}
+  clang_analyzer_dumpSvalType((_Float16)1.5f);  // expected-warning{{_Float16}}
+  clang_analyzer_dumpSvalType((__bf16)1.5f);    // expected-warning{{__bf16}}
+}
+
+// 0x3C00 is 1.0 as an IEEEhalf and 0.0078125 as a BFloat. Ensure conversions
+// between them are respected.
+void testSameBitsDifferentSemantics(void) {
+  clang_analyzer_dump((__bf16)0.0078125f);      // expected-warning{{0.007813 BFloat}}
+  _Float16 a = (_Float16)1.0f;
+  clang_analyzer_dump(a);                       // expected-warning{{1 IEEEhalf}}
+  clang_analyzer_dump(a + a);                   // expected-warning{{2 IEEEhalf}}
+  clang_analyzer_eval(a + a == (_Float16)2.0f); // expected-warning{{TRUE}}
+}
+
+// 1 + 2^-9 needs 10 mantissa bits, which fits an IEEEhalf but not a BFloat, so
+// result depends on rounding semantics => unmodeled.
+void testExactnessIsPerSemantics(void) {
+  clang_analyzer_dump((_Float16)1.001953125f);  // expected-warning{{1.002 IEEEhalf}}
+  clang_analyzer_dump((__bf16)1.001953125f);    // expected-warning{{Unknown}}
+}
diff --git a/clang/test/Analysis/constant-float-32bit-double.c b/clang/test/Analysis/constant-float-32bit-double.c
deleted file mode 100644
index 607a55e065443..0000000000000
--- a/clang/test/Analysis/constant-float-32bit-double.c
+++ /dev/null
@@ -1,25 +0,0 @@
-// We can't assume a double will be wider than a float.
-//
-// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -mdouble=32 \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify %s
-
-void clang_analyzer_dump_float(float);
-void clang_analyzer_dump_double(double);
-void clang_analyzer_dumpSvalType_double(double);
-void clang_analyzer_eval(int);
-
-void testDoubleIsSingle(void) {
-  clang_analyzer_dump_double(3.14);         // expected-warning{{3.1400001 IEEEsingle}}
-  clang_analyzer_dumpSvalType_double(3.14); // expected-warning{{float}}
-}
-
-void testConversionsAreExact(void) {
-  clang_analyzer_dump_float((float)3.14);   // expected-warning{{3.1400001 IEEEsingle}}
-  clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEsingle}}
-  clang_analyzer_eval(3.14 == 3.14f);       // expected-warning{{TRUE}}
-}
-
-void testInexactStillUnmodeled(void) {
-  clang_analyzer_dump_double(0.1 + 0.2);  // expected-warning{{Unknown}}
-}
diff --git a/clang/test/Analysis/constant-float-32bit-double.cpp b/clang/test/Analysis/constant-float-32bit-double.cpp
new file mode 100644
index 0000000000000..c0d601fbeb0c1
--- /dev/null
+++ b/clang/test/Analysis/constant-float-32bit-double.cpp
@@ -0,0 +1,23 @@
+// We can't assume a double will be wider than a float.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -mdouble=32 \
+// RUN:   -analyzer-checker=core,debug.ExprInspection -verify %s
+
+template <typename T> void clang_analyzer_dump(T);
+template <typename T> void clang_analyzer_dumpSvalType(T);
+void clang_analyzer_eval(bool);
+
+void testDoubleIsSingle(void) {
+  clang_analyzer_dump(3.14);          // expected-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dumpSvalType(3.14);  // expected-warning{{float}}
+}
+
+void testConversionsAreExact(void) {
+  clang_analyzer_dump((float)3.14);   // expected-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dump((double)1.5f);  // expected-warning{{1.5 IEEEsingle}}
+  clang_analyzer_eval(3.14 == 3.14f); // expected-warning{{TRUE}}
+}
+
+void testInexactStillUnmodeled(void) {
+  clang_analyzer_dump(0.1 + 0.2); // expected-warning{{Unknown}}
+}
diff --git a/clang/test/Analysis/constant-float-cxx.cpp b/clang/test/Analysis/constant-float-cxx.cpp
index 9b14ab559b263..6788046fe2739 100644
--- a/clang/test/Analysis/constant-float-cxx.cpp
+++ b/clang/test/Analysis/constant-float-cxx.cpp
@@ -1,16 +1,14 @@
 // RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify %s
+// RUN:   -analyzer-checker=core,debug.ExprInspection -verify %s
 
-void clang_analyzer_dump_float(float);
-void clang_analyzer_dump_double(double);
-void clang_analyzer_eval(int);
+template <typename T> void clang_analyzer_dump(T);
+void clang_analyzer_eval(bool);
 
 template <typename T> T twice(T x) { return x + x; }
 
-struct Vec {
+struct Container {
   float v;
-  Vec operator+(Vec o) const { return Vec{v + o.v}; }
+  Container operator+(Container o) const { return Container{v + o.v}; }
 };
 
 constexpr double half(double x) { return x / 2.0; }
@@ -19,30 +17,33 @@ double defaultArg(double i = 42) { return i; }
 float narrowingDefaultArg(float i = 1.5) { return i; }
 
 void testTemplate() {
-  clang_analyzer_dump_float(twice(1.5f)); // expected-warning{{3 IEEEsingle}}
-  clang_analyzer_dump_double(twice(2.5)); // expected-warning{{5 IEEEdouble}}
+  clang_analyzer_dump(twice(1.5f)); // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump(twice(2.5));  // expected-warning{{5 IEEEdouble}}
 }
 
 void testOverloadedOperator() {
-  Vec a{1.5f}, b{2.5f};
-  clang_analyzer_dump_float((a + b).v); // expected-warning{{4 IEEEsingle}}
+  Container a{1.5f}, b{2.5f};
+  clang_analyzer_dump((a + b).v); // expected-warning{{4 IEEEsingle}}
 }
 
 void testConstexpr() {
-  clang_analyzer_dump_double(half(3.0));  // expected-warning{{1.5 IEEEdouble}}
+  clang_analyzer_dump(half(3.0)); // expected-warning{{1.5 IEEEdouble}}
   constexpr double c = 1.25;
-  clang_analyzer_dump_double(c);          // expected-warning{{1.25 IEEEdouble}}
+  clang_analyzer_dump(c);         // expected-warning{{1.25 IEEEdouble}}
 }
 
 // A default argument is not evaluated through the CFG, rather it is folded by
 // getConstantVal.
 void testDefaultArgument() {
-  clang_analyzer_dump_double(defaultArg());         // expected-warning{{42 IEEEdouble}}
-  clang_analyzer_dump_float(narrowingDefaultArg()); // expected-warning{{1.5 IEEEsingle}}
+  clang_analyzer_dump(defaultArg());          // expected-warning{{42 IEEEdouble}}
+  clang_analyzer_dump(narrowingDefaultArg()); // expected-warning{{1.5 IEEEsingle}}
 }
 
-// A negative value does not fit an unsigned integer; the conversion should be
-// unmodeled.
+// Negative float -> int is well-defined ([conv.fpint], C11 6.3.1.4) by
+// discarding the fractional part, but only if the integral part can be
+// represented in the destination type (otherwise UB).
 void testUnsignedFromNegative() {
   clang_analyzer_eval((unsigned)-1.5f == 0);  // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval((unsigned)-0.5f == 0);  // expected-warning{{TRUE}}
+  clang_analyzer_eval((int)-1.5f == -1);      // expected-warning{{TRUE}}
 }
diff --git a/clang/test/Analysis/constant-float-long-double.c b/clang/test/Analysis/constant-float-long-double.cpp
similarity index 58%
rename from clang/test/Analysis/constant-float-long-double.c
rename to clang/test/Analysis/constant-float-long-double.cpp
index 87e56eb444196..c3523d710789e 100644
--- a/clang/test/Analysis/constant-float-long-double.c
+++ b/clang/test/Analysis/constant-float-long-double.cpp
@@ -1,25 +1,19 @@
 // Multiple targets needed because long double semantics differ on them.
 //
-// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify=x87 %s
-// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify=quad %s
-// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify=ldbl64 %s
-// RUN: %clang_analyze_cc1 -triple powerpc64le-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection \
-// RUN:   -analyzer-config eagerly-assume=false -verify=ibm128 %s
+// DEFINE: %{analyze}= %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection
+//
+// RUN: %{analyze} -triple x86_64-unknown-linux-gnu -verify=x87 %s
+// RUN: %{analyze} -triple aarch64-unknown-linux-gnu -verify=quad %s
+// RUN: %{analyze} -triple x86_64-pc-windows-msvc -verify=ldbl64 %s
+// RUN: %{analyze} -triple powerpc64le-unknown-linux-gnu -verify=ibm128 %s
 
-void clang_analyzer_dump_longdouble(long double);
-void clang_analyzer_dumpSvalType_longdouble(long double);
-void clang_analyzer_eval(int);
+template <typename T> void clang_analyzer_dump(T);
+template <typename T> void clang_analyzer_dumpSvalType(T);
+void clang_analyzer_eval(bool);
 
 void testVariables(void) {
   long double ld = 1.5L;
-  clang_analyzer_dump_longdouble(ld);
+  clang_analyzer_dump(ld);
   // x87-warning at -1{{1.5 x87DoubleExtended}}
   // quad-warning at -2{{1.5 IEEEquad}}
   // ldbl64-warning at -3{{1.5 IEEEdouble}}
@@ -29,12 +23,12 @@ void testVariables(void) {
 // long double has different semantics depending on target. IBM double-double
 // should not be modeled.
 void testLongDouble(void) {
-  clang_analyzer_dump_longdouble(1.0L);
+  clang_analyzer_dump(1.0L);
   // x87-warning at -1{{1 x87DoubleExtended}}
   // quad-warning at -2{{1 IEEEquad}}
   // ldbl64-warning at -3{{1 IEEEdouble}}
   // ibm128-warning at -4{{Unknown}}
-  clang_analyzer_dumpSvalType_longdouble(1.0L);
+  clang_analyzer_dumpSvalType(1.0L);
   // x87-warning at -1{{long double}}
   // quad-warning at -2{{__float128}}
   // ldbl64-warning at -3{{double}}
@@ -42,12 +36,12 @@ void testLongDouble(void) {
 }
 
 void testLongDoubleArithmetic(void) {
-  clang_analyzer_dump_longdouble(1.0L + 2.0L);
+  clang_analyzer_dump(1.0L + 2.0L);
   // x87-warning at -1{{3 x87DoubleExtended}}
   // quad-warning at -2{{3 IEEEquad}}
   // ldbl64-warning at -3{{3 IEEEdouble}}
   // ibm128-warning at -4{{Unknown}}
-  clang_analyzer_dump_longdouble(3.0L / 4.0L);
+  clang_analyzer_dump(3.0L / 4.0L);
   // x87-warning at -1{{0.75 x87DoubleExtended}}
   // quad-warning at -2{{0.75 IEEEquad}}
   // ldbl64-warning at -3{{0.75 IEEEdouble}}
@@ -60,7 +54,7 @@ void testLongDoubleArithmetic(void) {
 }
 
 void testIntToFloat(void) {
-  clang_analyzer_dump_longdouble((long double)1);
+  clang_analyzer_dump((long double)1);
   // x87-warning at -1{{1 x87DoubleExtended}}
   // quad-warning at -2{{1 IEEEquad}}
   // ldbl64-warning at -3{{1 IEEEdouble}}
@@ -70,7 +64,7 @@ void testIntToFloat(void) {
 // IBM double-double should not be created on the makeZeroVal build path.
 void testZeroInitialized(void) {
   static long double s;
-  clang_analyzer_dump_longdouble(s);
+  clang_analyzer_dump(s);
   // x87-warning at -1{{0 x87DoubleExtended}}
   // quad-warning at -2{{0 IEEEquad}}
   // ldbl64-warning at -3{{0 IEEEdouble}}
diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c
deleted file mode 100644
index 678dcaa84e2e4..0000000000000
--- a/clang/test/Analysis/constant-float.c
+++ /dev/null
@@ -1,182 +0,0 @@
-// Disable -Wliteral-range since we intentionally induce inf.
-//
-// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
-// RUN:   -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
-// RUN:   -analyzer-config eagerly-assume=false -verify %s
-//
-// Only exact results are modeled so the analyzer's behavior should not change
-// under a dynamic rounding mode.
-//
-// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -frounding-math \
-// RUN:   -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
-// RUN:   -analyzer-config eagerly-assume=false -verify %s
-
-void clang_analyzer_dump_float(float);
-void clang_analyzer_dump_double(double);
-void clang_analyzer_dump_int(int);
-void clang_analyzer_eval(int);
-
-void testLiterals(void) {
-  clang_analyzer_dump_float(0.0f);  // expected-warning{{0 IEEEsingle}}
-  clang_analyzer_dump_float(3.14f); // expected-warning{{3.1400001 IEEEsingle}}
-  clang_analyzer_dump_double(3.14); // expected-warning{{3.1400000000000001 IEEEdouble}}
-}
-
-void testVariables(void) {
-  float f = 1.5f;
-  clang_analyzer_dump_float(f); // expected-warning{{1.5 IEEEsingle}}
-}
-
-void testUnknown(float f) {
-  clang_analyzer_dump_float(f);         // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(f + 1.0f);  // expected-warning{{Unknown}}
-}
-
-// Exactly representable results are independent of rounding mode and
-// evaluation precision.
-void testArithmetic(void) {
-  clang_analyzer_dump_float(1.0f + 2.0f); // expected-warning{{3 IEEEsingle}}
-  clang_analyzer_dump_float(5.0f - 1.5f); // expected-warning{{3.5 IEEEsingle}}
-  clang_analyzer_dump_float(1.5f * 2.0f); // expected-warning{{3 IEEEsingle}}
-  clang_analyzer_dump_float(3.0f / 4.0f); // expected-warning{{0.75 IEEEsingle}}
-  clang_analyzer_dump_double(0.5 + 0.25); // expected-warning{{0.75 IEEEdouble}}
-  clang_analyzer_dump_float(0.1f + 0.2f); // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(1.0f / 3.0f); // expected-warning{{Unknown}}
-}
-
-// Comparisons are exact for every value, so all six predicates should fold.
-void testComparisons(void) {
-  clang_analyzer_eval(1.0f < 2.0f);   // expected-warning{{TRUE}}
-  clang_analyzer_eval(1.0f > 2.0f);   // expected-warning{{FALSE}}
-  clang_analyzer_eval(2.0f <= 2.0f);  // expected-warning{{TRUE}}
-  clang_analyzer_eval(2.0f >= 2.0f);  // expected-warning{{TRUE}}
-  clang_analyzer_eval(1.5 == 1.5);    // expected-warning{{TRUE}}
-  clang_analyzer_eval(1.5 != 2.5);    // expected-warning{{TRUE}}
-}
-
-// Unary negation can be modeled because of sign-bit.
-void testNegation(void) {
-  clang_analyzer_dump_float(-1.5f);     // expected-warning{{-1.5 IEEEsingle}}
-  clang_analyzer_dump_float(-(-1.5f));  // expected-warning{{1.5 IEEEsingle}}
-  clang_analyzer_dump_float(-0.0f);     // expected-warning{{-0 IEEEsingle}}
-  clang_analyzer_eval(0.0f == -0.0f);   // expected-warning{{TRUE}}
-}
-
-// Conversions between floating points should be modeled only when they are
-// exact. 3.14 stored in a double sets mantissa bits a float cannot hold, and
-// rounding direction can change at runtime.
-void testFloatConversions(void) {
-  clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEdouble}}
-  clang_analyzer_dump_float((float)3.14);   // expected-warning{{Unknown}}
-}
-
-// Type punning reinterprets the bits, whereas we only model conversions of the
-// value, so decline it in both directions.
-void testTypePunning(void) {
-  float f = 1.5f;
-  clang_analyzer_dump_int(*(int *)&f);     // expected-warning{{Unknown}}
-  int i = 5;
-  clang_analyzer_dump_float(*(float *)&i); // expected-warning{{Unknown}}
-}
-
-// Casts to bool is defined as a comparison to zero.
-void testFloatToBool(void) {
-  clang_analyzer_eval((_Bool)0.5f);   // expected-warning{{TRUE}}
-  clang_analyzer_eval((_Bool)2.0f);   // expected-warning{{TRUE}}
-  clang_analyzer_eval((_Bool)-3.0f);  // expected-warning{{TRUE}}
-  clang_analyzer_eval((_Bool)0.0f);   // expected-warning{{FALSE}}
-  clang_analyzer_eval((_Bool)-0.0f);  // expected-warning{{FALSE}}
-}
-
-// !E is defined as (0 == E) with the zero converted to the type of expr. E.
-void testLogicalNot(void) {
-  float nonzero = 0.5f, zero = 0.0f, negzero = -0.0f;
-  clang_analyzer_eval(!nonzero);  // expected-warning{{FALSE}}
-  clang_analyzer_eval(!zero);     // expected-warning{{TRUE}}
-  clang_analyzer_eval(!negzero);  // expected-warning{{TRUE}}
-}
-
-// Casts to integers discard fractional bits, which should be unmodeled when
-// the result is out of range.
-void testFloatToInt(void) {
-  clang_analyzer_eval((int)1.9f == 1);          // expected-warning{{TRUE}}
-  clang_analyzer_eval((int)-1.9 == -1);         // expected-warning{{TRUE}}
-  clang_analyzer_dump_float((float)(int)1e30f); // expected-warning{{Unknown}}
-}
-
-// Infinities and NaNs should not be modeled.
-void testNonFiniteIsUnmodeled(void) {
-  clang_analyzer_dump_float(1e400f);                // expected-warning{{Unknown}}
-  clang_analyzer_dump_float((float)1e300);          // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(__FLT_MAX__ * 2.0f);    // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(__builtin_inff());      // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(__builtin_huge_valf()); // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(__builtin_nanf(""));    // expected-warning{{Unknown}}
-  clang_analyzer_dump_double(0.0 / 0.0);            // expected-warning{{Unknown}}
-  clang_analyzer_dump_double(1.0 / 0.0);            // expected-warning{{Unknown}}
-}
-
-void testSelfArithmetic(void) {
-  float f = 1.5f;
-  clang_analyzer_dump_float(f - f); // expected-warning{{0 IEEEsingle}}
-  clang_analyzer_eval(f == f);      // expected-warning{{TRUE}}
-}
-
-// Subnormals should not be modeled.
-void testSubnormals(void) {
-  clang_analyzer_dump_float(__FLT_DENORM_MIN__);         // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(__FLT_MIN__ / 2.0f);         // expected-warning{{Unknown}}
-  clang_analyzer_dump_float(__FLT_MIN__ * __FLT_MIN__);  // expected-warning{{Unknown}}
-  clang_analyzer_dump_float((float)(double)__FLT_DENORM_MIN__);
-  // expected-warning at -1{{Unknown}}
-  clang_analyzer_dump_float(__FLT_MIN__);
-  // expected-warning at -1{{1.17549435E-38 IEEEsingle}}
-}
-
-// Integer to float conversions should only be modeled when the conversion is
-// exact.
-void testIntToFloat(void) {
-  float f = 1;
-  clang_analyzer_dump_float(f);                // expected-warning{{1 IEEEsingle}}
-  clang_analyzer_dump_float(1.0f + 1);         // expected-warning{{2 IEEEsingle}}
-  clang_analyzer_dump_float((float)-3);        // expected-warning{{-3 IEEEsingle}}
-  clang_analyzer_dump_float((float)16777216);  // expected-warning{{16777216 IEEEsingle}}
-  clang_analyzer_dump_float((float)16777217);  // expected-warning{{Unknown}}
-
-  // 2^26 can be represented, 2^26 + 1 cannot.
-  long yes = 1L << 26;
-  long no = yes + 1L;
-  clang_analyzer_dump_float((float)yes);  // expected-warning{{67108864 IEEEsingle}}
-  clang_analyzer_dump_float((float)no);   // expected-warning{{Unknown}}
-}
-
-// Complex floating-point types are not modeled.
-void testComplexIsUnmodeled(void) {
-  _Complex float z = 1.5f;
-  clang_analyzer_dump_float(__real__ z); // expected-warning{{Unknown}}
-  clang_analyzer_eval(!z);               // expected-warning{{UNKNOWN}}
-}
-
-// Inc/decrement operators compute through the same path as += and -=
-// respectively in the analyzer.
-void testIncrementDecrement(void) {
-  float g = 2.0f;
-  g += 1.0f;
-  clang_analyzer_dump_float(g);   // expected-warning{{3 IEEEsingle}}
-  --g;
-  clang_analyzer_dump_float(g);   // expected-warning{{2 IEEEsingle}}
-  clang_analyzer_dump_float(g++); // expected-warning{{2 IEEEsingle}}
-  clang_analyzer_dump_float(g);   // expected-warning{{3 IEEEsingle}}
-
-  float small = 0.5f;
-  ++small;
-  clang_analyzer_dump_float(small); // expected-warning{{1.5 IEEEsingle}}
-
-  float inexact = 0.1f;
-  ++inexact;
-  clang_analyzer_dump_float(inexact); // expected-warning{{Unknown}}
-
-  float max = __FLT_MAX__;
-  ++max;
-  clang_analyzer_dump_float(max); // expected-warning{{Unknown}}
-}
diff --git a/clang/test/Analysis/constant-float.cpp b/clang/test/Analysis/constant-float.cpp
new file mode 100644
index 0000000000000..19892d638a3eb
--- /dev/null
+++ b/clang/test/Analysis/constant-float.cpp
@@ -0,0 +1,180 @@
+// Disable -Wliteral-range since we intentionally induce inf.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
+// RUN:   -verify %s
+//
+// Only exact results are modeled so the analyzer's behavior should not change
+// under a dynamic rounding mode.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -frounding-math \
+// RUN:   -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
+// RUN:   -verify %s
+
+template <typename T> void clang_analyzer_dump(T);
+void clang_analyzer_eval(bool);
+
+void testLiterals(void) {
+  clang_analyzer_dump(0.0f);  // expected-warning{{0 IEEEsingle}}
+  clang_analyzer_dump(3.14f); // expected-warning{{3.1400001 IEEEsingle}}
+  clang_analyzer_dump(3.14);  // expected-warning{{3.1400000000000001 IEEEdouble}}
+}
+
+void testVariables(void) {
+  float f = 1.5f;
+  clang_analyzer_dump(f); // expected-warning{{1.5 IEEEsingle}}
+}
+
+void testUnknown(float f) {
+  clang_analyzer_dump(f);         // expected-warning{{Unknown}}
+  clang_analyzer_dump(f + 1.0f);  // expected-warning{{Unknown}}
+}
+
+// Exactly representable results are independent of rounding mode and
+// evaluation precision.
+void testArithmetic(void) {
+  clang_analyzer_dump(1.0f + 2.0f); // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump(5.0f - 1.5f); // expected-warning{{3.5 IEEEsingle}}
+  clang_analyzer_dump(1.5f * 2.0f); // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump(3.0f / 4.0f); // expected-warning{{0.75 IEEEsingle}}
+  clang_analyzer_dump(0.5 + 0.25);  // expected-warning{{0.75 IEEEdouble}}
+  clang_analyzer_dump(0.1f + 0.2f); // expected-warning{{Unknown}}
+  clang_analyzer_dump(1.0f / 3.0f); // expected-warning{{Unknown}}
+}
+
+// Comparisons are exact for every value, so all six predicates should fold.
+void testComparisons(void) {
+  clang_analyzer_eval(1.0f < 2.0f);   // expected-warning{{TRUE}}
+  clang_analyzer_eval(1.0f > 2.0f);   // expected-warning{{FALSE}}
+  clang_analyzer_eval(2.0f <= 2.0f);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(2.0f >= 2.0f);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(1.5 == 1.5);    // expected-warning{{TRUE}}
+  clang_analyzer_eval(1.5 != 2.5);    // expected-warning{{TRUE}}
+}
+
+// Unary negation can be modeled because of sign-bit.
+void testNegation(void) {
+  clang_analyzer_dump(-1.5f);         // expected-warning{{-1.5 IEEEsingle}}
+  clang_analyzer_dump(-(-1.5f));      // expected-warning{{1.5 IEEEsingle}}
+  clang_analyzer_dump(-0.0f);         // expected-warning{{-0 IEEEsingle}}
+  clang_analyzer_eval(0.0f == -0.0f); // expected-warning{{TRUE}}
+}
+
+// Conversions between floating points should be modeled only when they are
+// exact. 3.14 stored in a double sets mantissa bits a float cannot hold, and
+// rounding direction can change at runtime.
+void testFloatConversions(void) {
+  clang_analyzer_dump((double)1.5f);  // expected-warning{{1.5 IEEEdouble}}
+  clang_analyzer_dump((float)3.14);   // expected-warning{{Unknown}}
+}
+
+// Type punning reinterprets the bits, whereas we only model conversions of the
+// value, so decline it in both directions.
+void testTypePunning(void) {
+  float f = 1.5f;
+  clang_analyzer_dump(*(int *)&f);    // expected-warning{{Unknown}}
+  int i = 5;
+  clang_analyzer_dump(*(float *)&i);  // expected-warning{{Unknown}}
+}
+
+// Casts to bool is defined as a comparison to zero.
+void testFloatToBool(void) {
+  clang_analyzer_eval((bool)0.5f);  // expected-warning{{TRUE}}
+  clang_analyzer_eval((bool)2.0f);  // expected-warning{{TRUE}}
+  clang_analyzer_eval((bool)-3.0f); // expected-warning{{TRUE}}
+  clang_analyzer_eval((bool)0.0f);  // expected-warning{{FALSE}}
+  clang_analyzer_eval((bool)-0.0f); // expected-warning{{FALSE}}
+}
+
+// !E is defined as (0 == E) with the zero converted to the type of expr. E.
+void testLogicalNot(void) {
+  float nonzero = 0.5f, zero = 0.0f, negzero = -0.0f;
+  clang_analyzer_eval(!nonzero);  // expected-warning{{FALSE}}
+  clang_analyzer_eval(!zero);     // expected-warning{{TRUE}}
+  clang_analyzer_eval(!negzero);  // expected-warning{{TRUE}}
+}
+
+// Casts to integers discard fractional bits, which should be unmodeled when
+// the result is out of range.
+void testFloatToInt(void) {
+  clang_analyzer_eval((int)1.9f == 1);    // expected-warning{{TRUE}}
+  clang_analyzer_eval((int)-1.9 == -1);   // expected-warning{{TRUE}}
+  clang_analyzer_dump((float)(int)1e30f); // expected-warning{{Unknown}}
+}
+
+// Infinities and NaNs should not be modeled.
+void testNonFiniteIsUnmodeled(void) {
+  clang_analyzer_dump(1e400f);                // expected-warning{{Unknown}}
+  clang_analyzer_dump((float)1e300);          // expected-warning{{Unknown}}
+  clang_analyzer_dump(__FLT_MAX__ * 2.0f);    // expected-warning{{Unknown}}
+  clang_analyzer_dump(__builtin_inff());      // expected-warning{{Unknown}}
+  clang_analyzer_dump(__builtin_huge_valf()); // expected-warning{{Unknown}}
+  clang_analyzer_dump(__builtin_nanf(""));    // expected-warning{{Unknown}}
+  clang_analyzer_dump(0.0 / 0.0);             // expected-warning{{Unknown}}
+  clang_analyzer_dump(1.0 / 0.0);             // expected-warning{{Unknown}}
+}
+
+void testSelfArithmetic(void) {
+  float f = 1.5f;
+  clang_analyzer_dump(f - f);   // expected-warning{{0 IEEEsingle}}
+  clang_analyzer_eval(f == f);  // expected-warning{{TRUE}}
+}
+
+// Subnormals should not be modeled.
+void testSubnormals(void) {
+  clang_analyzer_dump(__FLT_DENORM_MIN__);        // expected-warning{{Unknown}}
+  clang_analyzer_dump(__FLT_MIN__ / 2.0f);        // expected-warning{{Unknown}}
+  clang_analyzer_dump(__FLT_MIN__ * __FLT_MIN__); // expected-warning{{Unknown}}
+  clang_analyzer_dump((float)(double)__FLT_DENORM_MIN__);
+  // expected-warning at -1{{Unknown}}
+  clang_analyzer_dump(__FLT_MIN__);
+  // expected-warning at -1{{1.17549435E-38 IEEEsingle}}
+}
+
+// Integer to float conversions should only be modeled when the conversion
+// is exact.
+void testIntToFloat(void) {
+  float f = 1;
+  clang_analyzer_dump(f);               // expected-warning{{1 IEEEsingle}}
+  clang_analyzer_dump(1.0f + 1);        // expected-warning{{2 IEEEsingle}}
+  clang_analyzer_dump((float)-3);       // expected-warning{{-3 IEEEsingle}}
+  clang_analyzer_dump((float)16777216); // expected-warning{{16777216 IEEEsingle}}
+  clang_analyzer_dump((float)16777217); // expected-warning{{Unknown}}
+
+  // 2^26 can be represented, 2^26 + 1 cannot.
+  long yes = 1L << 26;
+  long no = yes + 1L;
+  clang_analyzer_dump((float)yes);  // expected-warning{{67108864 IEEEsingle}}
+  clang_analyzer_dump((float)no);   // expected-warning{{Unknown}}
+}
+
+// Complex floating-point types are not modeled.
+void testComplexIsUnmodeled(void) {
+  _Complex float z = 1.5f;
+  clang_analyzer_dump(__real__ z);  // expected-warning{{Unknown}}
+  clang_analyzer_eval(!z);          // expected-warning{{UNKNOWN}}
+}
+
+// Inc/decrement operators compute through the same path as += and -=
+// respectively in the analyzer.
+void testIncrementDecrement(void) {
+  float g = 2.0f;
+  g += 1.0f;
+  clang_analyzer_dump(g);   // expected-warning{{3 IEEEsingle}}
+  --g;
+  clang_analyzer_dump(g);   // expected-warning{{2 IEEEsingle}}
+  clang_analyzer_dump(g++); // expected-warning{{2 IEEEsingle}}
+  clang_analyzer_dump(g);   // expected-warning{{3 IEEEsingle}}
+
+  float small = 0.5f;
+  ++small;
+  clang_analyzer_dump(small); // expected-warning{{1.5 IEEEsingle}}
+
+  float inexact = 0.1f;
+  ++inexact;
+  clang_analyzer_dump(inexact); // expected-warning{{Unknown}}
+
+  float max = __FLT_MAX__;
+  ++max;
+  clang_analyzer_dump(max); // expected-warning{{Unknown}}
+}

>From 5fb88ced4a038616f5ac753404ae61f4c5d7ed71 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 31 Aug 2026 19:44:03 +0200
Subject: [PATCH 13/17] Fold constant-float-cxx.cpp into constant-float.cpp

After changing to C++ testing with templates, constant-float-cxx.cpp
became redundant so merge those test cases into constant-float.cpp.
---
 clang/test/Analysis/constant-float-cxx.cpp | 49 ----------------------
 clang/test/Analysis/constant-float.cpp     | 44 +++++++++++++++++++
 2 files changed, 44 insertions(+), 49 deletions(-)
 delete mode 100644 clang/test/Analysis/constant-float-cxx.cpp

diff --git a/clang/test/Analysis/constant-float-cxx.cpp b/clang/test/Analysis/constant-float-cxx.cpp
deleted file mode 100644
index 6788046fe2739..0000000000000
--- a/clang/test/Analysis/constant-float-cxx.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 \
-// RUN:   -analyzer-checker=core,debug.ExprInspection -verify %s
-
-template <typename T> void clang_analyzer_dump(T);
-void clang_analyzer_eval(bool);
-
-template <typename T> T twice(T x) { return x + x; }
-
-struct Container {
-  float v;
-  Container operator+(Container o) const { return Container{v + o.v}; }
-};
-
-constexpr double half(double x) { return x / 2.0; }
-
-double defaultArg(double i = 42) { return i; }
-float narrowingDefaultArg(float i = 1.5) { return i; }
-
-void testTemplate() {
-  clang_analyzer_dump(twice(1.5f)); // expected-warning{{3 IEEEsingle}}
-  clang_analyzer_dump(twice(2.5));  // expected-warning{{5 IEEEdouble}}
-}
-
-void testOverloadedOperator() {
-  Container a{1.5f}, b{2.5f};
-  clang_analyzer_dump((a + b).v); // expected-warning{{4 IEEEsingle}}
-}
-
-void testConstexpr() {
-  clang_analyzer_dump(half(3.0)); // expected-warning{{1.5 IEEEdouble}}
-  constexpr double c = 1.25;
-  clang_analyzer_dump(c);         // expected-warning{{1.25 IEEEdouble}}
-}
-
-// A default argument is not evaluated through the CFG, rather it is folded by
-// getConstantVal.
-void testDefaultArgument() {
-  clang_analyzer_dump(defaultArg());          // expected-warning{{42 IEEEdouble}}
-  clang_analyzer_dump(narrowingDefaultArg()); // expected-warning{{1.5 IEEEsingle}}
-}
-
-// Negative float -> int is well-defined ([conv.fpint], C11 6.3.1.4) by
-// discarding the fractional part, but only if the integral part can be
-// represented in the destination type (otherwise UB).
-void testUnsignedFromNegative() {
-  clang_analyzer_eval((unsigned)-1.5f == 0);  // expected-warning{{UNKNOWN}}
-  clang_analyzer_eval((unsigned)-0.5f == 0);  // expected-warning{{TRUE}}
-  clang_analyzer_eval((int)-1.5f == -1);      // expected-warning{{TRUE}}
-}
diff --git a/clang/test/Analysis/constant-float.cpp b/clang/test/Analysis/constant-float.cpp
index 19892d638a3eb..2b0f6e2e35928 100644
--- a/clang/test/Analysis/constant-float.cpp
+++ b/clang/test/Analysis/constant-float.cpp
@@ -102,6 +102,15 @@ void testFloatToInt(void) {
   clang_analyzer_dump((float)(int)1e30f); // expected-warning{{Unknown}}
 }
 
+// Negative float -> int is well-defined ([conv.fpint], C11 6.3.1.4) by
+// discarding the fractional part, but only if the integral part can be
+// represented in the destination type (otherwise UB).
+void testUnsignedFromNegative(void) {
+  clang_analyzer_eval((unsigned)-1.5f == 0);  // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval((unsigned)-0.5f == 0);  // expected-warning{{TRUE}}
+  clang_analyzer_eval((int)-1.5f == -1);      // expected-warning{{TRUE}}
+}
+
 // Infinities and NaNs should not be modeled.
 void testNonFiniteIsUnmodeled(void) {
   clang_analyzer_dump(1e400f);                // expected-warning{{Unknown}}
@@ -178,3 +187,38 @@ void testIncrementDecrement(void) {
   ++max;
   clang_analyzer_dump(max); // expected-warning{{Unknown}}
 }
+
+template <typename T> T twice(T x) { return x + x; }
+
+struct Container {
+  float v;
+  Container operator+(Container o) const { return Container{v + o.v}; }
+};
+
+constexpr double half(double x) { return x / 2.0; }
+
+double defaultArg(double i = 42) { return i; }
+float narrowingDefaultArg(float i = 1.5) { return i; }
+
+void testTemplate(void) {
+  clang_analyzer_dump(twice(1.5f)); // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump(twice(2.5));  // expected-warning{{5 IEEEdouble}}
+}
+
+void testOverloadedOperator(void) {
+  Container a{1.5f}, b{2.5f};
+  clang_analyzer_dump((a + b).v); // expected-warning{{4 IEEEsingle}}
+}
+
+void testConstexpr(void) {
+  clang_analyzer_dump(half(3.0)); // expected-warning{{1.5 IEEEdouble}}
+  constexpr double c = 1.25;
+  clang_analyzer_dump(c);         // expected-warning{{1.25 IEEEdouble}}
+}
+
+// A default argument is not evaluated through the CFG, rather it is folded by
+// getConstantVal.
+void testDefaultArgument(void) {
+  clang_analyzer_dump(defaultArg());          // expected-warning{{42 IEEEdouble}}
+  clang_analyzer_dump(narrowingDefaultArg()); // expected-warning{{1.5 IEEEsingle}}
+}

>From 1a8ea152ff7bd0a3c016383f6a1077a4dbdc8c02 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 31 Aug 2026 22:05:07 +0200
Subject: [PATCH 14/17] Add tests for bitints and 128-bit ints

---
 clang/test/Analysis/constant-float.cpp | 33 ++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/clang/test/Analysis/constant-float.cpp b/clang/test/Analysis/constant-float.cpp
index 2b0f6e2e35928..c92775cc8dd88 100644
--- a/clang/test/Analysis/constant-float.cpp
+++ b/clang/test/Analysis/constant-float.cpp
@@ -157,6 +157,39 @@ void testIntToFloat(void) {
   clang_analyzer_dump((float)no);   // expected-warning{{Unknown}}
 }
 
+// _BitInt and 128-bit integers.
+void testWideAndBitIntConversions(void) {
+  clang_analyzer_dump((_BitInt(8))1.5f);            // expected-warning{{1 S8b}}
+  clang_analyzer_dump((unsigned _BitInt(8))200.0f); // expected-warning{{200 U8b}}
+  clang_analyzer_dump((__int128)1.5f);              // expected-warning{{1 S128b}}
+  clang_analyzer_dump((_BitInt(8))200.0f);          // expected-warning{{Unknown}}
+
+  clang_analyzer_dump((__int128)18446744073709551616.0f); // 2^64
+  // expected-warning at -1{{18446744073709551616 S128b}}
+  clang_analyzer_dump((unsigned long)18446744073709551616.0f);
+  // expected-warning at -1{{Unknown}}
+
+  clang_analyzer_dump((float)(_BitInt(8))3);      // expected-warning{{3 IEEEsingle}}
+  clang_analyzer_dump((float)(_BitInt(100))1);    // expected-warning{{1 IEEEsingle}}
+  clang_analyzer_dump((float)(__int128)16777216); // expected-warning{{16777216 IEEEsingle}}
+  clang_analyzer_dump((float)(__int128)16777217); // expected-warning{{Unknown}}
+
+  // Max value of 128-bit uint is too big for float.
+  constexpr unsigned _BitInt(128) upper = 0xFFFFFFFFFFFFFFFFULL;
+  constexpr unsigned _BitInt(128) lower = 0xFFFFFFFFFFFFFFFFULL;
+  unsigned _BitInt(128) total = (upper << 64) | lower;
+  float f = (float)total;
+  clang_analyzer_dump(total); // expected-warning{{340282366920938463463374607431768211455 U128b}}
+  clang_analyzer_dump(f);     // expected-warning{{Unknown}}
+
+  // Largest integer value of float.
+  constexpr unsigned _BitInt(128) upper2 = 0xFFFFFF0000000000ULL;
+  unsigned _BitInt(128) total2 = (upper2 << 64);
+  float f2 = (float)total2;
+  clang_analyzer_dump(total2);  // expected-warning{{340282346638528859811704183484516925440 U128b}}
+  clang_analyzer_dump(f2);      // expected-warning{{3.40282347E+38 IEEEsingle}}
+}
+
 // Complex floating-point types are not modeled.
 void testComplexIsUnmodeled(void) {
   _Complex float z = 1.5f;

>From 0ecc63e2cddfe9ce3a23f2b9cfe7254083fa72d0 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 31 Aug 2026 23:45:34 +0200
Subject: [PATCH 15/17] Add float semantics to sval explainer

Make getSemanticsName non-static so the sval explainer can call it from
a ConcreteFloat instance.
---
 .../include/clang/StaticAnalyzer/Checkers/SValExplainer.h | 4 ++--
 .../clang/StaticAnalyzer/Core/PathSensitive/SVals.h       | 5 +++++
 clang/lib/StaticAnalyzer/Core/SVals.cpp                   | 8 ++++----
 clang/test/Analysis/explain-svals.c                       | 6 ++++--
 4 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
index 29f50186d9328..01d488889566c 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
@@ -99,10 +99,10 @@ class SValExplainer : public FullSValVisitor<SValExplainer, std::string> {
   }
 
   std::string VisitConcreteFloat(nonloc::ConcreteFloat V) {
-    const llvm::APFloat &F = *V.getValue();
     std::string Str;
     llvm::raw_string_ostream OS(Str);
-    OS << "concrete floating-point value '" << F << "'";
+    OS << "concrete " << V.getSemanticsName() << " floating-point value '"
+       << *V.getValue() << "'";
     return Str;
   }
 
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
index 79752610c74fd..316cad84951e7 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
@@ -317,6 +317,11 @@ class ConcreteFloat : public NonLoc {
     return APFloatPtr(castDataAs<llvm::APFloat>());
   }
 
+  /// A human-readabale name for the floating-point format of this value,
+  /// e.g., "IEEEsingle." Formats with no explicit name are reported
+  /// as "unknown."
+  StringRef getSemanticsName() const;
+
   static bool classof(SVal V) { return V.getKind() == ConcreteFloatKind; }
 };
 
diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp
index f380554c073e7..9a43d0b2c6e32 100644
--- a/clang/lib/StaticAnalyzer/Core/SVals.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp
@@ -34,8 +34,8 @@
 using namespace clang;
 using namespace ento;
 
-static StringRef getFloatSemanticsName(const llvm::fltSemantics &Sem) {
-  switch (llvm::APFloat::SemanticsToEnum(Sem)) {
+StringRef nonloc::ConcreteFloat::getSemanticsName() const {
+  switch (llvm::APFloat::SemanticsToEnum(getValue()->getSemantics())) {
   case llvm::APFloat::S_IEEEhalf:
     return "IEEEhalf";
   case llvm::APFloat::S_BFloat:
@@ -354,8 +354,8 @@ void SVal::dumpToStream(raw_ostream &os) const {
 void NonLoc::dumpToStream(raw_ostream &os) const {
   switch (getKind()) {
   case nonloc::ConcreteFloatKind: {
-    const llvm::APFloat &Value = *castAs<nonloc::ConcreteFloat>().getValue();
-    os << Value << ' ' << getFloatSemanticsName(Value.getSemantics());
+    nonloc::ConcreteFloat V = castAs<nonloc::ConcreteFloat>();
+    os << *V.getValue() << ' ' << V.getSemanticsName();
     break;
   }
   case nonloc::ConcreteIntKind: {
diff --git a/clang/test/Analysis/explain-svals.c b/clang/test/Analysis/explain-svals.c
index e16f87fd04ff7..e3ac734a3620b 100644
--- a/clang/test/Analysis/explain-svals.c
+++ b/clang/test/Analysis/explain-svals.c
@@ -13,6 +13,7 @@ void clang_analyzer_explain_voidp(void *);
 void clang_analyzer_explain_S(struct S);
 void clang_analyzer_explain_float(float);
 void clang_analyzer_explain_double(double);
+void clang_analyzer_explain_longdouble(long double);
 
 int glob;
 
@@ -35,8 +36,9 @@ void test_3(int param) {
 }
 
 void test_float(void) {
-  clang_analyzer_explain_float(1.5f); // expected-warning-re{{{{^concrete floating-point value '1.5'$}}}}
-  clang_analyzer_explain_double(2.5); // expected-warning-re{{{{^concrete floating-point value '2.5'$}}}}
+  clang_analyzer_explain_float(1.5f);       // expected-warning-re{{{{^concrete IEEEsingle floating-point value '1.5'$}}}}
+  clang_analyzer_explain_double(2.5);       // expected-warning-re{{{{^concrete IEEEdouble floating-point value '2.5'$}}}}
+  clang_analyzer_explain_longdouble(3.5L);  // expected-warning-re{{{{^concrete x87DoubleExtended floating-point value '3.5'$}}}}
 }
 
 void test_non_top_level(int param) {

>From 312aac26afc3b434b7caf218243b94c9edec89af Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Sat, 5 Sep 2026 21:37:47 +0200
Subject: [PATCH 16/17] Increase coverage with C test

---
 clang/test/Analysis/constant-float-c.c | 43 ++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)
 create mode 100644 clang/test/Analysis/constant-float-c.c

diff --git a/clang/test/Analysis/constant-float-c.c b/clang/test/Analysis/constant-float-c.c
new file mode 100644
index 0000000000000..80ee5db305a37
--- /dev/null
+++ b/clang/test/Analysis/constant-float-c.c
@@ -0,0 +1,43 @@
+// Test lines unreachable from C++. Particularly when floats are used in a
+// boolean context (C++ inserts implicit FloatingToBoolean conversions,
+// C compares to 0).
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection -verify %s
+
+void clang_analyzer_dump_float(float);
+void clang_analyzer_eval(int);
+
+void testBranchOnFloat(void) {
+  float nonzero = 1.5f;
+  if (nonzero)
+    clang_analyzer_dump_float(nonzero); // expected-warning{{1.5 IEEEsingle}}
+  else
+    clang_analyzer_dump_float(nonzero);
+
+  float zero = 0.0f;
+  if (zero)
+    clang_analyzer_dump_float(zero);
+  else
+    clang_analyzer_dump_float(zero);  // expected-warning{{0 IEEEsingle}}
+
+  float negzero = -0.0f;
+  if (negzero)
+    clang_analyzer_dump_float(negzero);
+  else
+    clang_analyzer_dump_float(negzero); // expected-warning{{-0 IEEEsingle}}
+}
+
+void testLogicalNotOnFloat(void) {
+  float nonzero = 0.5f, zero = 0.0f, negzero = -0.0f;
+  clang_analyzer_eval(!nonzero);  // expected-warning{{FALSE}}
+  clang_analyzer_eval(!zero);     // expected-warning{{TRUE}}
+  clang_analyzer_eval(!negzero);  // expected-warning{{TRUE}}
+}
+
+void testUnknownFloatCondition(float f) {
+  if (f)
+    clang_analyzer_dump_float(f); // expected-warning{{Unknown}}
+  else
+    clang_analyzer_dump_float(f); // expected-warning{{Unknown}}
+}

>From 7143b5f998ee2407f9c97a7aa83663fcd60c1525 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Wed, 16 Sep 2026 19:50:51 +0200
Subject: [PATCH 17/17] Add fltSemantics to APFloat's Profile

We need the semantics in the profile because the same bit pattern can
represent different underlying values when semantics differ, so these
values should hash to different hash values.
---
 .../StaticAnalyzer/Core/BasicValueFactory.cpp |  9 ++----
 llvm/lib/Support/APFloat.cpp                  |  3 ++
 llvm/unittests/ADT/APFloatTest.cpp            | 32 +++++++++++++++++++
 3 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
index b0c410c4cde93..272c78698547a 100644
--- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
@@ -126,19 +126,16 @@ APSIntPtr BasicValueFactory::getValue(uint64_t X, QualType T) {
 
 APFloatPtr BasicValueFactory::getFloatValue(const llvm::APFloat &X) {
   llvm::FoldingSetNodeID ID;
-  void *InsertPos;
+  llvm::FoldingSetInsertToken InsertToken;
 
   using FoldNodeTy = llvm::FoldingSetNodeWrapper<llvm::APFloat>;
 
-  // ID must be unique to differentiate between nodes. Unlike integers, bit
-  // size and pattern are not sufficient, so add semantics to the ID as well.
-  ID.AddInteger(llvm::APFloat::SemanticsToEnum(X.getSemantics()));
   X.Profile(ID);
-  FoldNodeTy *P = APFloatSet.FindNodeOrInsertPos(ID, InsertPos);
+  FoldNodeTy *P = APFloatSet.lookup(ID, InsertToken);
 
   if (!P) {
     P = new (BPAlloc) FoldNodeTy(X);
-    APFloatSet.InsertNode(P, InsertPos);
+    APFloatSet.insert(P, InsertToken);
   }
 
   return APFloatPtr(&P->getValue());
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 59a6ba3867d40..51710929e6c98 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -6051,6 +6051,9 @@ LLVM_DUMP_METHOD void APFloat::dump() const {
 #endif
 
 void APFloat::Profile(FoldingSetNodeID &NID) const {
+  // Bit pattern alone does not uniquely identify a floating-point value;
+  // need semantics as well.
+  NID.AddInteger(SemanticsToEnum(getSemantics()));
   NID.Add(bitcastToAPInt());
 }
 
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index e161071e63af4..b9577a27bad77 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/ADT/APFloat.h"
 #include "llvm/ADT/APSInt.h"
+#include "llvm/ADT/FoldingSet.h"
 #include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
@@ -11171,3 +11172,34 @@ TEST(APFloatTest, exp_exceptions) {
 }
 
 } // namespace
+
+// Profile for APFloat should discriminate considering both bit patterns and
+// semantics.
+TEST(APFloatTest, Profile) {
+  auto profile = [](const APFloat &V) {
+    FoldingSetNodeID ID;
+    V.Profile(ID);
+    return ID;
+  };
+
+  EXPECT_EQ(profile(APFloat(APFloat::IEEEsingle(), "1.5")),
+            profile(APFloat(APFloat::IEEEsingle(), "1.5")));
+  EXPECT_NE(profile(APFloat(APFloat::IEEEsingle(), "1.5")),
+            profile(APFloat(APFloat::IEEEsingle(), "2.5")));
+
+  // These 16-bit values have the same bit pattern but different
+  // underlying value.
+  APFloat Half(APFloat::IEEEhalf(), APInt(16, 0x3C00));
+  APFloat BF(APFloat::BFloat(), APInt(16, 0x3C00));
+  EXPECT_EQ(Half.bitcastToAPInt(), BF.bitcastToAPInt());
+  EXPECT_NE(Half.convertToDouble(), BF.convertToDouble());
+  EXPECT_NE(profile(Half), profile(BF));
+
+  // Same case as above, but semantics differ only on infinity support.
+  APFloat E4M3(APFloat::Float8E4M3(), APInt(8, 0x78));
+  APFloat E4M3FN(APFloat::Float8E4M3FN(), APInt(8, 0x78));
+  EXPECT_EQ(E4M3.bitcastToAPInt(), E4M3FN.bitcastToAPInt());
+  EXPECT_TRUE(E4M3.isInfinity());
+  EXPECT_TRUE(E4M3FN.isFinite());
+  EXPECT_NE(profile(E4M3), profile(E4M3FN));
+}



More information about the llvm-commits mailing list