Skip to content

Commit b1d27e5

Browse files
committed
Merge branch 'wip/z3_update'
2 parents 7222e0f + a82d622 commit b1d27e5

9 files changed

+163
-36
lines changed

claripy/ast/fp.py

+5
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ def _fp_cmp_check(a, b):
132132
fpGEQ = operations.op('fpGEQ', (FP, FP), Bool, bound=False, extra_check=_fp_cmp_check)
133133
fpLT = operations.op('fpLT', (FP, FP), Bool, bound=False, extra_check=_fp_cmp_check)
134134
fpLEQ = operations.op('fpLEQ', (FP, FP), Bool, bound=False, extra_check=_fp_cmp_check)
135+
fpIsNaN = operations.op('fpIsNaN', (FP,), Bool, bound=False)
136+
fpIsInf = operations.op('fpIsInf', (FP,), Bool, bound=False)
135137

136138
#
137139
# unbound floating point arithmetic
@@ -173,3 +175,6 @@ def _fp_binop_length(rm, a, b): #pylint:disable=unused-argument
173175
FP.__rmul__ = operations.reversed_op(FP.__mul__)
174176
FP.__rdiv__ = operations.reversed_op(FP.__div__)
175177
FP.__rtruediv__ = operations.reversed_op(FP.__div__)
178+
179+
FP.isNaN = fpIsNaN
180+
FP.isInf = fpIsInf

claripy/backend_object.py

-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,3 @@ def to_claripy(self):
1515
"""
1616

1717
return self
18-

claripy/backends/backend_concrete.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def BVV(value, size):
3939
return bv.BVV(value, size)
4040

4141
@staticmethod
42-
def StringV(value, size):
42+
def StringV(value, size): # pylint: disable=unused-argument
4343
if not value:
4444
raise BackendError("can't handle empty Strings")
4545
return strings.StringV(value)
@@ -85,8 +85,7 @@ def convert(self, expr):
8585
def _If(self, b, t, f): #pylint:disable=no-self-use,unused-argument
8686
if not isinstance(b, bool):
8787
raise BackendError("BackendConcrete can't handle non-bool condition in If.")
88-
else:
89-
return t if b else f
88+
return t if b else f
9089

9190
def _size(self, e):
9291
if isinstance(e, (bool, numbers.Number)):
@@ -125,7 +124,7 @@ def _abstract(self, e): #pylint:disable=no-self-use
125124
elif isinstance(e, fp.FPV):
126125
return FPV(e.value, e.sort)
127126
elif isinstance(e, strings.StringV):
128-
return StringV(e.value)
127+
return StringV(e.value)
129128
else:
130129
raise BackendError("Couldn't abstract object of type {}".format(type(e)))
131130

@@ -141,12 +140,14 @@ def _cardinality(self, b):
141140
def _to_primitive(expr):
142141
if isinstance(expr, bv.BVV):
143142
return expr.value
144-
if isinstance(expr, fp.FPV):
143+
elif isinstance(expr, fp.FPV):
145144
return expr.value
146-
if isinstance(expr, bool):
145+
elif isinstance(expr, bool):
147146
return expr
148-
if isinstance(expr, numbers.Number):
147+
elif isinstance(expr, numbers.Number):
149148
return expr
149+
else:
150+
raise BackendError("idk how to turn this into a primitive")
150151

151152
def _eval(self, expr, n, extra_constraints=(), solver=None, model_callback=None):
152153
if not all(extra_constraints):

claripy/backends/backend_z3.py

+98-17
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
except ImportError:
2525
_is_pypy = False
2626

27+
def z3_expr_to_smt2(f, status="unknown", name="benchmark", logic=""):
28+
# from https://stackoverflow.com/a/14629021/9719920
29+
v = (z3.Ast * 0)()
30+
return z3.Z3_benchmark_to_smtlib_string(f.ctx_ref(), name, logic, status, "", 0, v, f.as_ast())
31+
2732
def _add_memory_pressure(p):
2833
"""
2934
PyPy's garbage collector is not aware of memory uses happening inside native code. When performing memory-intensive
@@ -46,6 +51,9 @@ def _add_memory_pressure(p):
4651

4752
supports_fp = hasattr(z3, 'fpEQ')
4853

54+
# you can toggle this flag if you want. I don't think it matters
55+
#z3.set_param('rewriter.hi_fp_unspecified', 'true')
56+
4957
#
5058
# Utility functions
5159
#
@@ -69,18 +77,8 @@ def raw_caller(*args, **kwargs):
6977
return raw_caller
7078

7179
def _z3_decl_name_str(ctx, decl):
72-
# reimplementation of Z3_get_symbol_string to not try to unicode-decode
73-
lib = z3.lib()
74-
75-
decl_name = lib.Z3_get_decl_name(ctx, decl)
76-
err = lib.Z3_get_error_code(ctx)
77-
if err != z3.Z3_OK:
78-
raise z3.Z3Exception(lib.Z3_get_error_msg(ctx, err))
79-
80-
symbol_name = lib.Z3_get_symbol_string(ctx, decl_name)
81-
err = lib.Z3_get_error_code(ctx)
82-
if err != z3.Z3_OK:
83-
raise z3.Z3Exception(z3.lib().Z3_get_error_msg(ctx, err))
80+
decl_name = z3.Z3_get_decl_name(ctx, decl)
81+
symbol_name = z3.Z3_get_symbol_string_bytes(ctx, decl_name)
8482
return symbol_name
8583

8684

@@ -562,20 +560,54 @@ def _abstract_to_primitive(self, ctx, ast):
562560
op_name = op_map[z3_op_nums[decl_num]]
563561

564562
if op_name == 'BitVecVal':
565-
if z3.Z3_get_numeral_uint64(ctx, ast, self._c_uint64_p):
566-
return self._c_uint64_p.contents.value
567-
else:
568-
bv_num = int(z3.Z3_get_numeral_string(ctx, ast))
569-
return bv_num
563+
return self._abstract_bv_val(ctx, ast)
570564
elif op_name == 'True':
571565
return True
572566
elif op_name == 'False':
573567
return False
574568
elif op_name in ('FPVal', 'MinusZero', 'MinusInf', 'PlusZero', 'PlusInf', 'NaN'):
575569
return self._abstract_fp_val(ctx, ast, op_name)
570+
elif op_name == 'Concat':
571+
# Quirk in how z3 might handle NaN encodings - it will not give us a fully evaluated model
572+
# https://github.com/Z3Prover/z3/issues/518
573+
# this case will be triggered if the z3 rewriter.hi_fp_unspecified is set to true
574+
nargs = z3.Z3_get_app_num_args(ctx, ast)
575+
res = 0
576+
for i in range(nargs):
577+
arg_ast = z3.Z3_get_app_arg(ctx, ast, i)
578+
arg_decl = z3.Z3_get_app_decl(ctx, arg_ast)
579+
arg_decl_num = z3.Z3_get_decl_kind(ctx, arg_decl)
580+
arg_size = z3.Z3_get_bv_sort_size(ctx, z3.Z3_get_sort(ctx, arg_ast))
581+
582+
neg = False
583+
if arg_decl_num == z3.Z3_OP_BNEG:
584+
arg_ast = z3.Z3_get_app_arg(ctx, arg_ast, 0)
585+
arg_decl = z3.Z3_get_app_decl(ctx, arg_ast)
586+
arg_decl_num = z3.Z3_get_decl_kind(ctx, arg_decl)
587+
neg = True
588+
if arg_decl_num != z3.Z3_OP_BNUM:
589+
raise BackendError("Weird z3 model")
590+
591+
arg_int = self._abstract_bv_val(ctx, arg_ast)
592+
if neg:
593+
arg_int = (1<<arg_size)-arg_int
594+
res <<= arg_size
595+
res |= arg_int
596+
return res
597+
elif op_name == 'fpToIEEEBV':
598+
# Another quirk in the way z3 might handle nan encodings. see above
599+
# this case will be triggered if the z3 rewriter.hi_fp_unspecified is set to false
600+
arg_ast = z3.Z3_get_app_arg(ctx, ast, 0)
601+
return self._abstract_fp_encoded_val(ctx, arg_ast)
576602
else:
577603
raise BackendError("Unable to abstract Z3 object to primitive")
578604

605+
def _abstract_bv_val(self, ctx, ast):
606+
if z3.Z3_get_numeral_uint64(ctx, ast, self._c_uint64_p):
607+
return self._c_uint64_p.contents.value
608+
else:
609+
return int(z3.Z3_get_numeral_string(ctx, ast))
610+
579611
def _abstract_fp_val(self, ctx, ast, op_name):
580612
if op_name == 'FPVal':
581613
# TODO: do better than this
@@ -599,6 +631,47 @@ def _abstract_fp_val(self, ctx, ast, op_name):
599631
else:
600632
raise BackendError("Called _abstract_fp_val with unknown type")
601633

634+
def _abstract_fp_encoded_val(self, ctx, ast):
635+
decl = z3.Z3_get_app_decl(ctx, ast)
636+
decl_num = z3.Z3_get_decl_kind(ctx, decl)
637+
op_name = op_map[z3_op_nums[decl_num]]
638+
sort = z3.Z3_get_sort(ctx, ast)
639+
ebits = z3.Z3_fpa_get_ebits(ctx, sort)
640+
sbits = z3.Z3_fpa_get_sbits(ctx, sort) - 1 # includes sign bit
641+
642+
if op_name == 'FPVal':
643+
# TODO: do better than this
644+
fp_mantissa = int(z3.Z3_fpa_get_numeral_significand_string(ctx, ast))
645+
fp_exp = int(z3.Z3_fpa_get_numeral_exponent_string(ctx, ast, True))
646+
fp_sign_c = ctypes.c_int()
647+
z3.Z3_fpa_get_numeral_sign(ctx, ast, ctypes.byref(fp_sign_c))
648+
fp_sign = 1 if fp_sign_c.value != 0 else 0
649+
elif op_name == 'MinusZero':
650+
fp_sign = 1
651+
fp_exp = 0
652+
fp_mantissa = 0
653+
elif op_name == 'MinusInf':
654+
fp_sign = 1
655+
fp_exp = (1<<ebits) - 1
656+
fp_mantissa = 0
657+
elif op_name == 'PlusZero':
658+
fp_sign = 0
659+
fp_exp = 0
660+
fp_mantissa = 0
661+
elif op_name == 'PlusInf':
662+
fp_sign = 0
663+
fp_exp = (1<<ebits) - 1
664+
fp_mantissa = 0
665+
elif op_name == 'NaN':
666+
fp_sign = 0
667+
fp_exp = (1<<ebits) - 1
668+
fp_mantissa = 1
669+
else:
670+
raise BackendError("Called _abstract_fp_val with unknown type")
671+
672+
value = (fp_sign << (ebits + sbits)) | (fp_exp << sbits) | fp_mantissa
673+
return value
674+
602675
def solver(self, timeout=None):
603676
if not self.reuse_z3_solver or getattr(self._tls, 'solver', None) is None:
604677
s = z3.Solver(ctx=self._context)
@@ -1047,6 +1120,14 @@ def _op_raw_fpGEQ(self, a, b):
10471120
def _op_raw_fpEQ(self, a, b):
10481121
return z3.BoolRef(z3.Z3_mk_fpa_eq(self._context.ref(), a.as_ast(), b.as_ast()), self._context)
10491122

1123+
@condom
1124+
def _op_raw_fpIsNaN(self, a):
1125+
return z3.BoolRef(z3.Z3_mk_fpa_is_nan(self._context.ref(), a.as_ast()), self._context)
1126+
1127+
@condom
1128+
def _op_raw_fpIsInf(self, a):
1129+
return z3.BoolRef(z3.Z3_mk_fpa_is_inf(self._context.ref(), a.as_ast()), self._context)
1130+
10501131
@condom
10511132
def _op_raw_fpFP(self, sgn, exp, sig):
10521133
return z3.FPRef(z3.Z3_mk_fpa_fp(self._context.ref(), sgn.ast, exp.ast, sig.ast), self._context)

claripy/fp.py

+12
Original file line numberDiff line numberDiff line change
@@ -443,4 +443,16 @@ def fpDiv(_rm, a, b):
443443
"""
444444
return a / b
445445

446+
def fpIsNaN(x):
447+
"""
448+
Checks whether the argument is a floating point NaN.
449+
"""
450+
return math.isnan(x)
451+
452+
def fpIsInf(x):
453+
"""
454+
Checks whether the argument is a floating point infinity.
455+
"""
456+
return math.isinf(x)
457+
446458
from .bv import BVV, Concat

claripy/operations.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,10 @@ def str_extract_check(start_idx, count, str_val):
162162
else:
163163
return True, ""
164164

165-
def str_extract_length_calc(start_idx, count, str_val):
165+
def str_extract_length_calc(start_idx, count, str_val): # pylint: diable=unused-argument
166166
return count
167167

168-
def int_to_str_length_calc(int_val):
168+
def int_to_str_length_calc(int_val): # pylint: disable=unused-argument
169169
return ast.String.MAX_LENGTH
170170

171171
def str_replace_check(*args):
@@ -174,7 +174,7 @@ def str_replace_check(*args):
174174
return False, "The pattern that has to be replaced is longer than the string itself"
175175
return True, ""
176176

177-
def substr_length_calc(start_idx, count, strval):
177+
def substr_length_calc(start_idx, count, strval): # pylint: disable=unused-argument
178178
# FIXME: How can I get the value of a concrete object without a solver
179179
return strval.string_length if not count.concrete else count.args[0]
180180

@@ -200,10 +200,10 @@ def str_replace_length_calc(*args):
200200
def strlen_bv_size_calc(s, bitlength):
201201
return bitlength
202202

203-
def strindexof_bv_size_calc(s1, s2, start_idx, bitlength):
203+
def strindexof_bv_size_calc(s1, s2, start_idx, bitlength): # pylint: disable=unused-argument
204204
return bitlength
205205

206-
def strtoint_bv_size_calc(s, bitlength):
206+
def strtoint_bv_size_calc(s, bitlength): # pylint: disable=unused-argument
207207
return bitlength
208208

209209
#
@@ -312,7 +312,7 @@ def strtoint_bv_size_calc(s, bitlength):
312312

313313
backend_fp_operations = {
314314
'FPS', 'fpToFP', 'fpToIEEEBV', 'fpFP', 'fpToSBV', 'fpToUBV',
315-
'fpNeg', 'fpSub', 'fpAdd', 'fpMul', 'fpDiv', 'fpAbs'
315+
'fpNeg', 'fpSub', 'fpAdd', 'fpMul', 'fpDiv', 'fpAbs', 'fpIsNaN', 'fpIsInf',
316316
} | backend_fp_cmp_operations
317317

318318
backend_strings_operations = {

claripy/simplifications.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -542,9 +542,18 @@ def bitwise_xor_simplifier(a, b, *args):
542542
def _flattening_filter(args):
543543
# since a ^ a == 0, we can safely remove those from args
544544
# this procedure is done carefully in order to keep the ordering of arguments
545-
ctr = collections.Counter(args)
546-
unique_args = set(k.cache_key for k in ctr if ctr[k] % 2 != 0)
547-
return tuple([ arg for arg in args if arg.cache_key in unique_args ])
545+
ctr = collections.Counter(arg.cache_key for arg in args)
546+
res = []
547+
seen = set()
548+
for arg in args:
549+
if ctr[arg.cache_key] % 2 == 0:
550+
continue
551+
l1 = len(seen)
552+
seen.add(arg.cache_key)
553+
l2 = len(seen)
554+
if l1 != l2:
555+
res.append(arg)
556+
return tuple(res)
548557

549558
return SimplificationManager._flatten_simplifier('__xor__', _flattening_filter, a, b, *args, initial_value=ast.all_operations.BVV(0, a.size()))
550559

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
python_requires='>=3.5',
1717
packages=packages,
1818
install_requires=[
19-
'z3-solver==4.5.1.0.post2',
19+
'z3-solver>=4.8.5.0',
2020
'future',
2121
'cachetools',
2222
'pysmt',

tests/test_solver.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import claripy
22
import nose
3+
import math
34

45
import logging
56
l = logging.getLogger('claripy.test.solver')
@@ -559,9 +560,27 @@ def test_zero_division_in_cache_mixin():
559560
s.add(denum == 3)
560561
assert not s.satisfiable()
561562

563+
def test_nan():
564+
a = claripy.FPS('a', claripy.FSORT_FLOAT)
565+
b = claripy.BVS('b', 32)
562566

563-
if __name__ == '__main__':
567+
s1 = claripy.Solver()
568+
s1.add((a + 1).isNaN())
569+
res = s1.eval(a, 1)[0]
570+
assert math.isnan(res)
571+
572+
s2 = claripy.Solver()
573+
s2.add(b.raw_to_fp().isNaN())
574+
res = s2.eval(b, 1)[0]
575+
assert res & 0xff800000 == 0x7f800000 and res & 0x007fffff != 0
576+
577+
s3 = claripy.Solver()
578+
s3.add(a.isNaN())
579+
res = s3.eval(a.raw_to_bv(), 1)[0]
580+
assert res & 0xff800000 == 0x7f800000 and res & 0x007fffff != 0
564581

582+
583+
if __name__ == '__main__':
565584
for fparams in test_unsat_core():
566585
fparams[0](*fparams[1:])
567586

@@ -584,3 +603,4 @@ def test_zero_division_in_cache_mixin():
584603
fparams[0](*fparams[1:])
585604
test_composite_solver()
586605
test_zero_division_in_cache_mixin()
606+
test_nan()

0 commit comments

Comments
 (0)