IagoAbal/haskell-z3

Crash on parseSMTLib2String

rcdickerson opened this issue · 8 comments

I'm seeing the following using haskell-z3 version 408.0 and z3 version 4.8.4 on a 64-bit Pop!_OS (Ubuntu-based) install, OS version 19.04.

Parsing a simple SMTLib2 string and displaying the AST crashes with what looks like a memory management error:

$ ghci
GHCi, version 8.4.4: http://www.haskell.org/ghc/  :? for help
Prelude> import Z3.Monad
Prelude Z3.Monad> ast <- evalZ3 $ parseSMTLib2String "(assert true)" [] [] [] []
Prelude Z3.Monad> s <- evalZ3 $ astToString ast
double free or corruption (out)
Aborted (core dumped)

I inspected this in gdb and saw the following backtrace:

#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007ffff67f9535 in __GI_abort () at abort.c:79
#2  0x00007ffff6860726 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ffff6986952 "%s\n") at ../sysdeps/posix/libc_fatal.c:181
#3  0x00007ffff686759a in malloc_printerr (str=str@entry=0x7ffff6984ab3 "free(): invalid size") at malloc.c:5352
#4  0x00007ffff68693dc in _int_free (av=<optimized out>, p=0x7fffffff94a0, have_lock=<optimized out>) at malloc.c:4185
#5  0x00007ffff6b909c7 in shared_occs::operator()(expr*, shared_occs_mark&) [clone .cold.69] () from /lib/libz3.so
#6  0x00007ffff78493ac in shared_occs::operator()(expr*) () from /lib/libz3.so
#7  0x00007ffff7878eff in smt2_printer::process(expr*, obj_ref<app, ast_manager>&) () from /lib/libz3.so
#8  0x00007ffff787a825 in smt2_printer::operator()(expr*, unsigned int, char const*, obj_ref<app, ast_manager>&, sbuffer<symbol, 16u>&) () from /lib/libz3.so
#9  0x00007ffff7873ced in ast_smt2_pp(std::ostream&, expr*, smt2_pp_environment&, params_ref const&, unsigned int, unsigned int, char const*) () from /lib/libz3.so
#10 0x00007ffff7873ed7 in operator<<(std::ostream&, mk_ismt2_pp const&) () from /lib/libz3.so
#11 0x00007ffff6c19093 in Z3_ast_to_string () from /lib/libz3.so
#12 0x00000000005c1263 in ?? ()
#13 0x0000000000000000 in ?? ()

I was unable to reproduce this on an up-to-date MacOS install with z3 version 4.8.0 (so an earlier point release.)

Because of the "double free" in the error message, I originally thought this might be a finalizer race or similar, with a certain GC ordering triggering some bad sequence of free()s. Seeing "invalid size" in the backtrace and the MacOS non-repro makes me wonder if it could be something more platform-specific, though?

I'm stuck on what to investigate next to understand why this is happening. Any thoughts?

Hi @rcdickerson, the bindings are rather low-level, and the "higher-level" Monad API is just offered for convenience but it doesn't really add any safety. In particular, if you look at the code, evalZ3 creates a new Z3 Context, and any Z3 object created inside evalZ3 is only valid for that particular Context. What you are doing here is illegal:

ast <- evalZ3 $ parseSMTLib2String "(assert true)" [] [] [] []
s <- evalZ3 $ astToString ast
...

I don't know exactly where this crashes and why it doesn't crash on some platforms or with earlier versions. That is a detail of the C++ implementation. At the high-level, the issue is that the ast belongs to the Context created by the first evalZ3, the second evalZ3 expects it to belong to its own Context. (Edited: I previously said that the first Context may have been finalized, but that shouldn't happen, it should wait for the ast to be GCed.)

You should do instead:

evalZ3 $ do
    ast <- parseSMTLib2String "(assert true)" [] [] [] []
    s    <- astToString ast
    ...

If you want that kind of flexibility then you should use Z3.Base and keep around the Context object yourself. (Note that Z3.Monad is essentially a Reader monad that stores the Context.) You don't need to call any finalizers manually though, that should be handled for you by the GC.

Thanks for the response! I didn't realize there was one Context per evalZ3 -- I will restructure my code accordingly. :)

So actually the same thing happens to me even with a single top-level evalZ3:

$ ghci
GHCi, version 8.4.4: http://www.haskell.org/ghc/  :? for help
Prelude> import Z3.Monad
Prelude Z3.Monad> evalZ3 $ astToString =<< parseSMTLib2String "(assert true)" [] [] [] []
double free or corruption (out)
Aborted (core dumped)

I see the same thing directly using Z3.Base as well:

$ ghci
GHCi, version 8.4.4: http://www.haskell.org/ghc/  :? for help
Prelude> import Z3.Base
Prelude Z3.Base> cfg <- mkConfig
Prelude Z3.Base> ctx <- mkContext cfg
Prelude Z3.Base> ast <- parseSMTLib2String ctx "(assert (= 1 1))" [] [] [] []
Prelude Z3.Base> astToString ctx ast
double free or corruption (out)
Aborted (core dumped)

As another data point, I tried this with the Python bindings as well to see if my Z3 install was the problem. Seems to work:

$ python
Python 2.7.16 (default, Apr  6 2019, 01:42:57) 
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from z3 import *
>>> str(parse_smt2_string("(assert (= 1 1))"))
'[1 == 1]'

Sorry for taking a year or so to reply. :-) It seems to be because the Z3 folks changed the return type of the function. It used to return a Z3_ast but it now returns a Z3_ast_vector. If that is all then it should be easy to fix. Patches are welcome!

I am currently trying to fix this ✌️

Fixed by @maurobringolf in 140e3e7 (PR #44), thanks a lot!