[clang] [analyzer] Model concrete floating-point values (PR #214098)
John Paul Jepko via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 7 20:25:30 PDT 2026
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 <john.jepko at ericsson.com>
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 <john.jepko at ericsson.com>
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 <john.jepko at ericsson.com>
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 at -1{{0 x87DoubleExtended}}
+ // quad-warning at -2{{0 IEEEquad}}
+ // ldbl64-warning at -3{{0 IEEEdouble}}
+ clang_analyzer_dump_longdouble(1.0L);
+ // x87-warning at -1{{1 x87DoubleExtended}}
+ // quad-warning at -2{{1 IEEEquad}}
+ // ldbl64-warning at -3{{1 IEEEdouble}}
}
//===----------------------------------------------------------------------===//
@@ -108,6 +113,37 @@ void testUnknown(float f) {
clang_analyzer_dump_float(f + 1.0f); // common-warning{{Unknown}}
}
+//===----------------------------------------------------------------------===//
+// Arithmetic between concrete floats is folded only when the result is exact.
+//===----------------------------------------------------------------------===//
+
+void testExactArithmetic(void) {
+ // All of these have exactly representable results, so they are independent
+ // of rounding mode and evaluation precision.
+ clang_analyzer_dump_float(1.0f + 2.0f); // common-warning{{3 IEEEsingle}}
+ clang_analyzer_dump_float(5.0f - 1.5f); // common-warning{{3.5 IEEEsingle}}
+ clang_analyzer_dump_float(1.5f * 2.0f); // common-warning{{3 IEEEsingle}}
+ clang_analyzer_dump_float(3.0f / 4.0f); // common-warning{{0.75 IEEEsingle}}
+ clang_analyzer_dump_double(0.5 + 0.25); // common-warning{{0.75 IEEEdouble}}
+}
+
+void testInexactArithmetic(void) {
+ // 0.1f + 0.2f is not exactly representable in single precision; the rounded
+ // result depends on the rounding mode / evaluation precision, so we do not
+ // model it.
+ clang_analyzer_dump_float(0.1f + 0.2f); // common-warning{{Unknown}}
+ // 1.0f / 3.0f is inexact.
+ clang_analyzer_dump_float(1.0f / 3.0f); // common-warning{{Unknown}}
+}
+
+void testComparisons(void) {
+ clang_analyzer_eval(1.0f < 2.0f); // common-warning{{TRUE}}
+ clang_analyzer_eval(2.0f < 1.0f); // common-warning{{FALSE}}
+ clang_analyzer_eval(1.5 == 1.5); // common-warning{{TRUE}}
+ clang_analyzer_eval(1.5 != 2.5); // common-warning{{TRUE}}
+ clang_analyzer_eval(2.0f >= 2.0f); // common-warning{{TRUE}}
+}
+
//===----------------------------------------------------------------------===//
// Division by zero detection with concrete floats.
//===----------------------------------------------------------------------===//
>From 65d317bb191403ec1d91d62b828a3a607d24aaf2 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Tue, 14 Jul 2026 17:38:45 +0200
Subject: [PATCH 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 <john.jepko at ericsson.com>
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(¶m); // 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(¶m); // expected-warning-re{{{{^pointer to parameter 'param'$}}}}
}
>From a36f4e68230a717accc7b4a5defbdea87862dc9e Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Fri, 7 Aug 2026 19:41:11 +0200
Subject: [PATCH 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 at -1{{0 x87DoubleExtended}}
- // quad-warning at -2{{0 IEEEquad}}
- // ldbl64-warning at -3{{0 IEEEdouble}}
- clang_analyzer_dump_longdouble(1.0L);
- // x87-warning at -1{{1 x87DoubleExtended}}
- // quad-warning at -2{{1 IEEEquad}}
- // ldbl64-warning at -3{{1 IEEEdouble}}
-}
-
-//===----------------------------------------------------------------------===//
-// Variables assigned from literals retain the ConcreteFloat value.
-//===----------------------------------------------------------------------===//
-
-void testVariables(void) {
- float f = 1.5f;
- double d = 2.5;
- clang_analyzer_dump_float(f); // common-warning{{1.5 IEEEsingle}}
- clang_analyzer_dump_double(d); // common-warning{{2.5 IEEEdouble}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-integer casts (truncation).
-//===----------------------------------------------------------------------===//
-
-void testFloatToInt(void) {
- float f = 1.9f;
- double d = 2.7;
- int i = (int)f;
- int j = (int)d;
- clang_analyzer_eval(i == 1); // common-warning{{TRUE}}
- clang_analyzer_eval(j == 2); // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-bool casts.
-//===----------------------------------------------------------------------===//
-
-void testFloatToBool(void) {
- float zero = 0.0f;
- float nonzero = 1.0f;
- clang_analyzer_eval((int)((_Bool)zero) == 0); // common-warning{{TRUE}}
- clang_analyzer_eval((int)((_Bool)nonzero) == 1); // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-float casts (precision change without loss).
-//===----------------------------------------------------------------------===//
-
-void testFloatUpcast(void) {
- float f = 1.5f;
- double d = f;
- // 1.5 is exactly representable in both, so no loss.
- clang_analyzer_dump_double(d); // common-warning{{1.5 IEEEdouble}}
-}
-
-//===----------------------------------------------------------------------===//
-// Float-to-float casts (inexact narrowing stays Unknown).
-//===----------------------------------------------------------------------===//
-
-void testFloatNarrowing(void) {
- double d = 3.14;
- float f = (float)d;
- // 3.14 is not exactly representable in float, and rounding direction is
- // implementation-defined, so we don't model here.
- clang_analyzer_dump_float(f); // common-warning{{Unknown}}
-}
-
-//===----------------------------------------------------------------------===//
-// Unknown float values (parameters, arithmetic results).
-//===----------------------------------------------------------------------===//
-
-void testUnknown(float f) {
- clang_analyzer_dump_float(f); // common-warning{{Unknown}}
- clang_analyzer_dump_float(f + 1.0f); // common-warning{{Unknown}}
-}
-
-//===----------------------------------------------------------------------===//
-// Arithmetic between concrete floats is folded only when the result is exact.
-//===----------------------------------------------------------------------===//
-
-void testExactArithmetic(void) {
- // All of these have exactly representable results, so they are independent
- // of rounding mode and evaluation precision.
- clang_analyzer_dump_float(1.0f + 2.0f); // common-warning{{3 IEEEsingle}}
- clang_analyzer_dump_float(5.0f - 1.5f); // common-warning{{3.5 IEEEsingle}}
- clang_analyzer_dump_float(1.5f * 2.0f); // common-warning{{3 IEEEsingle}}
- clang_analyzer_dump_float(3.0f / 4.0f); // common-warning{{0.75 IEEEsingle}}
- clang_analyzer_dump_double(0.5 + 0.25); // common-warning{{0.75 IEEEdouble}}
-}
-
-void testInexactArithmetic(void) {
- // 0.1f + 0.2f is not exactly representable in single precision; the rounded
- // result depends on the rounding mode / evaluation precision, so we do not
- // model it.
- clang_analyzer_dump_float(0.1f + 0.2f); // common-warning{{Unknown}}
- // 1.0f / 3.0f is inexact.
- clang_analyzer_dump_float(1.0f / 3.0f); // common-warning{{Unknown}}
-}
-
-void testComparisons(void) {
- clang_analyzer_eval(1.0f < 2.0f); // common-warning{{TRUE}}
- clang_analyzer_eval(2.0f < 1.0f); // common-warning{{FALSE}}
- clang_analyzer_eval(1.5 == 1.5); // common-warning{{TRUE}}
- clang_analyzer_eval(1.5 != 2.5); // common-warning{{TRUE}}
- clang_analyzer_eval(2.0f >= 2.0f); // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Unary negation is always exact (a sign-bit flip).
-//===----------------------------------------------------------------------===//
-
-void testNegation(void) {
- float f = 1.5f;
- double d = 2.5;
- clang_analyzer_dump_float(-f); // common-warning{{-1.5 IEEEsingle}}
- clang_analyzer_dump_double(-d); // common-warning{{-2.5 IEEEdouble}}
- clang_analyzer_dump_float(-(-f)); // common-warning{{1.5 IEEEsingle}}
- clang_analyzer_eval(-f < 0.0f); // common-warning{{TRUE}}
-}
-
-//===----------------------------------------------------------------------===//
-// Infinity from an overflowing literal is concrete, but arithmetic on it is
-// not folded and casting it to int is not modeled (both would depend on
-// IEC 60559 semantics / are undefined in C), while comparisons still work.
-//===----------------------------------------------------------------------===//
-
-void testInfinity(void) {
- float big = 1e400f; // common-warning{{magnitude of floating-point constant too large}}
- clang_analyzer_dump_float(big); // common-warning{{+Inf IEEEsingle}}
- clang_analyzer_dump_float(big + 1.0f); // common-warning{{Unknown}}
- clang_analyzer_eval(big > 1.0f); // common-warning{{TRUE}}
- clang_analyzer_dump_float((float)(int)big); // common-warning{{Unknown}}
-}
-
-//===----------------------------------------------------------------------===//
-// Division by zero detection with concrete floats.
-//===----------------------------------------------------------------------===//
-
-float testDivByZeroFloat(void) {
- float x = 0.0f;
- return 1.0f / x; // common-warning{{Division by zero}}
-}
diff --git a/clang/test/Analysis/constant-float-long-double.c b/clang/test/Analysis/constant-float-long-double.c
new file mode 100644
index 0000000000000..87e56eb444196
--- /dev/null
+++ b/clang/test/Analysis/constant-float-long-double.c
@@ -0,0 +1,78 @@
+// Multiple targets needed because long double semantics differ on them.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN: -analyzer-checker=core,debug.ExprInspection \
+// RUN: -analyzer-config eagerly-assume=false -verify=x87 %s
+// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \
+// RUN: -analyzer-checker=core,debug.ExprInspection \
+// RUN: -analyzer-config eagerly-assume=false -verify=quad %s
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \
+// RUN: -analyzer-checker=core,debug.ExprInspection \
+// RUN: -analyzer-config eagerly-assume=false -verify=ldbl64 %s
+// RUN: %clang_analyze_cc1 -triple powerpc64le-unknown-linux-gnu \
+// RUN: -analyzer-checker=core,debug.ExprInspection \
+// RUN: -analyzer-config eagerly-assume=false -verify=ibm128 %s
+
+void clang_analyzer_dump_longdouble(long double);
+void clang_analyzer_dumpSvalType_longdouble(long double);
+void clang_analyzer_eval(int);
+
+void testVariables(void) {
+ long double ld = 1.5L;
+ clang_analyzer_dump_longdouble(ld);
+ // x87-warning at -1{{1.5 x87DoubleExtended}}
+ // quad-warning at -2{{1.5 IEEEquad}}
+ // ldbl64-warning at -3{{1.5 IEEEdouble}}
+ // ibm128-warning at -4{{Unknown}}
+}
+
+// long double has different semantics depending on target. IBM double-double
+// should not be modeled.
+void testLongDouble(void) {
+ clang_analyzer_dump_longdouble(1.0L);
+ // x87-warning at -1{{1 x87DoubleExtended}}
+ // quad-warning at -2{{1 IEEEquad}}
+ // ldbl64-warning at -3{{1 IEEEdouble}}
+ // ibm128-warning at -4{{Unknown}}
+ clang_analyzer_dumpSvalType_longdouble(1.0L);
+ // x87-warning at -1{{long double}}
+ // quad-warning at -2{{__float128}}
+ // ldbl64-warning at -3{{double}}
+ // ibm128-warning at -4{{NULL TYPE}}
+}
+
+void testLongDoubleArithmetic(void) {
+ clang_analyzer_dump_longdouble(1.0L + 2.0L);
+ // x87-warning at -1{{3 x87DoubleExtended}}
+ // quad-warning at -2{{3 IEEEquad}}
+ // ldbl64-warning at -3{{3 IEEEdouble}}
+ // ibm128-warning at -4{{Unknown}}
+ clang_analyzer_dump_longdouble(3.0L / 4.0L);
+ // x87-warning at -1{{0.75 x87DoubleExtended}}
+ // quad-warning at -2{{0.75 IEEEquad}}
+ // ldbl64-warning at -3{{0.75 IEEEdouble}}
+ // ibm128-warning at -4{{Unknown}}
+ clang_analyzer_eval(1.0L < 2.0L);
+ // x87-warning at -1{{TRUE}}
+ // quad-warning at -2{{TRUE}}
+ // ldbl64-warning at -3{{TRUE}}
+ // ibm128-warning at -4{{UNKNOWN}}
+}
+
+void testIntToFloat(void) {
+ clang_analyzer_dump_longdouble((long double)1);
+ // x87-warning at -1{{1 x87DoubleExtended}}
+ // quad-warning at -2{{1 IEEEquad}}
+ // ldbl64-warning at -3{{1 IEEEdouble}}
+ // ibm128-warning at -4{{Unknown}}
+}
+
+// IBM double-double should not be created on the makeZeroVal build path.
+void testZeroInitialized(void) {
+ static long double s;
+ clang_analyzer_dump_longdouble(s);
+ // x87-warning at -1{{0 x87DoubleExtended}}
+ // quad-warning at -2{{0 IEEEquad}}
+ // ldbl64-warning at -3{{0 IEEEdouble}}
+ // ibm128-warning at -4{{Unknown}}
+}
diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c
new file mode 100644
index 0000000000000..6c91df894a21f
--- /dev/null
+++ b/clang/test/Analysis/constant-float.c
@@ -0,0 +1,172 @@
+// Disable -Wliteral-range since we intentionally induce inf.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN: -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
+// RUN: -analyzer-config eagerly-assume=false -verify %s
+//
+// Only exact results are modeled so the analyzer's behavior should not change
+// under a dynamic rounding mode.
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -frounding-math \
+// RUN: -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \
+// RUN: -analyzer-config eagerly-assume=false -verify %s
+
+void clang_analyzer_dump_float(float);
+void clang_analyzer_dump_double(double);
+void clang_analyzer_eval(int);
+
+void testLiterals(void) {
+ clang_analyzer_dump_float(0.0f); // expected-warning{{0 IEEEsingle}}
+ clang_analyzer_dump_float(3.14f); // expected-warning{{3.1400001 IEEEsingle}}
+ clang_analyzer_dump_double(3.14); // expected-warning{{3.1400000000000001 IEEEdouble}}
+}
+
+void testVariables(void) {
+ float f = 1.5f;
+ clang_analyzer_dump_float(f); // expected-warning{{1.5 IEEEsingle}}
+}
+
+void testUnknown(float f) {
+ clang_analyzer_dump_float(f); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(f + 1.0f); // expected-warning{{Unknown}}
+}
+
+// Exactly representable results are independent of rounding mode and
+// evaluation precision.
+void testArithmetic(void) {
+ clang_analyzer_dump_float(1.0f + 2.0f); // expected-warning{{3 IEEEsingle}}
+ clang_analyzer_dump_float(5.0f - 1.5f); // expected-warning{{3.5 IEEEsingle}}
+ clang_analyzer_dump_float(1.5f * 2.0f); // expected-warning{{3 IEEEsingle}}
+ clang_analyzer_dump_float(3.0f / 4.0f); // expected-warning{{0.75 IEEEsingle}}
+ clang_analyzer_dump_double(0.5 + 0.25); // expected-warning{{0.75 IEEEdouble}}
+ clang_analyzer_dump_float(0.1f + 0.2f); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(1.0f / 3.0f); // expected-warning{{Unknown}}
+}
+
+// Comparisons are exact for every value, so all six predicates should fold.
+void testComparisons(void) {
+ clang_analyzer_eval(1.0f < 2.0f); // expected-warning{{TRUE}}
+ clang_analyzer_eval(1.0f > 2.0f); // expected-warning{{FALSE}}
+ clang_analyzer_eval(2.0f <= 2.0f); // expected-warning{{TRUE}}
+ clang_analyzer_eval(2.0f >= 2.0f); // expected-warning{{TRUE}}
+ clang_analyzer_eval(1.5 == 1.5); // expected-warning{{TRUE}}
+ clang_analyzer_eval(1.5 != 2.5); // expected-warning{{TRUE}}
+}
+
+// Unary negation can be modeled because of sign-bit.
+void testNegation(void) {
+ clang_analyzer_dump_float(-1.5f); // expected-warning{{-1.5 IEEEsingle}}
+ clang_analyzer_dump_float(-(-1.5f)); // expected-warning{{1.5 IEEEsingle}}
+ clang_analyzer_dump_float(-0.0f); // expected-warning{{-0 IEEEsingle}}
+ clang_analyzer_eval(0.0f == -0.0f); // expected-warning{{TRUE}}
+}
+
+// Conversions between floating points should be modeled only when they are
+// exact. 3.14 stored in a double sets mantissa bits a float cannot hold, and
+// rounding direction is implementation-specific.
+void testFloatConversions(void) {
+ clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEdouble}}
+ clang_analyzer_dump_float((float)3.14); // expected-warning{{Unknown}}
+}
+
+// Casts to bool is defined as a comparison to zero.
+void testFloatToBool(void) {
+ clang_analyzer_eval((_Bool)0.5f); // expected-warning{{TRUE}}
+ clang_analyzer_eval((_Bool)2.0f); // expected-warning{{TRUE}}
+ clang_analyzer_eval((_Bool)-3.0f); // expected-warning{{TRUE}}
+ clang_analyzer_eval((_Bool)0.0f); // expected-warning{{FALSE}}
+ clang_analyzer_eval((_Bool)-0.0f); // expected-warning{{FALSE}}
+}
+
+// !E is defined as (0 == E) with the zero converted to the type of expr. E.
+void testLogicalNot(void) {
+ float nonzero = 0.5f, zero = 0.0f, negzero = -0.0f;
+ clang_analyzer_eval(!nonzero); // expected-warning{{FALSE}}
+ clang_analyzer_eval(!zero); // expected-warning{{TRUE}}
+ clang_analyzer_eval(!negzero); // expected-warning{{TRUE}}
+}
+
+// Casts to integers discard fractional bits, which should be unmodeled when
+// the result is out of range.
+void testFloatToInt(void) {
+ clang_analyzer_eval((int)1.9f == 1); // expected-warning{{TRUE}}
+ clang_analyzer_eval((int)-1.9 == -1); // expected-warning{{TRUE}}
+ clang_analyzer_dump_float((float)(int)1e30f); // expected-warning{{Unknown}}
+}
+
+// Infinities and NaNs should not be modeled.
+void testNonFiniteIsUnmodeled(void) {
+ clang_analyzer_dump_float(1e400f); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float((float)1e300); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(__FLT_MAX__ * 2.0f); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(__builtin_inff()); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(__builtin_huge_valf()); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(__builtin_nanf("")); // expected-warning{{Unknown}}
+ clang_analyzer_dump_double(0.0 / 0.0); // expected-warning{{Unknown}}
+ clang_analyzer_dump_double(1.0 / 0.0); // expected-warning{{Unknown}}
+}
+
+void testSelfArithmetic(void) {
+ float f = 1.5f;
+ clang_analyzer_dump_float(f - f); // expected-warning{{0 IEEEsingle}}
+ clang_analyzer_eval(f == f); // expected-warning{{TRUE}}
+}
+
+// Subnormals should not be modeled.
+void testSubnormals(void) {
+ clang_analyzer_dump_float(__FLT_DENORM_MIN__); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(__FLT_MIN__ / 2.0f); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float(__FLT_MIN__ * __FLT_MIN__); // expected-warning{{Unknown}}
+ clang_analyzer_dump_float((float)(double)__FLT_DENORM_MIN__);
+ // expected-warning at -1{{Unknown}}
+ clang_analyzer_dump_float(__FLT_MIN__);
+ // expected-warning at -1{{1.17549435E-38 IEEEsingle}}
+}
+
+// Integer to float conversions should only be modeled when the conversion is
+// exact.
+void testIntToFloat(void) {
+ float f = 1;
+ clang_analyzer_dump_float(f); // expected-warning{{1 IEEEsingle}}
+ clang_analyzer_dump_float(1.0f + 1); // expected-warning{{2 IEEEsingle}}
+ clang_analyzer_dump_float((float)-3); // expected-warning{{-3 IEEEsingle}}
+ clang_analyzer_dump_float((float)16777216); // expected-warning{{16777216 IEEEsingle}}
+ clang_analyzer_dump_float((float)16777217); // expected-warning{{Unknown}}
+
+ // 2^26 can be represented, 2^26 + 1 cannot.
+ long yes = 1L << 26;
+ long no = yes + 1L;
+ clang_analyzer_dump_float((float)yes); // expected-warning{{67108864}}
+ clang_analyzer_dump_float((float)no); // expected-warning{{Unknown}}
+}
+
+// Complex floating-point types are not modeled.
+void testComplexIsUnmodeled(void) {
+ _Complex float z = 1.5f;
+ clang_analyzer_dump_float(__real__ z); // expected-warning{{Unknown}}
+ clang_analyzer_eval(!z); // expected-warning{{UNKNOWN}}
+}
+
+// Inc/decrement operators compute through the same path as += and -=
+// respectively in the analyzer.
+void testIncrementDecrement(void) {
+ float g = 2.0f;
+ g += 1.0f;
+ clang_analyzer_dump_float(g); // expected-warning{{3 IEEEsingle}}
+ --g;
+ clang_analyzer_dump_float(g); // expected-warning{{2 IEEEsingle}}
+ clang_analyzer_dump_float(g++); // expected-warning{{2 IEEEsingle}}
+ clang_analyzer_dump_float(g); // expected-warning{{3 IEEEsingle}}
+
+ float small = 0.5f;
+ ++small;
+ clang_analyzer_dump_float(small); // expected-warning{{1.5 IEEEsingle}}
+
+ float inexact = 0.1f;
+ ++inexact;
+ clang_analyzer_dump_float(inexact); // expected-warning{{Unknown}}
+
+ float max = __FLT_MAX__;
+ ++max;
+ clang_analyzer_dump_float(max); // expected-warning{{Unknown}}
+}
diff --git a/clang/test/Analysis/inline.cpp b/clang/test/Analysis/inline.cpp
index 2b31460330e44..f718e6d8ae4c5 100644
--- a/clang/test/Analysis/inline.cpp
+++ b/clang/test/Analysis/inline.cpp
@@ -285,11 +285,11 @@ namespace DefaultArgs {
}
void testFloatReference() {
- clang_analyzer_eval(defaultFloatReference(1) == -1); // expected-warning{{UNKNOWN}}
- clang_analyzer_eval(defaultFloatReference() == -42); // expected-warning{{UNKNOWN}}
+ clang_analyzer_eval(defaultFloatReference(1) == -1); // expected-warning{{TRUE}}
+ clang_analyzer_eval(defaultFloatReference() == -42); // expected-warning{{TRUE}}
- clang_analyzer_eval(defaultFloatReferenceZero(1) == -1); // expected-warning{{UNKNOWN}}
- clang_analyzer_eval(defaultFloatReferenceZero() == 0); // expected-warning{{UNKNOWN}}
+ clang_analyzer_eval(defaultFloatReferenceZero(1) == -1); // expected-warning{{TRUE}}
+ clang_analyzer_eval(defaultFloatReferenceZero() == 0); // expected-warning{{TRUE}}
}
char defaultString(const char *s = "abc") {
diff --git a/clang/test/Analysis/operator-calls.cpp b/clang/test/Analysis/operator-calls.cpp
index dde21f164cee8..9825dc5ebf888 100644
--- a/clang/test/Analysis/operator-calls.cpp
+++ b/clang/test/Analysis/operator-calls.cpp
@@ -69,6 +69,7 @@ namespace RValues {
}
};
+ // 1.0 is no longer unknown. This function forces an unknown float.
float getUnknownFloat();
SmallOpaque getSmallOpaque() {
>From e3ae42a530e88dbbb1e6a94b6720f832e8de1147 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Fri, 7 Aug 2026 21:54:53 +0200
Subject: [PATCH 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 190706a4975e0f77441c2ca497c2aa4fc4a9f985 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Sat, 8 Aug 2026 05:20:46 +0200
Subject: [PATCH 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..af65106878f52 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 differently).
+ if (OriginalTy.isNull()) {
+ if (CastTy->isRealFloatingType() &&
+ &VB.getContext().getFloatTypeSemantics(CastTy) ==
+ &V.getValue()->getSemantics())
+ return V;
+ return UnknownVal();
+ }
+
// Float to float. Modeled only when the conversion is exact, which needs no
// rounding and so does not depend on the rounding mode in effect.
if (CastTy->isRealFloatingType()) {
@@ -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}}
More information about the cfe-commits
mailing list