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

>From 0e8eef21d532ff7d817033263658c1268773dac8 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 9 Jul 2026 16:56:04 +0200
Subject: [PATCH 1/8] 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 b86f0e8309dc7..5fb203c665892 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 6127328cefe23..ebe4a29617024 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -288,8 +288,12 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const 
Expr *Ex,
 
       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 57c97b2852445..4f8a73e495aeb 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -375,6 +375,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));
 
@@ -863,6 +866,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 7ac30704793349a7b103762fa56cea9a22d2ed30 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 9 Jul 2026 21:54:52 +0200
Subject: [PATCH 2/8] 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 a2388ce47fc4e148f76f781272972db27228cd7c Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 13 Jul 2026 17:18:55 +0200
Subject: [PATCH 3/8] 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@-1{{0 x87DoubleExtended}}
+  // quad-warning@-2{{0 IEEEquad}}
+  // ldbl64-warning@-3{{0 IEEEdouble}}
+  clang_analyzer_dump_longdouble(1.0L);
+  // x87-warning@-1{{1 x87DoubleExtended}}
+  // quad-warning@-2{{1 IEEEquad}}
+  // ldbl64-warning@-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 65d317bb191403ec1d91d62b828a3a607d24aaf2 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Tue, 14 Jul 2026 17:38:45 +0200
Subject: [PATCH 4/8] 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 4f8a73e495aeb..55cbae3019fd7 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -459,6 +459,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 f3c59dbfa7d48380a793906b23b32f2ae8a153a0 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 15 Jul 2026 21:11:47 +0200
Subject: [PATCH 5/8] 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 7976b82b63f6e..36cff7b8cda3b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -555,6 +555,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
 
 #### Diagnostic changes
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 a36f4e68230a717accc7b4a5defbdea87862dc9e Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Fri, 7 Aug 2026 19:41:11 +0200
Subject: [PATCH 6/8] 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 36cff7b8cda3b..263055b6448fa 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -556,10 +556,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 5fb203c665892..ae1dc83ff0aad 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 ebe4a29617024..786454de33225 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -288,9 +288,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const 
Expr *Ex,
 
       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());
@@ -975,12 +974,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);
@@ -1039,6 +1047,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 55cbae3019fd7..1d44815626356 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>();
   }
 
@@ -375,8 +382,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));
@@ -396,6 +407,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();
@@ -460,8 +473,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);
@@ -874,19 +887,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);
@@ -899,10 +921,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) {
@@ -924,6 +942,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@-1{{0 x87DoubleExtended}}
-  // quad-warning@-2{{0 IEEEquad}}
-  // ldbl64-warning@-3{{0 IEEEdouble}}
-  clang_analyzer_dump_longdouble(1.0L);
-  // x87-warning@-1{{1 x87DoubleExtended}}
-  // quad-warning@-2{{1 IEEEquad}}
-  // ldbl64-warning@-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@-1{{1.5 x87DoubleExtended}}
+  // quad-warning@-2{{1.5 IEEEquad}}
+  // ldbl64-warning@-3{{1.5 IEEEdouble}}
+  // ibm128-warning@-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@-1{{1 x87DoubleExtended}}
+  // quad-warning@-2{{1 IEEEquad}}
+  // ldbl64-warning@-3{{1 IEEEdouble}}
+  // ibm128-warning@-4{{Unknown}}
+  clang_analyzer_dumpSvalType_longdouble(1.0L);
+  // x87-warning@-1{{long double}}
+  // quad-warning@-2{{__float128}}
+  // ldbl64-warning@-3{{double}}
+  // ibm128-warning@-4{{NULL TYPE}}
+}
+
+void testLongDoubleArithmetic(void) {
+  clang_analyzer_dump_longdouble(1.0L + 2.0L);
+  // x87-warning@-1{{3 x87DoubleExtended}}
+  // quad-warning@-2{{3 IEEEquad}}
+  // ldbl64-warning@-3{{3 IEEEdouble}}
+  // ibm128-warning@-4{{Unknown}}
+  clang_analyzer_dump_longdouble(3.0L / 4.0L);
+  // x87-warning@-1{{0.75 x87DoubleExtended}}
+  // quad-warning@-2{{0.75 IEEEquad}}
+  // ldbl64-warning@-3{{0.75 IEEEdouble}}
+  // ibm128-warning@-4{{Unknown}}
+  clang_analyzer_eval(1.0L < 2.0L);
+  // x87-warning@-1{{TRUE}}
+  // quad-warning@-2{{TRUE}}
+  // ldbl64-warning@-3{{TRUE}}
+  // ibm128-warning@-4{{UNKNOWN}}
+}
+
+void testIntToFloat(void) {
+  clang_analyzer_dump_longdouble((long double)1);
+  // x87-warning@-1{{1 x87DoubleExtended}}
+  // quad-warning@-2{{1 IEEEquad}}
+  // ldbl64-warning@-3{{1 IEEEdouble}}
+  // ibm128-warning@-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@-1{{0 x87DoubleExtended}}
+  // quad-warning@-2{{0 IEEEquad}}
+  // ldbl64-warning@-3{{0 IEEEdouble}}
+  // ibm128-warning@-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@-1{{Unknown}}
+  clang_analyzer_dump_float(__FLT_MIN__);
+  // expected-warning@-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 e3ae42a530e88dbbb1e6a94b6720f832e8de1147 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Fri, 7 Aug 2026 21:54:53 +0200
Subject: [PATCH 7/8] 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 3c169fff0fcd1c067635050806c4735ba62f9e91 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Sat, 8 Aug 2026 05:20:46 +0200
Subject: [PATCH 8/8] 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 1d44815626356..c93554187a2f0 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -887,6 +887,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 differentlty).
+    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()) {
@@ -944,6 +955,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}}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to