Repository: angr/claripy

Issue:
FP.isNaN() and FP.isInf() broken for FPV
### Description

Thanks for the project. :)

For `FP.isNaN()` and `FP.isInf()` on FPVs, the concrete backend is used. The code ends up at Python's `math.isnan(x)` and `math.isinf(x)`methods, respectively, where `x` is the FP. This call causes a `TypeError` exception.



### Steps to reproduce the bug

```
from claripy import FPS, FPV, FSORT_FLOAT

s = FPS('s', FSORT_FLOAT)
s.isNaN() # okay
s.isInf() # okay

v = FPV(0, FSORT_FLOAT)
v.isNaN() # TypeError
v.isNaN() # TypeError
```

The FPV first needs to be "solved", but maybe there's another workaround?

```
import math
from claripy import FPV, FSORT_FLOAT, Solver

nan = FPV(math.nan, FSORT_FLOAT)
nan.isNaN() # TypeError

inf = FPV(math.inf, FSORT_FLOAT)
inf.isInf() # TypeError

slv = Solver()
math.isnan(slv.eval(nan, 1)[0]) # True
math.isinf(slv.eval(inf, 1)[0]) # True
```

### Environment

```
claripy==9.2.128
```

### Additional context

_No response_

Discussion:
@antifob: Wow, that was quick! Thanks! :tada:  lgtm@twizmwazin: No problem, thanks for the report!

Solution patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 56d88e3e..75b6194c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -94,6 +94,6 @@ repos:
     -   id: check-docstring-first
 
 -   repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.7.3
+    rev: v0.7.4
     hooks:
     - id: ruff
diff --git a/claripy/__init__.py b/claripy/__init__.py
index bbd7c6b5..8dc9e5d3 100644
--- a/claripy/__init__.py
+++ b/claripy/__init__.py
@@ -107,7 +107,7 @@ from claripy.solvers import (
     SolverVSA,
 )
 
-__version__ = "9.2.129.dev0"
+__version__ = "9.2.130.dev0"
 
 __all__ = (
     "algorithm",
diff --git a/claripy/backends/backend_concrete/backend_concrete.py b/claripy/backends/backend_concrete/backend_concrete.py
index b7bc3251..8a1ac4ee 100644
--- a/claripy/backends/backend_concrete/backend_concrete.py
+++ b/claripy/backends/backend_concrete/backend_concrete.py
@@ -7,7 +7,7 @@ import operator
 from functools import reduce
 
 import claripy
-from claripy.ast import BV, Base, Bool
+from claripy.ast import BV, Bool
 from claripy.backends.backend import Backend
 from claripy.backends.backend_concrete import bv, fp, strings
 from claripy.errors import BackendError, UnsatError
@@ -195,21 +195,17 @@ class BackendConcrete(Backend):
 
     # Override Backend.is_true() for a better performance
     def is_true(self, e, extra_constraints=(), solver=None, model_callback=None):
-        if e in {True, 1, 1.0}:
-            return True
-        if e in {False, 0, 0.0}:
-            return False
-        if type(e) is Base and e.op == "BoolV" and len(e.args) == 1 and e.args[0] is True:
+        if isinstance(e, numbers.Number):
+            return bool(e)
+        if e is claripy.true():
             return True
         return super().is_true(e, extra_constraints=extra_constraints, solver=solver, model_callback=model_callback)
 
     # Override Backend.is_false() for a better performance
     def is_false(self, e, extra_constraints=(), solver=None, model_callback=None):
-        if e in {False, 0, 0.0}:
-            return True
-        if e in {True, 1, 1.0}:
-            return False
-        if type(e) is Base and e.op == "BoolV" and len(e.args) == 1 and e.args[0] is False:
+        if isinstance(e, numbers.Number):
+            return not bool(e)
+        if e is claripy.false():
             return True
         return super().is_false(e, extra_constraints=extra_constraints, solver=solver, model_callback=model_callback)
 
diff --git a/claripy/backends/backend_concrete/fp.py b/claripy/backends/backend_concrete/fp.py
index 7a4fc960..2829bcd1 100644
--- a/claripy/backends/backend_concrete/fp.py
+++ b/claripy/backends/backend_concrete/fp.py
@@ -403,11 +403,11 @@ def fpIsNaN(x):
     """
     Checks whether the argument is a floating point NaN.
     """
-    return math.isnan(x)
+    return math.isnan(x.value)
 
 
 def fpIsInf(x):
     """
     Checks whether the argument is a floating point infinity.
     """
-    return math.isinf(x)
+    return math.isinf(x.value)


Test patch:
diff --git a/tests/test_fp.py b/tests/test_fp.py
index a3bfeddd..d6afbdd6 100644
--- a/tests/test_fp.py
+++ b/tests/test_fp.py
@@ -67,6 +67,12 @@ class TestFp(unittest.TestCase):
         assert edd != edd2
         assert edd2 == 0x4237B4C7C0000000
 
+    def test_concrete_isnan(self):
+        assert claripy.FPV(0, claripy.FSORT_FLOAT).isNaN() is claripy.false()
+
+    def test_concrete_isinf(self):
+        assert claripy.FPV(0, claripy.FSORT_FLOAT).isInf() is claripy.false()
+
 
 if __name__ == "__main__":
     unittest.main()