https://github.com/jpjepko updated https://github.com/llvm/llvm-project/pull/214098
>From 0e8eef21d532ff7d817033263658c1268773dac8 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 9 Jul 2026 16:56:04 +0200 Subject: [PATCH 1/8] Add concrete float support in clangSA Currently float literals resolve to UnknownSVal in the analyzer. This commit introduces a ConcreteFloat SVal that wraps LLVM's APFloat to make the analyzer aware of concrete floating-point values, and teaches it some basic casting rule, like float -> int. --- .../Core/PathSensitive/APFloatPtr.h | 52 ++++++++++++ .../Core/PathSensitive/BasicValueFactory.h | 6 ++ .../Core/PathSensitive/SValBuilder.h | 13 +++ .../Core/PathSensitive/SVals.def | 1 + .../StaticAnalyzer/Core/PathSensitive/SVals.h | 13 +++ .../StaticAnalyzer/Core/BasicValueFactory.cpp | 20 +++++ clang/lib/StaticAnalyzer/Core/Environment.cpp | 1 + clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 8 +- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 35 ++++++++ clang/lib/StaticAnalyzer/Core/SVals.cpp | 39 ++++++++- .../Core/SimpleConstraintManager.cpp | 6 ++ .../StaticAnalyzer/Core/SimpleSValBuilder.cpp | 10 +++ clang/test/Analysis/constant-float-literals.c | 83 +++++++++++++++++++ clang/test/Analysis/operator-calls.cpp | 6 +- 14 files changed, 288 insertions(+), 5 deletions(-) create mode 100644 clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h create mode 100644 clang/test/Analysis/constant-float-literals.c diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h new file mode 100644 index 0000000000000..69a8a66653756 --- /dev/null +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h @@ -0,0 +1,52 @@ +//== APFloatPtr.h - Wrapper for APFloat objects owned separately -*- C++ -*--=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_APFLOATPTR_H +#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_APFLOATPTR_H + +#include "llvm/ADT/APFloat.h" +#include "llvm/Support/Compiler.h" + +namespace clang::ento { + +/// A safe wrapper around APFloat objects allocated and owned by +/// \c BasicValueFactory. This just wraps a common llvm::APFloat. +class APFloatPtr { + using APFloat = llvm::APFloat; + +public: + APFloatPtr() = delete; + APFloatPtr(const APFloatPtr &) = default; + APFloatPtr &operator=(const APFloatPtr &) & = default; + ~APFloatPtr() = default; + + /// You should not use this API. + /// If do, ensure that the \p Ptr is not going to dangle. + /// Prefer using \c BasicValueFactory::getFloatValue() to get an APFloatPtr + /// object. + static APFloatPtr unsafeConstructor(const APFloat *Ptr) { + return APFloatPtr(Ptr); + } + + LLVM_ATTRIBUTE_RETURNS_NONNULL + const APFloat *get() const { return Ptr; } + /*implicit*/ operator const APFloat &() const { return *get(); } + + const APFloat &operator*() const { return *Ptr; } + const APFloat *operator->() const { return Ptr; } + +private: + explicit APFloatPtr(const APFloat *Ptr) : Ptr(Ptr) {} + + /// Owned by \c BasicValueFactory. + const APFloat *Ptr; +}; + +} // namespace clang::ento + +#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_APFLOATPTR_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h index 38eaabf74dd34..65f6362f041fd 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h @@ -18,6 +18,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" #include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntPtr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" @@ -114,11 +115,14 @@ class PointerToMemberData : public llvm::FoldingSetNode { class BasicValueFactory { using APSIntSetTy = llvm::FoldingSet<llvm::FoldingSetNodeWrapper<llvm::APSInt>>; + using APFloatSetTy = + llvm::FoldingSet<llvm::FoldingSetNodeWrapper<llvm::APFloat>>; ASTContext &Ctx; llvm::BumpPtrAllocator& BPAlloc; APSIntSetTy APSIntSet; + APFloatSetTy APFloatSet; void *PersistentSVals = nullptr; void *PersistentSValPairs = nullptr; @@ -145,6 +149,8 @@ class BasicValueFactory { APSIntPtr getValue(const llvm::APInt &X, bool isUnsigned); APSIntPtr getValue(uint64_t X, QualType T); + APFloatPtr getFloatValue(const llvm::APFloat &X); + /// Returns the type of the APSInt used to store values of the given QualType. APSIntType getAPSIntType(QualType T) const { // For the purposes of the analysis and constraints, we treat atomics diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h index a92acfea8f702..5c2675023993e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h @@ -110,6 +110,11 @@ class SValBuilder { /// that value is returned. Otherwise, returns NULL. virtual const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal val) = 0; + /// If the SVal represents a concrete floating-point value, returns a pointer + /// to that value. Otherwise, returns NULL. + virtual const llvm::APFloat *getKnownFloatValue(ProgramStateRef state, + SVal val) = 0; + /// Tries to get the minimal possible (integer) value of a given SVal. This /// always returns the value of a ConcreteInt, but may return NULL if the /// value is symbolic and the constraint manager cannot provide a useful @@ -275,6 +280,14 @@ class SValBuilder { integer->getType()->isUnsignedIntegerOrEnumerationType())); } + nonloc::ConcreteFloat makeFloatVal(const FloatingLiteral *F) { + return nonloc::ConcreteFloat(BasicVals.getFloatValue(F->getValue())); + } + + nonloc::ConcreteFloat makeFloatVal(const llvm::APFloat &F) { + return nonloc::ConcreteFloat(BasicVals.getFloatValue(F)); + } + nonloc::ConcreteInt makeBoolVal(const ObjCBoolLiteralExpr *boolean) { return makeTruthVal(boolean->getValue(), boolean->getType()); } diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def index 36d2425d155a9..1dbd375afe0fa 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.def @@ -58,6 +58,7 @@ ABSTRACT_SVAL(DefinedOrUnknownSVal, SVal) SVAL_RANGE(Loc, ConcreteInt, MemRegionVal) ABSTRACT_SVAL(NonLoc, DefinedSVal) NONLOC_SVAL(CompoundVal, NonLoc) + NONLOC_SVAL(ConcreteFloat, NonLoc) NONLOC_SVAL(ConcreteInt, NonLoc) NONLOC_SVAL(LazyCompoundVal, NonLoc) NONLOC_SVAL(LocAsInteger, NonLoc) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h index 0561a2b8d1d77..a6835c1303765 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h @@ -17,6 +17,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/Type.h" #include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/APFloatPtr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntPtr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "llvm/ADT/APSInt.h" @@ -302,6 +303,18 @@ class SymbolVal : public NonLoc { static bool classof(SVal V) { return V.getKind() == SymbolValKind; } }; +/// Value representing floating-point constant. +class ConcreteFloat : public NonLoc { +public: + explicit ConcreteFloat(APFloatPtr V) : NonLoc(ConcreteFloatKind, V.get()) {} + + APFloatPtr getValue() const { + return APFloatPtr::unsafeConstructor(castDataAs<llvm::APFloat>()); + } + + static bool classof(SVal V) { return V.getKind() == ConcreteFloatKind; } +}; + /// Value representing integer constant. class ConcreteInt : public NonLoc { public: diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index b86f0e8309dc7..5fb203c665892 100644 --- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -83,6 +83,9 @@ BasicValueFactory::~BasicValueFactory() { for (const auto &I : APSIntSet) I.getValue().~APSInt(); + for (const auto &I : APFloatSet) + I.getValue().~APFloat(); + delete (PersistentSValsTy*) PersistentSVals; delete (PersistentSValPairsTy*) PersistentSValPairs; } @@ -121,6 +124,23 @@ APSIntPtr BasicValueFactory::getValue(uint64_t X, QualType T) { return getValue(getAPSIntType(T).getValue(X)); } +APFloatPtr BasicValueFactory::getFloatValue(const llvm::APFloat &X) { + llvm::FoldingSetNodeID ID; + void *InsertPos; + + using FoldNodeTy = llvm::FoldingSetNodeWrapper<llvm::APFloat>; + + X.Profile(ID); + FoldNodeTy *P = APFloatSet.FindNodeOrInsertPos(ID, InsertPos); + + if (!P) { + P = new (BPAlloc) FoldNodeTy(X); + APFloatSet.InsertNode(P, InsertPos); + } + + return APFloatPtr::unsafeConstructor(&P->getValue()); +} + const CompoundValData* BasicValueFactory::getCompoundValData(QualType T, llvm::ImmutableList<SVal> Vals) { diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp index 12f61c0416a13..911d28a464064 100644 --- a/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -95,6 +95,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::CharacterLiteralClass: case Stmt::CXXBoolLiteralExprClass: case Stmt::CXXScalarValueInitExprClass: + case Stmt::FloatingLiteralClass: case Stmt::ImplicitValueInitExprClass: case Stmt::IntegerLiteralClass: case Stmt::ObjCBoolLiteralExprClass: diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 6127328cefe23..ebe4a29617024 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -288,8 +288,12 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, if (const MemRegion *MR = State->getSVal(Ex, SF).getAsRegion()) { SVal OrigV = State->getSVal(MR); - CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV), - CastE->getType(), Ex->getType()); + // __builtin_bit_cast reinterprets raw bits. We cannot model this + // for floating-point values because evalCast performs a value + // conversion, not a bit reinterpretation. + if (!OrigV.getAs<nonloc::ConcreteFloat>()) + CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV), + CastE->getType(), Ex->getType()); } Dst.insert(Engine.makeNodeWithBinding(Node, CastE, CastedV)); } diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 57c97b2852445..4f8a73e495aeb 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -375,6 +375,9 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { case Stmt::IntegerLiteralClass: return makeIntVal(cast<IntegerLiteral>(E)); + case Stmt::FloatingLiteralClass: + return makeFloatVal(cast<FloatingLiteral>(E)); + case Stmt::ObjCBoolLiteralExprClass: return makeBoolVal(cast<ObjCBoolLiteralExpr>(E)); @@ -863,6 +866,38 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> { // Compound to whatever. return UnknownVal(); } + SVal VisitConcreteFloat(nonloc::ConcreteFloat V) { + // Float to float. + if (CastTy->isRealFloatingType()) { + const llvm::fltSemantics &TargetSem = + VB.getContext().getFloatTypeSemantics(CastTy); + llvm::APFloat Value = *V.getValue(); + bool LosesInfo = false; + Value.convert(TargetSem, llvm::APFloat::rmNearestTiesToEven, &LosesInfo); + if (!LosesInfo) + return VB.makeFloatVal(Value); + return UnknownVal(); + } + + // Float to integer. + if (CastTy->isIntegralOrEnumerationType()) { + APSIntType ResultType = VB.getBasicValueFactory().getAPSIntType(CastTy); + llvm::APSInt Result = ResultType.getValue(0); + llvm::APFloat Value = *V.getValue(); + bool IsExact; + llvm::APFloat::opStatus Status = + Value.convertToInteger(Result, llvm::APFloat::rmTowardZero, &IsExact); + if (Status == llvm::APFloat::opOK || Status == llvm::APFloat::opInexact) + return VB.makeIntVal(Result); + return UnknownVal(); + } + + // Float to bool. + if (CastTy->isBooleanType()) + return VB.makeTruthVal(!V.getValue()->isZero(), CastTy); + + return UnknownVal(); + } SVal VisitConcreteInt(nonloc::ConcreteInt V) { auto CastedValue = [V, this]() { llvm::APSInt Value = V.getValue(); diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp index 483e62d4a9a7e..32c8968aea395 100644 --- a/clang/lib/StaticAnalyzer/Core/SVals.cpp +++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp @@ -147,6 +147,20 @@ class TypeRetrievingVisitor return Context.BoolTy; return Context.getIntTypeForBitwidth(Value.getBitWidth(), Value.isSigned()); } + QualType VisitConcreteFloat(nonloc::ConcreteFloat CF) { + const llvm::fltSemantics &Sem = CF.getValue()->getSemantics(); + if (&Sem == &llvm::APFloat::IEEEsingle()) + return Context.FloatTy; + if (&Sem == &llvm::APFloat::IEEEdouble()) + return Context.DoubleTy; + if (&Sem == &llvm::APFloat::x87DoubleExtended()) + return Context.LongDoubleTy; + if (&Sem == &llvm::APFloat::IEEEhalf()) + return Context.Float16Ty; + if (&Sem == &llvm::APFloat::IEEEquad()) + return Context.Float128Ty; + return QualType{}; + } QualType VisitLocAsInteger(nonloc::LocAsInteger LI) { QualType NestedType = Visit(LI.getLoc()); if (NestedType.isNull()) @@ -243,7 +257,8 @@ nonloc::PointerToMember::iterator nonloc::PointerToMember::end() const { //===----------------------------------------------------------------------===// bool SVal::isConstant() const { - return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>(); + return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>() || + getAs<nonloc::ConcreteFloat>(); } bool SVal::isConstant(int I) const { @@ -255,6 +270,8 @@ bool SVal::isConstant(int I) const { } bool SVal::isZeroConstant() const { + if (std::optional<nonloc::ConcreteFloat> FV = getAs<nonloc::ConcreteFloat>()) + return FV->getValue()->isZero(); return isConstant(0); } @@ -312,6 +329,26 @@ void SVal::dumpToStream(raw_ostream &os) const { void NonLoc::dumpToStream(raw_ostream &os) const { switch (getKind()) { + case nonloc::ConcreteFloatKind: { + const llvm::APFloat &Value = *castAs<nonloc::ConcreteFloat>().getValue(); + llvm::SmallString<16> Str; + Value.toString(Str); + os << Str << ' '; + const auto &Sem = Value.getSemantics(); + if (&Sem == &llvm::APFloat::IEEEhalf()) + os << "IEEEhalf"; + else if (&Sem == &llvm::APFloat::IEEEsingle()) + os << "IEEEsingle"; + else if (&Sem == &llvm::APFloat::IEEEdouble()) + os << "IEEEdouble"; + else if (&Sem == &llvm::APFloat::IEEEquad()) + os << "IEEEquad"; + else if (&Sem == &llvm::APFloat::x87DoubleExtended()) + os << "x87DoubleExtended"; + else + os << "unknown"; + break; + } case nonloc::ConcreteIntKind: { APSIntPtr Value = castAs<nonloc::ConcreteInt>().getValue(); os << Value << ' ' << (Value->isSigned() ? 'S' : 'U') diff --git a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp index dd5e374f52a91..b5d3ef1366d1c 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp @@ -73,6 +73,12 @@ ProgramStateRef SimpleConstraintManager::assumeAux(ProgramStateRef State, return assumeSym(State, Sym, Assumption); } + case nonloc::ConcreteFloatKind: { + bool b = !Cond.castAs<nonloc::ConcreteFloat>().getValue()->isZero(); + bool isFeasible = b ? Assumption : !Assumption; + return isFeasible ? State : nullptr; + } + case nonloc::ConcreteIntKind: { bool b = *Cond.castAs<nonloc::ConcreteInt>().getValue() != 0; bool isFeasible = b ? Assumption : !Assumption; diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 7d154cbc840ac..ace03e0df7ac0 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -81,6 +81,9 @@ class SimpleSValBuilder : public SValBuilder { /// (integer) value, that value is returned. Otherwise, returns NULL. const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal V) override; + const llvm::APFloat *getKnownFloatValue(ProgramStateRef state, + SVal V) override; + /// Evaluates a given SVal by recursively evaluating and simplifying the /// children SVals, then returns its minimal possible (integer) value. If the /// constraint manager cannot provide a meaningful answer, this returns NULL. @@ -1223,6 +1226,13 @@ const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state, return getConstValue(state, simplifySVal(state, V)); } +const llvm::APFloat * +SimpleSValBuilder::getKnownFloatValue(ProgramStateRef state, SVal V) { + if (auto X = V.getAs<nonloc::ConcreteFloat>()) + return X->getValue().get(); + return nullptr; +} + const llvm::APSInt *SimpleSValBuilder::getMinValue(ProgramStateRef state, SVal V) { V = simplifySVal(state, V); diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c new file mode 100644 index 0000000000000..88a4a800a163c --- /dev/null +++ b/clang/test/Analysis/constant-float-literals.c @@ -0,0 +1,83 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify \ +// RUN: -analyzer-config eagerly-assume=false %s + +void clang_analyzer_dump_float(float); +void clang_analyzer_dump_double(double); +void clang_analyzer_eval(int); + +//===----------------------------------------------------------------------===// +// Floating-point literals are modeled as ConcreteFloat SVals. +//===----------------------------------------------------------------------===// + +void testFloatLiterals(void) { + clang_analyzer_dump_float(0.0f); // expected-warning{{0 IEEEsingle}} + clang_analyzer_dump_float(1.0f); // expected-warning{{1 IEEEsingle}} + clang_analyzer_dump_float(3.14f); // expected-warning{{3.1400001 IEEEsingle}} + clang_analyzer_dump_double(0.0); // expected-warning{{0 IEEEdouble}} + clang_analyzer_dump_double(1.0); // expected-warning{{1 IEEEdouble}} + clang_analyzer_dump_double(3.14); // expected-warning{{3.1400000000000001 IEEEdouble}} +} + +//===----------------------------------------------------------------------===// +// Variables assigned from literals retain the ConcreteFloat value. +//===----------------------------------------------------------------------===// + +void testVariables(void) { + float f = 1.5f; + double d = 2.5; + clang_analyzer_dump_float(f); // expected-warning{{1.5 IEEEsingle}} + clang_analyzer_dump_double(d); // expected-warning{{2.5 IEEEdouble}} +} + +//===----------------------------------------------------------------------===// +// Float-to-integer casts (truncation). +//===----------------------------------------------------------------------===// + +void testFloatToInt(void) { + float f = 1.9f; + double d = 2.7; + int i = (int)f; + int j = (int)d; + clang_analyzer_eval(i == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(j == 2); // expected-warning{{TRUE}} +} + +//===----------------------------------------------------------------------===// +// Float-to-bool casts. +//===----------------------------------------------------------------------===// + +void testFloatToBool(void) { + float zero = 0.0f; + float nonzero = 1.0f; + clang_analyzer_eval((int)((_Bool)zero) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval((int)((_Bool)nonzero) == 1); // expected-warning{{TRUE}} +} + +//===----------------------------------------------------------------------===// +// Float-to-float casts (precision change without loss). +//===----------------------------------------------------------------------===// + +void testFloatUpcast(void) { + float f = 1.5f; + double d = f; + // 1.5 is exactly representable in both, so no loss. + clang_analyzer_dump_double(d); // expected-warning{{1.5 IEEEdouble}} +} + +//===----------------------------------------------------------------------===// +// Unknown float values (parameters, arithmetic results). +//===----------------------------------------------------------------------===// + +void testUnknown(float f) { + clang_analyzer_dump_float(f); // expected-warning{{Unknown}} + clang_analyzer_dump_float(f + 1.0f); // expected-warning{{Unknown}} +} + +//===----------------------------------------------------------------------===// +// Division by zero detection with concrete floats. +//===----------------------------------------------------------------------===// + +float testDivByZeroFloat(void) { + float x = 0.0f; + return 1.0f / x; // expected-warning{{Division by zero}} +} diff --git a/clang/test/Analysis/operator-calls.cpp b/clang/test/Analysis/operator-calls.cpp index 57da7cdc16923..dde21f164cee8 100644 --- a/clang/test/Analysis/operator-calls.cpp +++ b/clang/test/Analysis/operator-calls.cpp @@ -69,15 +69,17 @@ namespace RValues { } }; + float getUnknownFloat(); + SmallOpaque getSmallOpaque() { SmallOpaque obj; - obj.x = 1.0; + obj.x = getUnknownFloat(); return obj; } LargeOpaque getLargeOpaque() { LargeOpaque obj = LargeOpaque(); - obj.x[0] = 1.0; + obj.x[0] = getUnknownFloat(); return obj; } >From 7ac30704793349a7b103762fa56cea9a22d2ed30 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 9 Jul 2026 21:54:52 +0200 Subject: [PATCH 2/8] Refactor and add target-specific tests --- clang/lib/StaticAnalyzer/Core/SVals.cpp | 58 +++++++++------ clang/test/Analysis/constant-float-literals.c | 71 ++++++++++++++----- 2 files changed, 88 insertions(+), 41 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp index 32c8968aea395..b13019d74e883 100644 --- a/clang/lib/StaticAnalyzer/Core/SVals.cpp +++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp @@ -34,6 +34,27 @@ using namespace clang; using namespace ento; +/// Returns a human-readable name for the most common floating-point semantics. +/// Anything else is reported as "unknown." +static StringRef getFloatSemanticsName(const llvm::fltSemantics &Sem) { + switch (llvm::APFloat::SemanticsToEnum(Sem)) { + case llvm::APFloat::S_IEEEhalf: + return "IEEEhalf"; + case llvm::APFloat::S_BFloat: + return "BFloat"; + case llvm::APFloat::S_IEEEsingle: + return "IEEEsingle"; + case llvm::APFloat::S_IEEEdouble: + return "IEEEdouble"; + case llvm::APFloat::S_IEEEquad: + return "IEEEquad"; + case llvm::APFloat::S_x87DoubleExtended: + return "x87DoubleExtended"; + default: + return "unknown"; + } +} + //===----------------------------------------------------------------------===// // Symbol iteration within an SVal. //===----------------------------------------------------------------------===// @@ -148,18 +169,22 @@ class TypeRetrievingVisitor return Context.getIntTypeForBitwidth(Value.getBitWidth(), Value.isSigned()); } QualType VisitConcreteFloat(nonloc::ConcreteFloat CF) { - const llvm::fltSemantics &Sem = CF.getValue()->getSemantics(); - if (&Sem == &llvm::APFloat::IEEEsingle()) + switch (llvm::APFloat::SemanticsToEnum(CF.getValue()->getSemantics())) { + case llvm::APFloat::S_IEEEhalf: + return Context.Float16Ty; + case llvm::APFloat::S_BFloat: + return Context.BFloat16Ty; + case llvm::APFloat::S_IEEEsingle: return Context.FloatTy; - if (&Sem == &llvm::APFloat::IEEEdouble()) + case llvm::APFloat::S_IEEEdouble: return Context.DoubleTy; - if (&Sem == &llvm::APFloat::x87DoubleExtended()) - return Context.LongDoubleTy; - if (&Sem == &llvm::APFloat::IEEEhalf()) - return Context.Float16Ty; - if (&Sem == &llvm::APFloat::IEEEquad()) + case llvm::APFloat::S_IEEEquad: return Context.Float128Ty; - return QualType{}; + case llvm::APFloat::S_x87DoubleExtended: + return Context.LongDoubleTy; + default: + return QualType{}; + } } QualType VisitLocAsInteger(nonloc::LocAsInteger LI) { QualType NestedType = Visit(LI.getLoc()); @@ -333,20 +358,7 @@ void NonLoc::dumpToStream(raw_ostream &os) const { const llvm::APFloat &Value = *castAs<nonloc::ConcreteFloat>().getValue(); llvm::SmallString<16> Str; Value.toString(Str); - os << Str << ' '; - const auto &Sem = Value.getSemantics(); - if (&Sem == &llvm::APFloat::IEEEhalf()) - os << "IEEEhalf"; - else if (&Sem == &llvm::APFloat::IEEEsingle()) - os << "IEEEsingle"; - else if (&Sem == &llvm::APFloat::IEEEdouble()) - os << "IEEEdouble"; - else if (&Sem == &llvm::APFloat::IEEEquad()) - os << "IEEEquad"; - else if (&Sem == &llvm::APFloat::x87DoubleExtended()) - os << "x87DoubleExtended"; - else - os << "unknown"; + os << Str << ' ' << getFloatSemanticsName(Value.getSemantics()); break; } case nonloc::ConcreteIntKind: { diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c index 88a4a800a163c..f1405a187c6e2 100644 --- a/clang/test/Analysis/constant-float-literals.c +++ b/clang/test/Analysis/constant-float-literals.c @@ -1,8 +1,19 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify \ -// RUN: -analyzer-config eagerly-assume=false %s +// Semantics of long double differ depending on target, which is why we run on +// multiple targets. +// +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify=common,x87 %s +// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify=common,quad %s +// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify=common,ldbl64 %s void clang_analyzer_dump_float(float); void clang_analyzer_dump_double(double); +void clang_analyzer_dump_longdouble(long double); void clang_analyzer_eval(int); //===----------------------------------------------------------------------===// @@ -10,12 +21,24 @@ void clang_analyzer_eval(int); //===----------------------------------------------------------------------===// void testFloatLiterals(void) { - clang_analyzer_dump_float(0.0f); // expected-warning{{0 IEEEsingle}} - clang_analyzer_dump_float(1.0f); // expected-warning{{1 IEEEsingle}} - clang_analyzer_dump_float(3.14f); // expected-warning{{3.1400001 IEEEsingle}} - clang_analyzer_dump_double(0.0); // expected-warning{{0 IEEEdouble}} - clang_analyzer_dump_double(1.0); // expected-warning{{1 IEEEdouble}} - clang_analyzer_dump_double(3.14); // expected-warning{{3.1400000000000001 IEEEdouble}} + clang_analyzer_dump_float(0.0f); // common-warning{{0 IEEEsingle}} + clang_analyzer_dump_float(1.0f); // common-warning{{1 IEEEsingle}} + clang_analyzer_dump_float(3.14f); // common-warning{{3.1400001 IEEEsingle}} + clang_analyzer_dump_double(0.0); // common-warning{{0 IEEEdouble}} + clang_analyzer_dump_double(1.0); // common-warning{{1 IEEEdouble}} + clang_analyzer_dump_double(3.14); // common-warning{{3.1400000000000001 IEEEdouble}} +} + +//===----------------------------------------------------------------------===// +// long double is modeled with the target's floating-point semantics. +//===----------------------------------------------------------------------===// + +void testLongDoubleLiterals(void) { + // 0.0 and 1.0 are representable exactly in all formats so only semantic name + // differs on different targets. + clang_analyzer_dump_longdouble(0.0L); // x87-warning{{0 x87DoubleExtended}} +quad-warning{{0 IEEEquad}} ldbl64-warning{{0 IEEEdouble}} + clang_analyzer_dump_longdouble(1.0L); // x87-warning{{1 x87DoubleExtended}} quad-warning{{1 IEEEquad}} ldbl64-warning{{1 IEEEdouble}} } //===----------------------------------------------------------------------===// @@ -25,8 +48,8 @@ void testFloatLiterals(void) { void testVariables(void) { float f = 1.5f; double d = 2.5; - clang_analyzer_dump_float(f); // expected-warning{{1.5 IEEEsingle}} - clang_analyzer_dump_double(d); // expected-warning{{2.5 IEEEdouble}} + clang_analyzer_dump_float(f); // common-warning{{1.5 IEEEsingle}} + clang_analyzer_dump_double(d); // common-warning{{2.5 IEEEdouble}} } //===----------------------------------------------------------------------===// @@ -38,8 +61,8 @@ void testFloatToInt(void) { double d = 2.7; int i = (int)f; int j = (int)d; - clang_analyzer_eval(i == 1); // expected-warning{{TRUE}} - clang_analyzer_eval(j == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(i == 1); // common-warning{{TRUE}} + clang_analyzer_eval(j == 2); // common-warning{{TRUE}} } //===----------------------------------------------------------------------===// @@ -49,8 +72,8 @@ void testFloatToInt(void) { void testFloatToBool(void) { float zero = 0.0f; float nonzero = 1.0f; - clang_analyzer_eval((int)((_Bool)zero) == 0); // expected-warning{{TRUE}} - clang_analyzer_eval((int)((_Bool)nonzero) == 1); // expected-warning{{TRUE}} + clang_analyzer_eval((int)((_Bool)zero) == 0); // common-warning{{TRUE}} + clang_analyzer_eval((int)((_Bool)nonzero) == 1); // common-warning{{TRUE}} } //===----------------------------------------------------------------------===// @@ -61,7 +84,19 @@ void testFloatUpcast(void) { float f = 1.5f; double d = f; // 1.5 is exactly representable in both, so no loss. - clang_analyzer_dump_double(d); // expected-warning{{1.5 IEEEdouble}} + clang_analyzer_dump_double(d); // common-warning{{1.5 IEEEdouble}} +} + +//===----------------------------------------------------------------------===// +// Float-to-float casts (inexact narrowing stays Unknown). +//===----------------------------------------------------------------------===// + +void testFloatNarrowing(void) { + double d = 3.14; + float f = (float)d; + // 3.14 is not exactly representable in float, and rounding direction is + // implementation-defined, so we don't model here. + clang_analyzer_dump_float(f); // common-warning{{Unknown}} } //===----------------------------------------------------------------------===// @@ -69,8 +104,8 @@ void testFloatUpcast(void) { //===----------------------------------------------------------------------===// void testUnknown(float f) { - clang_analyzer_dump_float(f); // expected-warning{{Unknown}} - clang_analyzer_dump_float(f + 1.0f); // expected-warning{{Unknown}} + clang_analyzer_dump_float(f); // common-warning{{Unknown}} + clang_analyzer_dump_float(f + 1.0f); // common-warning{{Unknown}} } //===----------------------------------------------------------------------===// @@ -79,5 +114,5 @@ void testUnknown(float f) { float testDivByZeroFloat(void) { float x = 0.0f; - return 1.0f / x; // expected-warning{{Division by zero}} + return 1.0f / x; // common-warning{{Division by zero}} } >From a2388ce47fc4e148f76f781272972db27228cd7c Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Mon, 13 Jul 2026 17:18:55 +0200 Subject: [PATCH 3/8] Add modeling support for comparisons and arithmetic --- .../StaticAnalyzer/Core/SimpleSValBuilder.cpp | 72 ++++++++++++++++++- clang/test/Analysis/constant-float-literals.c | 42 ++++++++++- 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index ace03e0df7ac0..48aec507a85a9 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -440,7 +440,9 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, rhs = *simplifiedRhsAsNonLoc; // Handle trivial case where left-side and right-side are the same. - if (lhs == rhs) + // Deliberately exclude floating-point values since x - x isn't necessarily 0 + // (e.g., inf - inf), and x == x is false when x is NaN. + if (lhs == rhs && !lhs.getAs<nonloc::ConcreteFloat>()) switch (op) { default: break; @@ -526,6 +528,74 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, } } } + case nonloc::ConcreteFloatKind: { + // Only fold operations between concrete floats that have the same + // semantics; normally implicit casts are inserted in the AST so they + // match, but check anyway for robustness. + std::optional<nonloc::ConcreteFloat> RHSFloat = + rhs.getAs<nonloc::ConcreteFloat>(); + if (!RHSFloat) + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + + const llvm::APFloat &L = *lhs.castAs<nonloc::ConcreteFloat>().getValue(); + const llvm::APFloat &R = *RHSFloat->getValue(); + if (&L.getSemantics() != &R.getSemantics()) + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + + // We can model comparisons between floats since they are defined for + // every value regardless of rounding mode or excess precision. + llvm::APFloat::cmpResult Cmp = L.compare(R); + switch (op) { + case BO_EQ: + return makeTruthVal(Cmp == llvm::APFloat::cmpEqual, resultTy); + case BO_NE: + return makeTruthVal(Cmp != llvm::APFloat::cmpEqual, resultTy); + case BO_LT: + return makeTruthVal(Cmp == llvm::APFloat::cmpLessThan, resultTy); + case BO_GT: + return makeTruthVal(Cmp == llvm::APFloat::cmpGreaterThan, resultTy); + case BO_LE: + return makeTruthVal(Cmp == llvm::APFloat::cmpLessThan || + Cmp == llvm::APFloat::cmpEqual, + resultTy); + case BO_GE: + return makeTruthVal(Cmp == llvm::APFloat::cmpGreaterThan || + Cmp == llvm::APFloat::cmpEqual, + resultTy); + default: + break; + } + + // We can model arithmetic (operators +, -, *, /) only when both operands + // are finite and result is exact (which needs no rounding). Inexact, + // non-finite, or exceptions (like overflow or div by zero) is unmodeled. + if (!L.isFinite() || !R.isFinite()) + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + + llvm::APFloat Result = L; + llvm::APFloat::opStatus Status; + switch (op) { + case BO_Add: + Status = Result.add(R, llvm::APFloat::rmNearestTiesToEven); + break; + case BO_Sub: + Status = Result.subtract(R, llvm::APFloat::rmNearestTiesToEven); + break; + case BO_Mul: + Status = Result.multiply(R, llvm::APFloat::rmNearestTiesToEven); + break; + case BO_Div: + Status = Result.divide(R, llvm::APFloat::rmNearestTiesToEven); + break; + default: + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + } + + if (Status == llvm::APFloat::opOK) + return makeFloatVal(Result); + + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + } case nonloc::ConcreteIntKind: { llvm::APSInt LHSValue = lhs.castAs<nonloc::ConcreteInt>().getValue(); diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c index f1405a187c6e2..431adca6121e1 100644 --- a/clang/test/Analysis/constant-float-literals.c +++ b/clang/test/Analysis/constant-float-literals.c @@ -36,9 +36,14 @@ void testFloatLiterals(void) { void testLongDoubleLiterals(void) { // 0.0 and 1.0 are representable exactly in all formats so only semantic name // differs on different targets. - clang_analyzer_dump_longdouble(0.0L); // x87-warning{{0 x87DoubleExtended}} -quad-warning{{0 IEEEquad}} ldbl64-warning{{0 IEEEdouble}} - clang_analyzer_dump_longdouble(1.0L); // x87-warning{{1 x87DoubleExtended}} quad-warning{{1 IEEEquad}} ldbl64-warning{{1 IEEEdouble}} + clang_analyzer_dump_longdouble(0.0L); + // x87-warning@-1{{0 x87DoubleExtended}} + // quad-warning@-2{{0 IEEEquad}} + // ldbl64-warning@-3{{0 IEEEdouble}} + clang_analyzer_dump_longdouble(1.0L); + // x87-warning@-1{{1 x87DoubleExtended}} + // quad-warning@-2{{1 IEEEquad}} + // ldbl64-warning@-3{{1 IEEEdouble}} } //===----------------------------------------------------------------------===// @@ -108,6 +113,37 @@ void testUnknown(float f) { clang_analyzer_dump_float(f + 1.0f); // common-warning{{Unknown}} } +//===----------------------------------------------------------------------===// +// Arithmetic between concrete floats is folded only when the result is exact. +//===----------------------------------------------------------------------===// + +void testExactArithmetic(void) { + // All of these have exactly representable results, so they are independent + // of rounding mode and evaluation precision. + clang_analyzer_dump_float(1.0f + 2.0f); // common-warning{{3 IEEEsingle}} + clang_analyzer_dump_float(5.0f - 1.5f); // common-warning{{3.5 IEEEsingle}} + clang_analyzer_dump_float(1.5f * 2.0f); // common-warning{{3 IEEEsingle}} + clang_analyzer_dump_float(3.0f / 4.0f); // common-warning{{0.75 IEEEsingle}} + clang_analyzer_dump_double(0.5 + 0.25); // common-warning{{0.75 IEEEdouble}} +} + +void testInexactArithmetic(void) { + // 0.1f + 0.2f is not exactly representable in single precision; the rounded + // result depends on the rounding mode / evaluation precision, so we do not + // model it. + clang_analyzer_dump_float(0.1f + 0.2f); // common-warning{{Unknown}} + // 1.0f / 3.0f is inexact. + clang_analyzer_dump_float(1.0f / 3.0f); // common-warning{{Unknown}} +} + +void testComparisons(void) { + clang_analyzer_eval(1.0f < 2.0f); // common-warning{{TRUE}} + clang_analyzer_eval(2.0f < 1.0f); // common-warning{{FALSE}} + clang_analyzer_eval(1.5 == 1.5); // common-warning{{TRUE}} + clang_analyzer_eval(1.5 != 2.5); // common-warning{{TRUE}} + clang_analyzer_eval(2.0f >= 2.0f); // common-warning{{TRUE}} +} + //===----------------------------------------------------------------------===// // Division by zero detection with concrete floats. //===----------------------------------------------------------------------===// >From 65d317bb191403ec1d91d62b828a3a607d24aaf2 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 14 Jul 2026 17:38:45 +0200 Subject: [PATCH 4/8] Add unary negation support --- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 7 +++++++ clang/test/Analysis/constant-float-literals.c | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 4f8a73e495aeb..55cbae3019fd7 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -459,6 +459,13 @@ SVal SValBuilder::evalMinus(NonLoc X) { switch (X.getKind()) { case nonloc::ConcreteIntKind: return makeIntVal(-X.castAs<nonloc::ConcreteInt>().getValue()); + case nonloc::ConcreteFloatKind: { + // Negation only flips the sign bit and is well-defined for all + // floating-point values regardless of semantics, so model it. + llvm::APFloat Value = *X.castAs<nonloc::ConcreteFloat>().getValue(); + Value.changeSign(); + return makeFloatVal(Value); + } case nonloc::SymbolValKind: return makeNonLoc(X.castAs<nonloc::SymbolVal>().getSymbol(), UO_Minus, X.getType(Context)); diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c index 431adca6121e1..7e3dd38d78f07 100644 --- a/clang/test/Analysis/constant-float-literals.c +++ b/clang/test/Analysis/constant-float-literals.c @@ -144,6 +144,19 @@ void testComparisons(void) { clang_analyzer_eval(2.0f >= 2.0f); // common-warning{{TRUE}} } +//===----------------------------------------------------------------------===// +// Unary negation is always exact (a sign-bit flip). +//===----------------------------------------------------------------------===// + +void testNegation(void) { + float f = 1.5f; + double d = 2.5; + clang_analyzer_dump_float(-f); // common-warning{{-1.5 IEEEsingle}} + clang_analyzer_dump_double(-d); // common-warning{{-2.5 IEEEdouble}} + clang_analyzer_dump_float(-(-f)); // common-warning{{1.5 IEEEsingle}} + clang_analyzer_eval(-f < 0.0f); // common-warning{{TRUE}} +} + //===----------------------------------------------------------------------===// // Division by zero detection with concrete floats. //===----------------------------------------------------------------------===// >From f3c59dbfa7d48380a793906b23b32f2ae8a153a0 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Wed, 15 Jul 2026 21:11:47 +0200 Subject: [PATCH 5/8] Add concrete float to analyzerExplain and relnotes --- clang/docs/ReleaseNotes.md | 6 +++++ .../StaticAnalyzer/Checkers/SValExplainer.h | 10 +++++++++ clang/test/Analysis/constant-float-literals.c | 22 +++++++++++++++---- clang/test/Analysis/explain-svals.c | 7 ++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 7976b82b63f6e..36cff7b8cda3b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -555,6 +555,12 @@ features cannot lower the translation-unit ABI level; - The lock-order-reversal check in ``alpha.unix.PthreadLock`` is now disabled by default. It can be re-enabled with the ``WarnOnLockOrderReversal`` option. +- The analyzer now models concrete floating-point values. Floating-point + literals, simple arithmetic operations, and casts between floating-point types + are tracked as concrete values instead of being treated as unknown. Only + results that are exact (independent of rounding mode and evaluation precision) + are modeled. Fixes #GH82910. + #### Moved checkers #### Diagnostic changes diff --git a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h index 6c1025ecc7f4d..ac4c65d575073 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h +++ b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h @@ -107,6 +107,16 @@ class SValExplainer : public FullSValVisitor<SValExplainer, std::string> { return Str; } + std::string VisitConcreteFloat(nonloc::ConcreteFloat V) { + const llvm::APFloat &F = *V.getValue(); + std::string Str; + llvm::raw_string_ostream OS(Str); + llvm::SmallString<16> Buf; + F.toString(Buf); + OS << "concrete floating-point value '" << Buf << "'"; + return Str; + } + std::string VisitLazyCompoundVal(nonloc::LazyCompoundVal V) { return "lazily frozen compound value of " + Visit(V.getRegion()); } diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c index 7e3dd38d78f07..09a57e55a42a6 100644 --- a/clang/test/Analysis/constant-float-literals.c +++ b/clang/test/Analysis/constant-float-literals.c @@ -151,10 +151,24 @@ void testComparisons(void) { void testNegation(void) { float f = 1.5f; double d = 2.5; - clang_analyzer_dump_float(-f); // common-warning{{-1.5 IEEEsingle}} - clang_analyzer_dump_double(-d); // common-warning{{-2.5 IEEEdouble}} - clang_analyzer_dump_float(-(-f)); // common-warning{{1.5 IEEEsingle}} - clang_analyzer_eval(-f < 0.0f); // common-warning{{TRUE}} + clang_analyzer_dump_float(-f); // common-warning{{-1.5 IEEEsingle}} + clang_analyzer_dump_double(-d); // common-warning{{-2.5 IEEEdouble}} + clang_analyzer_dump_float(-(-f)); // common-warning{{1.5 IEEEsingle}} + clang_analyzer_eval(-f < 0.0f); // common-warning{{TRUE}} +} + +//===----------------------------------------------------------------------===// +// Infinity from an overflowing literal is concrete, but arithmetic on it is +// not folded and casting it to int is not modeled (both would depend on +// IEC 60559 semantics / are undefined in C), while comparisons still work. +//===----------------------------------------------------------------------===// + +void testInfinity(void) { + float big = 1e400f; // common-warning{{magnitude of floating-point constant too large}} + clang_analyzer_dump_float(big); // common-warning{{+Inf IEEEsingle}} + clang_analyzer_dump_float(big + 1.0f); // common-warning{{Unknown}} + clang_analyzer_eval(big > 1.0f); // common-warning{{TRUE}} + clang_analyzer_dump_float((float)(int)big); // common-warning{{Unknown}} } //===----------------------------------------------------------------------===// diff --git a/clang/test/Analysis/explain-svals.c b/clang/test/Analysis/explain-svals.c index 4e095efbab777..e16f87fd04ff7 100644 --- a/clang/test/Analysis/explain-svals.c +++ b/clang/test/Analysis/explain-svals.c @@ -11,6 +11,8 @@ struct S { void clang_analyzer_explain_int(int); void clang_analyzer_explain_voidp(void *); void clang_analyzer_explain_S(struct S); +void clang_analyzer_explain_float(float); +void clang_analyzer_explain_double(double); int glob; @@ -32,6 +34,11 @@ void test_3(int param) { clang_analyzer_explain_voidp(¶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 <[email protected]> Date: Fri, 7 Aug 2026 19:41:11 +0200 Subject: [PATCH 6/8] Restrict ConcreteFloat to finite normal values and add int conversions --- clang/docs/ReleaseNotes.md | 12 +- .../Core/PathSensitive/SValBuilder.h | 22 ++- .../Checkers/DivZeroChecker.cpp | 5 + .../StaticAnalyzer/Core/BasicValueFactory.cpp | 3 + clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 28 ++- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 60 ++++-- clang/lib/StaticAnalyzer/Core/SVals.cpp | 9 +- .../StaticAnalyzer/Core/SimpleSValBuilder.cpp | 29 +-- clang/test/Analysis/constant-float-16bit.c | 39 ++++ .../Analysis/constant-float-32bit-double.c | 25 +++ clang/test/Analysis/constant-float-cxx.cpp | 48 +++++ clang/test/Analysis/constant-float-literals.c | 181 ------------------ .../Analysis/constant-float-long-double.c | 78 ++++++++ clang/test/Analysis/constant-float.c | 172 +++++++++++++++++ clang/test/Analysis/inline.cpp | 8 +- clang/test/Analysis/operator-calls.cpp | 1 + 16 files changed, 482 insertions(+), 238 deletions(-) create mode 100644 clang/test/Analysis/constant-float-16bit.c create mode 100644 clang/test/Analysis/constant-float-32bit-double.c create mode 100644 clang/test/Analysis/constant-float-cxx.cpp delete mode 100644 clang/test/Analysis/constant-float-literals.c create mode 100644 clang/test/Analysis/constant-float-long-double.c create mode 100644 clang/test/Analysis/constant-float.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 36cff7b8cda3b..263055b6448fa 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -556,10 +556,14 @@ features cannot lower the translation-unit ABI level; It can be re-enabled with the ``WarnOnLockOrderReversal`` option. - The analyzer now models concrete floating-point values. Floating-point - literals, simple arithmetic operations, and casts between floating-point types - are tracked as concrete values instead of being treated as unknown. Only - results that are exact (independent of rounding mode and evaluation precision) - are modeled. Fixes #GH82910. + literals, simple arithmetic operations, and casts, including to and from + integer types, are tracked as concrete values instead of being treated as + unknown. Only results that are exact (independent of rounding mode and + evaluation precision) are modeled. Infinities and NaNs remain unknown, because + a NaN's bit pattern is non-deterministic. Subnormals remain unknown because + their semantics are controlled by factors the analyzer cannot see. + ``__ibm128`` is not modeled, since LLVM implements several of its operations + through an inaccurate fallback format. Fixes #GH82910. #### Moved checkers diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h index 5c2675023993e..70f7591ecf297 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h @@ -110,11 +110,6 @@ class SValBuilder { /// that value is returned. Otherwise, returns NULL. virtual const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal val) = 0; - /// If the SVal represents a concrete floating-point value, returns a pointer - /// to that value. Otherwise, returns NULL. - virtual const llvm::APFloat *getKnownFloatValue(ProgramStateRef state, - SVal val) = 0; - /// Tries to get the minimal possible (integer) value of a given SVal. This /// always returns the value of a ConcreteInt, but may return NULL if the /// value is symbolic and the constraint manager cannot provide a useful @@ -280,6 +275,23 @@ class SValBuilder { integer->getType()->isUnsignedIntegerOrEnumerationType())); } + /// Whether a nonloc::ConcreteFloat may hold \p V. + /// + /// Infinities and NaNs are not modeled: the semantics of which depend on + /// IEC 60559 conformance which is not readily available to the analyzer, so + /// we leave these as unknowns. NaNs we can never model, since LangRef + /// dictates their bit patterns are non-deterministic. Subnormals are not + /// modeled because their semantics depend on hardware and compiler denormal + /// modes which the analyzer cannot see. IBM double-double is also not modeled + /// because some of its operations are inaccurately emulated. + static bool isModeledFloatValue(const llvm::APFloat &V) { + return V.isFinite() && !V.isDenormal() && + llvm::APFloat::SemanticsToEnum(V.getSemantics()) != + llvm::APFloat::S_PPCDoubleDouble; + } + + /// Create a concrete floating-point value. The value must satisfy + /// \c isModeledFloatValue. nonloc::ConcreteFloat makeFloatVal(const FloatingLiteral *F) { return nonloc::ConcreteFloat(BasicVals.getFloatValue(F->getValue())); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index ab90615f63182..3741cc41c5a97 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -91,6 +91,11 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B, if (!B->getRHS()->getType()->isScalarType()) return; + // Floating-point divide by zero is defined when floating semantics conform + // to IEC 60559. + if (B->getRHS()->getType()->isRealFloatingType()) + return; + SVal Denom = C.getSVal(B->getRHS()); std::optional<DefinedSVal> DV = Denom.getAs<DefinedSVal>(); diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index 5fb203c665892..ae1dc83ff0aad 100644 --- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -130,6 +130,9 @@ APFloatPtr BasicValueFactory::getFloatValue(const llvm::APFloat &X) { using FoldNodeTy = llvm::FoldingSetNodeWrapper<llvm::APFloat>; + // ID must be unique to differentiate between nodes. Unlike integers, bit + // size and pattern are not sufficient, so add semantics to the ID as well. + ID.AddInteger(llvm::APFloat::SemanticsToEnum(X.getSemantics())); X.Profile(ID); FoldNodeTy *P = APFloatSet.FindNodeOrInsertPos(ID, InsertPos); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index ebe4a29617024..786454de33225 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -288,9 +288,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, if (const MemRegion *MR = State->getSVal(Ex, SF).getAsRegion()) { SVal OrigV = State->getSVal(MR); - // __builtin_bit_cast reinterprets raw bits. We cannot model this - // for floating-point values because evalCast performs a value - // conversion, not a bit reinterpretation. + // evalCast converts the value, but we are doing a bitcast here, which + // is unmodeled for floats. if (!OrigV.getAs<nonloc::ConcreteFloat>()) CastedV = svalBuilder.evalCast(svalBuilder.simplifySVal(State, OrigV), CastE->getType(), Ex->getType()); @@ -975,12 +974,21 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, if (std::optional<Loc> LV = V.getAs<Loc>()) { Loc X = svalBuilder.makeNullWithType(Ex->getType()); Result = evalBinOp(state, BO_EQ, *LV, X, U->getType()); + } else if (Ex->getType()->isRealFloatingType()) { + // Create a zero with matching semantics to the floating point. + DefinedOrUnknownSVal X = svalBuilder.makeZeroVal(Ex->getType()); + if (std::optional<NonLoc> ZeroNL = X.getAs<NonLoc>()) + Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), *ZeroNL, + U->getType()); + else + Result = UnknownVal(); } else if (Ex->getType()->isFloatingType()) { - // FIXME: handle floating point types. - Result = UnknownVal(); + // FIXME: handle complex floating point types. + Result = UnknownVal(); } else { - nonloc::ConcreteInt X(getBasicVals().getValue(0, Ex->getType())); - Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), X, U->getType()); + nonloc::ConcreteInt X(getBasicVals().getValue(0, Ex->getType())); + Result = + evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), X, U->getType()); } state = state->BindExpr(U, SF, Result); @@ -1039,6 +1047,12 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, RHS = svalBuilder.makeArrayIndex(1); else if (U->getType()->isIntegralOrEnumerationType()) RHS = svalBuilder.makeIntVal(1, U->getType()); + else if (U->getType()->isRealFloatingType()) + // C99 6.5.3.1: ++E is equivalent to (E += 1). Then the usual arithmetic + // conversions convert the 1 to E's type, so just build it as that type + // here. + RHS = svalBuilder.makeFloatVal(llvm::APFloat::getOne( + getContext().getFloatTypeSemantics(U->getType()))); else RHS = UnknownVal(); diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 55cbae3019fd7..1d44815626356 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -66,11 +66,18 @@ DefinedOrUnknownSVal SValBuilder::makeZeroVal(QualType type) { if (type->isIntegralOrEnumerationType()) return makeIntVal(0, type); + if (type->isRealFloatingType()) { + llvm::APFloat Zero = + llvm::APFloat::getZero(Context.getFloatTypeSemantics(type)); + if (!isModeledFloatValue(Zero)) + return UnknownVal(); + return makeFloatVal(Zero); + } + if (type->isArrayType() || type->isRecordType() || type->isVectorType() || type->isAnyComplexType()) return makeCompoundVal(type, BasicVals.getEmptySValList()); - // FIXME: Handle floats. return UnknownVal(); } @@ -220,8 +227,8 @@ DefinedSVal SValBuilder::getConjuredHeapSymbolVal(ConstCFGElementRef elem, assert(Loc::isLocType(type)); assert(SymbolManager::canSymbolicate(type)); if (type->isNullPtrType()) { - // makeZeroVal() returns UnknownVal only in case of FP number, which - // is not the case. + // The assert above establishes this is a Loc type, for which makeZeroVal() + // always returns a defined value. return makeZeroVal(type).castAs<DefinedSVal>(); } @@ -375,8 +382,12 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { case Stmt::IntegerLiteralClass: return makeIntVal(cast<IntegerLiteral>(E)); - case Stmt::FloatingLiteralClass: - return makeFloatVal(cast<FloatingLiteral>(E)); + case Stmt::FloatingLiteralClass: { + const auto *FL = cast<FloatingLiteral>(E); + if (!isModeledFloatValue(FL->getValue())) + return UnknownVal(); + return makeFloatVal(FL); + } case Stmt::ObjCBoolLiteralExprClass: return makeBoolVal(cast<ObjCBoolLiteralExpr>(E)); @@ -396,6 +407,8 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { break; case CK_ArrayToPointerDecay: case CK_IntegralToPointer: + case CK_IntegralToFloating: + case CK_FloatingCast: case CK_NoOp: case CK_BitCast: { const Expr *SE = CE->getSubExpr(); @@ -460,8 +473,8 @@ SVal SValBuilder::evalMinus(NonLoc X) { case nonloc::ConcreteIntKind: return makeIntVal(-X.castAs<nonloc::ConcreteInt>().getValue()); case nonloc::ConcreteFloatKind: { - // Negation only flips the sign bit and is well-defined for all - // floating-point values regardless of semantics, so model it. + // Negation is well-defined regardless of floating-point semantics (it's + // just a sign bit flip). llvm::APFloat Value = *X.castAs<nonloc::ConcreteFloat>().getValue(); Value.changeSign(); return makeFloatVal(Value); @@ -874,19 +887,28 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> { return UnknownVal(); } SVal VisitConcreteFloat(nonloc::ConcreteFloat V) { - // Float to float. + // Float to float. Modeled only when the conversion is exact, which needs no + // rounding and so does not depend on the rounding mode in effect. if (CastTy->isRealFloatingType()) { const llvm::fltSemantics &TargetSem = VB.getContext().getFloatTypeSemantics(CastTy); llvm::APFloat Value = *V.getValue(); bool LosesInfo = false; Value.convert(TargetSem, llvm::APFloat::rmNearestTiesToEven, &LosesInfo); - if (!LosesInfo) + if (!LosesInfo && SValBuilder::isModeledFloatValue(Value)) return VB.makeFloatVal(Value); return UnknownVal(); } - // Float to integer. + // Float to bool. Must precede integral case below since bool is also an + // integral but needs special handling. + if (CastTy->isBooleanType()) + return VB.makeTruthVal(!V.getValue()->isZero(), CastTy); + + // Float to integer. Since only finite floats are modeled, the only + // possible failure is if the float doesn't fit in the target, which opOK + // helps us catch. opInexact catches whether truncation toward zero + // happened, which is defined behavior, thus we model. if (CastTy->isIntegralOrEnumerationType()) { APSIntType ResultType = VB.getBasicValueFactory().getAPSIntType(CastTy); llvm::APSInt Result = ResultType.getValue(0); @@ -899,10 +921,6 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> { return UnknownVal(); } - // Float to bool. - if (CastTy->isBooleanType()) - return VB.makeTruthVal(!V.getValue()->isZero(), CastTy); - return UnknownVal(); } SVal VisitConcreteInt(nonloc::ConcreteInt V) { @@ -924,6 +942,20 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> { if (Loc::isLocType(CastTy)) return VB.makeIntLocVal(CastedValue()); + // Integer to float. Modeled only when the conversion is exact. + if (CastTy->isRealFloatingType()) { + const llvm::fltSemantics &TargetSem = + VB.getContext().getFloatTypeSemantics(CastTy); + llvm::APSInt Value = V.getValue(); + llvm::APFloat Result(TargetSem); + llvm::APFloat::opStatus Status = Result.convertFromAPInt( + Value, Value.isSigned(), llvm::APFloat::rmNearestTiesToEven); + if (Status == llvm::APFloat::opOK && + SValBuilder::isModeledFloatValue(Result)) + return VB.makeFloatVal(Result); + return UnknownVal(); + } + // Pointer to whatever else. return UnknownVal(); } diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp index b13019d74e883..afcf81db949d6 100644 --- a/clang/lib/StaticAnalyzer/Core/SVals.cpp +++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp @@ -48,6 +48,8 @@ static StringRef getFloatSemanticsName(const llvm::fltSemantics &Sem) { return "IEEEdouble"; case llvm::APFloat::S_IEEEquad: return "IEEEquad"; + case llvm::APFloat::S_PPCDoubleDouble: + return "PPCDoubleDouble"; case llvm::APFloat::S_x87DoubleExtended: return "x87DoubleExtended"; default: @@ -180,6 +182,8 @@ class TypeRetrievingVisitor return Context.DoubleTy; case llvm::APFloat::S_IEEEquad: return Context.Float128Ty; + case llvm::APFloat::S_PPCDoubleDouble: + return Context.Ibm128Ty; case llvm::APFloat::S_x87DoubleExtended: return Context.LongDoubleTy; default: @@ -282,8 +286,7 @@ nonloc::PointerToMember::iterator nonloc::PointerToMember::end() const { //===----------------------------------------------------------------------===// bool SVal::isConstant() const { - return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>() || - getAs<nonloc::ConcreteFloat>(); + return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>(); } bool SVal::isConstant(int I) const { @@ -295,8 +298,6 @@ bool SVal::isConstant(int I) const { } bool SVal::isZeroConstant() const { - if (std::optional<nonloc::ConcreteFloat> FV = getAs<nonloc::ConcreteFloat>()) - return FV->getValue()->isZero(); return isConstant(0); } diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 48aec507a85a9..17672a26b2b8b 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -81,9 +81,6 @@ class SimpleSValBuilder : public SValBuilder { /// (integer) value, that value is returned. Otherwise, returns NULL. const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal V) override; - const llvm::APFloat *getKnownFloatValue(ProgramStateRef state, - SVal V) override; - /// Evaluates a given SVal by recursively evaluating and simplifying the /// children SVals, then returns its minimal possible (integer) value. If the /// constraint manager cannot provide a meaningful answer, this returns NULL. @@ -439,9 +436,9 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, if (auto simplifiedRhsAsNonLoc = simplifiedRhs.getAs<NonLoc>()) rhs = *simplifiedRhsAsNonLoc; - // Handle trivial case where left-side and right-side are the same. - // Deliberately exclude floating-point values since x - x isn't necessarily 0 - // (e.g., inf - inf), and x == x is false when x is NaN. + // Handle trivial case where left-side and right-side are the same. Exclude + // floating points: the ConcreteFloat case below folds these correctly + // instead. if (lhs == rhs && !lhs.getAs<nonloc::ConcreteFloat>()) switch (op) { default: @@ -566,11 +563,12 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, break; } - // We can model arithmetic (operators +, -, *, /) only when both operands - // are finite and result is exact (which needs no rounding). Inexact, - // non-finite, or exceptions (like overflow or div by zero) is unmodeled. - if (!L.isFinite() || !R.isFinite()) - return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + // We can model arithmetic (operators +, -, *, /) only when the result is + // exact (which needs no rounding). The opOK guard ensures rounding or + // exceptional conditions (e.g., overflows and div by zero) are also not + // modeled. + assert(L.isFinite() && R.isFinite() && + "A concrete float is always finite"); llvm::APFloat Result = L; llvm::APFloat::opStatus Status; @@ -591,7 +589,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } - if (Status == llvm::APFloat::opOK) + if (Status == llvm::APFloat::opOK && isModeledFloatValue(Result)) return makeFloatVal(Result); return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); @@ -1296,13 +1294,6 @@ const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state, return getConstValue(state, simplifySVal(state, V)); } -const llvm::APFloat * -SimpleSValBuilder::getKnownFloatValue(ProgramStateRef state, SVal V) { - if (auto X = V.getAs<nonloc::ConcreteFloat>()) - return X->getValue().get(); - return nullptr; -} - const llvm::APSInt *SimpleSValBuilder::getMinValue(ProgramStateRef state, SVal V) { V = simplifySVal(state, V); diff --git a/clang/test/Analysis/constant-float-16bit.c b/clang/test/Analysis/constant-float-16bit.c new file mode 100644 index 0000000000000..999a036e1e7c9 --- /dev/null +++ b/clang/test/Analysis/constant-float-16bit.c @@ -0,0 +1,39 @@ +// _Float16 and __bf16 are both 16 bits wide but have different semantics, so +// the same bit pattern denotes different values in each. +// +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify %s +// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify %s + +void clang_analyzer_dump_half(_Float16); +void clang_analyzer_dump_bfloat(__bf16); +void clang_analyzer_dumpSvalType_half(_Float16); +void clang_analyzer_dumpSvalType_bfloat(__bf16); +void clang_analyzer_eval(int); + +void test16BitValues(void) { + clang_analyzer_dump_half((_Float16)1.5f); // expected-warning{{1.5 IEEEhalf}} + clang_analyzer_dump_bfloat((__bf16)1.5f); // expected-warning{{1.5 BFloat}} + clang_analyzer_dumpSvalType_half((_Float16)1.5f); // expected-warning{{_Float16}} + clang_analyzer_dumpSvalType_bfloat((__bf16)1.5f); // expected-warning{{__bf16}} +} + +// 0x3C00 is 1.0 as an IEEEhalf and 0.0078125 as a BFloat. Ensure conversions +// between them are respected. +void testSameBitsDifferentSemantics(void) { + clang_analyzer_dump_bfloat((__bf16)0.0078125f); // expected-warning{{0.007813 BFloat}} + _Float16 a = (_Float16)1.0f; + clang_analyzer_dump_half(a); // expected-warning{{1 IEEEhalf}} + clang_analyzer_dump_half(a + a); // expected-warning{{2 IEEEhalf}} + clang_analyzer_eval(a + a == (_Float16)2.0f); // expected-warning{{TRUE}} +} + +// 1 + 2^-9 needs 10 mantissa bits, which fits an IEEEhalf but not a BFloat, so +// result depends on rounding semantics => unmodeled. +void testExactnessIsPerSemantics(void) { + clang_analyzer_dump_half((_Float16)1.001953125f); // expected-warning{{1.002 IEEEhalf}} + clang_analyzer_dump_bfloat((__bf16)1.001953125f); // expected-warning{{Unknown}} +} diff --git a/clang/test/Analysis/constant-float-32bit-double.c b/clang/test/Analysis/constant-float-32bit-double.c new file mode 100644 index 0000000000000..607a55e065443 --- /dev/null +++ b/clang/test/Analysis/constant-float-32bit-double.c @@ -0,0 +1,25 @@ +// We can't assume a double will be wider than a float. +// +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -mdouble=32 \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify %s + +void clang_analyzer_dump_float(float); +void clang_analyzer_dump_double(double); +void clang_analyzer_dumpSvalType_double(double); +void clang_analyzer_eval(int); + +void testDoubleIsSingle(void) { + clang_analyzer_dump_double(3.14); // expected-warning{{3.1400001 IEEEsingle}} + clang_analyzer_dumpSvalType_double(3.14); // expected-warning{{float}} +} + +void testConversionsAreExact(void) { + clang_analyzer_dump_float((float)3.14); // expected-warning{{3.1400001 IEEEsingle}} + clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEsingle}} + clang_analyzer_eval(3.14 == 3.14f); // expected-warning{{TRUE}} +} + +void testInexactStillUnmodeled(void) { + clang_analyzer_dump_double(0.1 + 0.2); // expected-warning{{Unknown}} +} diff --git a/clang/test/Analysis/constant-float-cxx.cpp b/clang/test/Analysis/constant-float-cxx.cpp new file mode 100644 index 0000000000000..9b14ab559b263 --- /dev/null +++ b/clang/test/Analysis/constant-float-cxx.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify %s + +void clang_analyzer_dump_float(float); +void clang_analyzer_dump_double(double); +void clang_analyzer_eval(int); + +template <typename T> T twice(T x) { return x + x; } + +struct Vec { + float v; + Vec operator+(Vec o) const { return Vec{v + o.v}; } +}; + +constexpr double half(double x) { return x / 2.0; } + +double defaultArg(double i = 42) { return i; } +float narrowingDefaultArg(float i = 1.5) { return i; } + +void testTemplate() { + clang_analyzer_dump_float(twice(1.5f)); // expected-warning{{3 IEEEsingle}} + clang_analyzer_dump_double(twice(2.5)); // expected-warning{{5 IEEEdouble}} +} + +void testOverloadedOperator() { + Vec a{1.5f}, b{2.5f}; + clang_analyzer_dump_float((a + b).v); // expected-warning{{4 IEEEsingle}} +} + +void testConstexpr() { + clang_analyzer_dump_double(half(3.0)); // expected-warning{{1.5 IEEEdouble}} + constexpr double c = 1.25; + clang_analyzer_dump_double(c); // expected-warning{{1.25 IEEEdouble}} +} + +// A default argument is not evaluated through the CFG, rather it is folded by +// getConstantVal. +void testDefaultArgument() { + clang_analyzer_dump_double(defaultArg()); // expected-warning{{42 IEEEdouble}} + clang_analyzer_dump_float(narrowingDefaultArg()); // expected-warning{{1.5 IEEEsingle}} +} + +// A negative value does not fit an unsigned integer; the conversion should be +// unmodeled. +void testUnsignedFromNegative() { + clang_analyzer_eval((unsigned)-1.5f == 0); // expected-warning{{UNKNOWN}} +} diff --git a/clang/test/Analysis/constant-float-literals.c b/clang/test/Analysis/constant-float-literals.c deleted file mode 100644 index 09a57e55a42a6..0000000000000 --- a/clang/test/Analysis/constant-float-literals.c +++ /dev/null @@ -1,181 +0,0 @@ -// Semantics of long double differ depending on target, which is why we run on -// multiple targets. -// -// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \ -// RUN: -analyzer-checker=core,debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false -verify=common,x87 %s -// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \ -// RUN: -analyzer-checker=core,debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false -verify=common,quad %s -// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \ -// RUN: -analyzer-checker=core,debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false -verify=common,ldbl64 %s - -void clang_analyzer_dump_float(float); -void clang_analyzer_dump_double(double); -void clang_analyzer_dump_longdouble(long double); -void clang_analyzer_eval(int); - -//===----------------------------------------------------------------------===// -// Floating-point literals are modeled as ConcreteFloat SVals. -//===----------------------------------------------------------------------===// - -void testFloatLiterals(void) { - clang_analyzer_dump_float(0.0f); // common-warning{{0 IEEEsingle}} - clang_analyzer_dump_float(1.0f); // common-warning{{1 IEEEsingle}} - clang_analyzer_dump_float(3.14f); // common-warning{{3.1400001 IEEEsingle}} - clang_analyzer_dump_double(0.0); // common-warning{{0 IEEEdouble}} - clang_analyzer_dump_double(1.0); // common-warning{{1 IEEEdouble}} - clang_analyzer_dump_double(3.14); // common-warning{{3.1400000000000001 IEEEdouble}} -} - -//===----------------------------------------------------------------------===// -// long double is modeled with the target's floating-point semantics. -//===----------------------------------------------------------------------===// - -void testLongDoubleLiterals(void) { - // 0.0 and 1.0 are representable exactly in all formats so only semantic name - // differs on different targets. - clang_analyzer_dump_longdouble(0.0L); - // x87-warning@-1{{0 x87DoubleExtended}} - // quad-warning@-2{{0 IEEEquad}} - // ldbl64-warning@-3{{0 IEEEdouble}} - clang_analyzer_dump_longdouble(1.0L); - // x87-warning@-1{{1 x87DoubleExtended}} - // quad-warning@-2{{1 IEEEquad}} - // ldbl64-warning@-3{{1 IEEEdouble}} -} - -//===----------------------------------------------------------------------===// -// Variables assigned from literals retain the ConcreteFloat value. -//===----------------------------------------------------------------------===// - -void testVariables(void) { - float f = 1.5f; - double d = 2.5; - clang_analyzer_dump_float(f); // common-warning{{1.5 IEEEsingle}} - clang_analyzer_dump_double(d); // common-warning{{2.5 IEEEdouble}} -} - -//===----------------------------------------------------------------------===// -// Float-to-integer casts (truncation). -//===----------------------------------------------------------------------===// - -void testFloatToInt(void) { - float f = 1.9f; - double d = 2.7; - int i = (int)f; - int j = (int)d; - clang_analyzer_eval(i == 1); // common-warning{{TRUE}} - clang_analyzer_eval(j == 2); // common-warning{{TRUE}} -} - -//===----------------------------------------------------------------------===// -// Float-to-bool casts. -//===----------------------------------------------------------------------===// - -void testFloatToBool(void) { - float zero = 0.0f; - float nonzero = 1.0f; - clang_analyzer_eval((int)((_Bool)zero) == 0); // common-warning{{TRUE}} - clang_analyzer_eval((int)((_Bool)nonzero) == 1); // common-warning{{TRUE}} -} - -//===----------------------------------------------------------------------===// -// Float-to-float casts (precision change without loss). -//===----------------------------------------------------------------------===// - -void testFloatUpcast(void) { - float f = 1.5f; - double d = f; - // 1.5 is exactly representable in both, so no loss. - clang_analyzer_dump_double(d); // common-warning{{1.5 IEEEdouble}} -} - -//===----------------------------------------------------------------------===// -// Float-to-float casts (inexact narrowing stays Unknown). -//===----------------------------------------------------------------------===// - -void testFloatNarrowing(void) { - double d = 3.14; - float f = (float)d; - // 3.14 is not exactly representable in float, and rounding direction is - // implementation-defined, so we don't model here. - clang_analyzer_dump_float(f); // common-warning{{Unknown}} -} - -//===----------------------------------------------------------------------===// -// Unknown float values (parameters, arithmetic results). -//===----------------------------------------------------------------------===// - -void testUnknown(float f) { - clang_analyzer_dump_float(f); // common-warning{{Unknown}} - clang_analyzer_dump_float(f + 1.0f); // common-warning{{Unknown}} -} - -//===----------------------------------------------------------------------===// -// Arithmetic between concrete floats is folded only when the result is exact. -//===----------------------------------------------------------------------===// - -void testExactArithmetic(void) { - // All of these have exactly representable results, so they are independent - // of rounding mode and evaluation precision. - clang_analyzer_dump_float(1.0f + 2.0f); // common-warning{{3 IEEEsingle}} - clang_analyzer_dump_float(5.0f - 1.5f); // common-warning{{3.5 IEEEsingle}} - clang_analyzer_dump_float(1.5f * 2.0f); // common-warning{{3 IEEEsingle}} - clang_analyzer_dump_float(3.0f / 4.0f); // common-warning{{0.75 IEEEsingle}} - clang_analyzer_dump_double(0.5 + 0.25); // common-warning{{0.75 IEEEdouble}} -} - -void testInexactArithmetic(void) { - // 0.1f + 0.2f is not exactly representable in single precision; the rounded - // result depends on the rounding mode / evaluation precision, so we do not - // model it. - clang_analyzer_dump_float(0.1f + 0.2f); // common-warning{{Unknown}} - // 1.0f / 3.0f is inexact. - clang_analyzer_dump_float(1.0f / 3.0f); // common-warning{{Unknown}} -} - -void testComparisons(void) { - clang_analyzer_eval(1.0f < 2.0f); // common-warning{{TRUE}} - clang_analyzer_eval(2.0f < 1.0f); // common-warning{{FALSE}} - clang_analyzer_eval(1.5 == 1.5); // common-warning{{TRUE}} - clang_analyzer_eval(1.5 != 2.5); // common-warning{{TRUE}} - clang_analyzer_eval(2.0f >= 2.0f); // common-warning{{TRUE}} -} - -//===----------------------------------------------------------------------===// -// Unary negation is always exact (a sign-bit flip). -//===----------------------------------------------------------------------===// - -void testNegation(void) { - float f = 1.5f; - double d = 2.5; - clang_analyzer_dump_float(-f); // common-warning{{-1.5 IEEEsingle}} - clang_analyzer_dump_double(-d); // common-warning{{-2.5 IEEEdouble}} - clang_analyzer_dump_float(-(-f)); // common-warning{{1.5 IEEEsingle}} - clang_analyzer_eval(-f < 0.0f); // common-warning{{TRUE}} -} - -//===----------------------------------------------------------------------===// -// Infinity from an overflowing literal is concrete, but arithmetic on it is -// not folded and casting it to int is not modeled (both would depend on -// IEC 60559 semantics / are undefined in C), while comparisons still work. -//===----------------------------------------------------------------------===// - -void testInfinity(void) { - float big = 1e400f; // common-warning{{magnitude of floating-point constant too large}} - clang_analyzer_dump_float(big); // common-warning{{+Inf IEEEsingle}} - clang_analyzer_dump_float(big + 1.0f); // common-warning{{Unknown}} - clang_analyzer_eval(big > 1.0f); // common-warning{{TRUE}} - clang_analyzer_dump_float((float)(int)big); // common-warning{{Unknown}} -} - -//===----------------------------------------------------------------------===// -// Division by zero detection with concrete floats. -//===----------------------------------------------------------------------===// - -float testDivByZeroFloat(void) { - float x = 0.0f; - return 1.0f / x; // common-warning{{Division by zero}} -} diff --git a/clang/test/Analysis/constant-float-long-double.c b/clang/test/Analysis/constant-float-long-double.c new file mode 100644 index 0000000000000..87e56eb444196 --- /dev/null +++ b/clang/test/Analysis/constant-float-long-double.c @@ -0,0 +1,78 @@ +// Multiple targets needed because long double semantics differ on them. +// +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify=x87 %s +// RUN: %clang_analyze_cc1 -triple aarch64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify=quad %s +// RUN: %clang_analyze_cc1 -triple x86_64-pc-windows-msvc \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify=ldbl64 %s +// RUN: %clang_analyze_cc1 -triple powerpc64le-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false -verify=ibm128 %s + +void clang_analyzer_dump_longdouble(long double); +void clang_analyzer_dumpSvalType_longdouble(long double); +void clang_analyzer_eval(int); + +void testVariables(void) { + long double ld = 1.5L; + clang_analyzer_dump_longdouble(ld); + // x87-warning@-1{{1.5 x87DoubleExtended}} + // quad-warning@-2{{1.5 IEEEquad}} + // ldbl64-warning@-3{{1.5 IEEEdouble}} + // ibm128-warning@-4{{Unknown}} +} + +// long double has different semantics depending on target. IBM double-double +// should not be modeled. +void testLongDouble(void) { + clang_analyzer_dump_longdouble(1.0L); + // x87-warning@-1{{1 x87DoubleExtended}} + // quad-warning@-2{{1 IEEEquad}} + // ldbl64-warning@-3{{1 IEEEdouble}} + // ibm128-warning@-4{{Unknown}} + clang_analyzer_dumpSvalType_longdouble(1.0L); + // x87-warning@-1{{long double}} + // quad-warning@-2{{__float128}} + // ldbl64-warning@-3{{double}} + // ibm128-warning@-4{{NULL TYPE}} +} + +void testLongDoubleArithmetic(void) { + clang_analyzer_dump_longdouble(1.0L + 2.0L); + // x87-warning@-1{{3 x87DoubleExtended}} + // quad-warning@-2{{3 IEEEquad}} + // ldbl64-warning@-3{{3 IEEEdouble}} + // ibm128-warning@-4{{Unknown}} + clang_analyzer_dump_longdouble(3.0L / 4.0L); + // x87-warning@-1{{0.75 x87DoubleExtended}} + // quad-warning@-2{{0.75 IEEEquad}} + // ldbl64-warning@-3{{0.75 IEEEdouble}} + // ibm128-warning@-4{{Unknown}} + clang_analyzer_eval(1.0L < 2.0L); + // x87-warning@-1{{TRUE}} + // quad-warning@-2{{TRUE}} + // ldbl64-warning@-3{{TRUE}} + // ibm128-warning@-4{{UNKNOWN}} +} + +void testIntToFloat(void) { + clang_analyzer_dump_longdouble((long double)1); + // x87-warning@-1{{1 x87DoubleExtended}} + // quad-warning@-2{{1 IEEEquad}} + // ldbl64-warning@-3{{1 IEEEdouble}} + // ibm128-warning@-4{{Unknown}} +} + +// IBM double-double should not be created on the makeZeroVal build path. +void testZeroInitialized(void) { + static long double s; + clang_analyzer_dump_longdouble(s); + // x87-warning@-1{{0 x87DoubleExtended}} + // quad-warning@-2{{0 IEEEquad}} + // ldbl64-warning@-3{{0 IEEEdouble}} + // ibm128-warning@-4{{Unknown}} +} diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c new file mode 100644 index 0000000000000..6c91df894a21f --- /dev/null +++ b/clang/test/Analysis/constant-float.c @@ -0,0 +1,172 @@ +// Disable -Wliteral-range since we intentionally induce inf. +// +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \ +// RUN: -analyzer-config eagerly-assume=false -verify %s +// +// Only exact results are modeled so the analyzer's behavior should not change +// under a dynamic rounding mode. +// +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -frounding-math \ +// RUN: -analyzer-checker=core,debug.ExprInspection -Wno-literal-range \ +// RUN: -analyzer-config eagerly-assume=false -verify %s + +void clang_analyzer_dump_float(float); +void clang_analyzer_dump_double(double); +void clang_analyzer_eval(int); + +void testLiterals(void) { + clang_analyzer_dump_float(0.0f); // expected-warning{{0 IEEEsingle}} + clang_analyzer_dump_float(3.14f); // expected-warning{{3.1400001 IEEEsingle}} + clang_analyzer_dump_double(3.14); // expected-warning{{3.1400000000000001 IEEEdouble}} +} + +void testVariables(void) { + float f = 1.5f; + clang_analyzer_dump_float(f); // expected-warning{{1.5 IEEEsingle}} +} + +void testUnknown(float f) { + clang_analyzer_dump_float(f); // expected-warning{{Unknown}} + clang_analyzer_dump_float(f + 1.0f); // expected-warning{{Unknown}} +} + +// Exactly representable results are independent of rounding mode and +// evaluation precision. +void testArithmetic(void) { + clang_analyzer_dump_float(1.0f + 2.0f); // expected-warning{{3 IEEEsingle}} + clang_analyzer_dump_float(5.0f - 1.5f); // expected-warning{{3.5 IEEEsingle}} + clang_analyzer_dump_float(1.5f * 2.0f); // expected-warning{{3 IEEEsingle}} + clang_analyzer_dump_float(3.0f / 4.0f); // expected-warning{{0.75 IEEEsingle}} + clang_analyzer_dump_double(0.5 + 0.25); // expected-warning{{0.75 IEEEdouble}} + clang_analyzer_dump_float(0.1f + 0.2f); // expected-warning{{Unknown}} + clang_analyzer_dump_float(1.0f / 3.0f); // expected-warning{{Unknown}} +} + +// Comparisons are exact for every value, so all six predicates should fold. +void testComparisons(void) { + clang_analyzer_eval(1.0f < 2.0f); // expected-warning{{TRUE}} + clang_analyzer_eval(1.0f > 2.0f); // expected-warning{{FALSE}} + clang_analyzer_eval(2.0f <= 2.0f); // expected-warning{{TRUE}} + clang_analyzer_eval(2.0f >= 2.0f); // expected-warning{{TRUE}} + clang_analyzer_eval(1.5 == 1.5); // expected-warning{{TRUE}} + clang_analyzer_eval(1.5 != 2.5); // expected-warning{{TRUE}} +} + +// Unary negation can be modeled because of sign-bit. +void testNegation(void) { + clang_analyzer_dump_float(-1.5f); // expected-warning{{-1.5 IEEEsingle}} + clang_analyzer_dump_float(-(-1.5f)); // expected-warning{{1.5 IEEEsingle}} + clang_analyzer_dump_float(-0.0f); // expected-warning{{-0 IEEEsingle}} + clang_analyzer_eval(0.0f == -0.0f); // expected-warning{{TRUE}} +} + +// Conversions between floating points should be modeled only when they are +// exact. 3.14 stored in a double sets mantissa bits a float cannot hold, and +// rounding direction is implementation-specific. +void testFloatConversions(void) { + clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEdouble}} + clang_analyzer_dump_float((float)3.14); // expected-warning{{Unknown}} +} + +// Casts to bool is defined as a comparison to zero. +void testFloatToBool(void) { + clang_analyzer_eval((_Bool)0.5f); // expected-warning{{TRUE}} + clang_analyzer_eval((_Bool)2.0f); // expected-warning{{TRUE}} + clang_analyzer_eval((_Bool)-3.0f); // expected-warning{{TRUE}} + clang_analyzer_eval((_Bool)0.0f); // expected-warning{{FALSE}} + clang_analyzer_eval((_Bool)-0.0f); // expected-warning{{FALSE}} +} + +// !E is defined as (0 == E) with the zero converted to the type of expr. E. +void testLogicalNot(void) { + float nonzero = 0.5f, zero = 0.0f, negzero = -0.0f; + clang_analyzer_eval(!nonzero); // expected-warning{{FALSE}} + clang_analyzer_eval(!zero); // expected-warning{{TRUE}} + clang_analyzer_eval(!negzero); // expected-warning{{TRUE}} +} + +// Casts to integers discard fractional bits, which should be unmodeled when +// the result is out of range. +void testFloatToInt(void) { + clang_analyzer_eval((int)1.9f == 1); // expected-warning{{TRUE}} + clang_analyzer_eval((int)-1.9 == -1); // expected-warning{{TRUE}} + clang_analyzer_dump_float((float)(int)1e30f); // expected-warning{{Unknown}} +} + +// Infinities and NaNs should not be modeled. +void testNonFiniteIsUnmodeled(void) { + clang_analyzer_dump_float(1e400f); // expected-warning{{Unknown}} + clang_analyzer_dump_float((float)1e300); // expected-warning{{Unknown}} + clang_analyzer_dump_float(__FLT_MAX__ * 2.0f); // expected-warning{{Unknown}} + clang_analyzer_dump_float(__builtin_inff()); // expected-warning{{Unknown}} + clang_analyzer_dump_float(__builtin_huge_valf()); // expected-warning{{Unknown}} + clang_analyzer_dump_float(__builtin_nanf("")); // expected-warning{{Unknown}} + clang_analyzer_dump_double(0.0 / 0.0); // expected-warning{{Unknown}} + clang_analyzer_dump_double(1.0 / 0.0); // expected-warning{{Unknown}} +} + +void testSelfArithmetic(void) { + float f = 1.5f; + clang_analyzer_dump_float(f - f); // expected-warning{{0 IEEEsingle}} + clang_analyzer_eval(f == f); // expected-warning{{TRUE}} +} + +// Subnormals should not be modeled. +void testSubnormals(void) { + clang_analyzer_dump_float(__FLT_DENORM_MIN__); // expected-warning{{Unknown}} + clang_analyzer_dump_float(__FLT_MIN__ / 2.0f); // expected-warning{{Unknown}} + clang_analyzer_dump_float(__FLT_MIN__ * __FLT_MIN__); // expected-warning{{Unknown}} + clang_analyzer_dump_float((float)(double)__FLT_DENORM_MIN__); + // expected-warning@-1{{Unknown}} + clang_analyzer_dump_float(__FLT_MIN__); + // expected-warning@-1{{1.17549435E-38 IEEEsingle}} +} + +// Integer to float conversions should only be modeled when the conversion is +// exact. +void testIntToFloat(void) { + float f = 1; + clang_analyzer_dump_float(f); // expected-warning{{1 IEEEsingle}} + clang_analyzer_dump_float(1.0f + 1); // expected-warning{{2 IEEEsingle}} + clang_analyzer_dump_float((float)-3); // expected-warning{{-3 IEEEsingle}} + clang_analyzer_dump_float((float)16777216); // expected-warning{{16777216 IEEEsingle}} + clang_analyzer_dump_float((float)16777217); // expected-warning{{Unknown}} + + // 2^26 can be represented, 2^26 + 1 cannot. + long yes = 1L << 26; + long no = yes + 1L; + clang_analyzer_dump_float((float)yes); // expected-warning{{67108864}} + clang_analyzer_dump_float((float)no); // expected-warning{{Unknown}} +} + +// Complex floating-point types are not modeled. +void testComplexIsUnmodeled(void) { + _Complex float z = 1.5f; + clang_analyzer_dump_float(__real__ z); // expected-warning{{Unknown}} + clang_analyzer_eval(!z); // expected-warning{{UNKNOWN}} +} + +// Inc/decrement operators compute through the same path as += and -= +// respectively in the analyzer. +void testIncrementDecrement(void) { + float g = 2.0f; + g += 1.0f; + clang_analyzer_dump_float(g); // expected-warning{{3 IEEEsingle}} + --g; + clang_analyzer_dump_float(g); // expected-warning{{2 IEEEsingle}} + clang_analyzer_dump_float(g++); // expected-warning{{2 IEEEsingle}} + clang_analyzer_dump_float(g); // expected-warning{{3 IEEEsingle}} + + float small = 0.5f; + ++small; + clang_analyzer_dump_float(small); // expected-warning{{1.5 IEEEsingle}} + + float inexact = 0.1f; + ++inexact; + clang_analyzer_dump_float(inexact); // expected-warning{{Unknown}} + + float max = __FLT_MAX__; + ++max; + clang_analyzer_dump_float(max); // expected-warning{{Unknown}} +} diff --git a/clang/test/Analysis/inline.cpp b/clang/test/Analysis/inline.cpp index 2b31460330e44..f718e6d8ae4c5 100644 --- a/clang/test/Analysis/inline.cpp +++ b/clang/test/Analysis/inline.cpp @@ -285,11 +285,11 @@ namespace DefaultArgs { } void testFloatReference() { - clang_analyzer_eval(defaultFloatReference(1) == -1); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(defaultFloatReference() == -42); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(defaultFloatReference(1) == -1); // expected-warning{{TRUE}} + clang_analyzer_eval(defaultFloatReference() == -42); // expected-warning{{TRUE}} - clang_analyzer_eval(defaultFloatReferenceZero(1) == -1); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(defaultFloatReferenceZero() == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(defaultFloatReferenceZero(1) == -1); // expected-warning{{TRUE}} + clang_analyzer_eval(defaultFloatReferenceZero() == 0); // expected-warning{{TRUE}} } char defaultString(const char *s = "abc") { diff --git a/clang/test/Analysis/operator-calls.cpp b/clang/test/Analysis/operator-calls.cpp index dde21f164cee8..9825dc5ebf888 100644 --- a/clang/test/Analysis/operator-calls.cpp +++ b/clang/test/Analysis/operator-calls.cpp @@ -69,6 +69,7 @@ namespace RValues { } }; + // 1.0 is no longer unknown. This function forces an unknown float. float getUnknownFloat(); SmallOpaque getSmallOpaque() { >From e3ae42a530e88dbbb1e6a94b6720f832e8de1147 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Fri, 7 Aug 2026 21:54:53 +0200 Subject: [PATCH 7/8] Gate ConcreteFloat invariant in makeFloatVal, update test comments --- .../clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h | 3 +++ clang/test/Analysis/constant-float.c | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h index 70f7591ecf297..466926a3474e0 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h @@ -293,10 +293,13 @@ class SValBuilder { /// Create a concrete floating-point value. The value must satisfy /// \c isModeledFloatValue. nonloc::ConcreteFloat makeFloatVal(const FloatingLiteral *F) { + assert(isModeledFloatValue(F->getValue()) && + "ConcreteFloat must be normal and finite"); return nonloc::ConcreteFloat(BasicVals.getFloatValue(F->getValue())); } nonloc::ConcreteFloat makeFloatVal(const llvm::APFloat &F) { + assert(isModeledFloatValue(F) && "ConcreteFloat must be normal and finite"); return nonloc::ConcreteFloat(BasicVals.getFloatValue(F)); } diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c index 6c91df894a21f..3dd52d5838285 100644 --- a/clang/test/Analysis/constant-float.c +++ b/clang/test/Analysis/constant-float.c @@ -63,7 +63,7 @@ void testNegation(void) { // Conversions between floating points should be modeled only when they are // exact. 3.14 stored in a double sets mantissa bits a float cannot hold, and -// rounding direction is implementation-specific. +// rounding direction can change at runtime. void testFloatConversions(void) { clang_analyzer_dump_double((double)1.5f); // expected-warning{{1.5 IEEEdouble}} clang_analyzer_dump_float((float)3.14); // expected-warning{{Unknown}} @@ -136,7 +136,7 @@ void testIntToFloat(void) { // 2^26 can be represented, 2^26 + 1 cannot. long yes = 1L << 26; long no = yes + 1L; - clang_analyzer_dump_float((float)yes); // expected-warning{{67108864}} + clang_analyzer_dump_float((float)yes); // expected-warning{{67108864 IEEEsingle}} clang_analyzer_dump_float((float)no); // expected-warning{{Unknown}} } >From 3c169fff0fcd1c067635050806c4735ba62f9e91 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Sat, 8 Aug 2026 05:20:46 +0200 Subject: [PATCH 8/8] Fix type punning bug Ensure that type punning involving floats are not modeled. Found on a downstream 16-bit target, but reproduces on any target. --- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 14 ++++++++++++++ clang/test/Analysis/constant-float.c | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 1d44815626356..c93554187a2f0 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -887,6 +887,17 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> { return UnknownVal(); } SVal VisitConcreteFloat(nonloc::ConcreteFloat V) { + // A null original type occurs when trying to read a region as CastTy, as + // in the case of type punning. Only model when trying to read a float back + // as its original format (otherwise bits may be interpreted differentlty). + if (OriginalTy.isNull()) { + if (CastTy->isRealFloatingType() && + &VB.getContext().getFloatTypeSemantics(CastTy) == + &V.getValue()->getSemantics()) + return V; + return UnknownVal(); + } + // Float to float. Modeled only when the conversion is exact, which needs no // rounding and so does not depend on the rounding mode in effect. if (CastTy->isRealFloatingType()) { @@ -944,6 +955,9 @@ class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> { // Integer to float. Modeled only when the conversion is exact. if (CastTy->isRealFloatingType()) { + // Do not model type punning. + if (OriginalTy.isNull()) + return UnknownVal(); const llvm::fltSemantics &TargetSem = VB.getContext().getFloatTypeSemantics(CastTy); llvm::APSInt Value = V.getValue(); diff --git a/clang/test/Analysis/constant-float.c b/clang/test/Analysis/constant-float.c index 3dd52d5838285..678dcaa84e2e4 100644 --- a/clang/test/Analysis/constant-float.c +++ b/clang/test/Analysis/constant-float.c @@ -13,6 +13,7 @@ void clang_analyzer_dump_float(float); void clang_analyzer_dump_double(double); +void clang_analyzer_dump_int(int); void clang_analyzer_eval(int); void testLiterals(void) { @@ -69,6 +70,15 @@ void testFloatConversions(void) { clang_analyzer_dump_float((float)3.14); // expected-warning{{Unknown}} } +// Type punning reinterprets the bits, whereas we only model conversions of the +// value, so decline it in both directions. +void testTypePunning(void) { + float f = 1.5f; + clang_analyzer_dump_int(*(int *)&f); // expected-warning{{Unknown}} + int i = 5; + clang_analyzer_dump_float(*(float *)&i); // expected-warning{{Unknown}} +} + // Casts to bool is defined as a comparison to zero. void testFloatToBool(void) { clang_analyzer_eval((_Bool)0.5f); // expected-warning{{TRUE}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
