wolfgangj / bone-lisp

The Bone Lisp programming language
Other
321 stars 21 forks source link

Segfault in some specific mixes of in-reg, _protect, and eval #20

Closed dubek closed 8 years ago

dubek commented 8 years ago

Example:

Bone Lisp 0.5.0-pre
@0: (in-reg (do (_protect (lambda () (eval '(if)) #t)) (_protect (lambda () (eval '(if)) #t)) (_protect (lambda () (eval '(if)) #t))))
#f
@1: (in-reg (do (_protect (lambda () (eval '(if)) #t)) (_protect (lambda () (eval '(if)) #t)) (_protect (lambda () (eval '(if)) #t))))
#f
@2: (in-reg (do (_protect (lambda () (eval '(if)) #t)) (_protect (lambda () (eval '(if)) #t)) (_protect (lambda () (eval '(if)) #t))))
Segmentation fault

I couldn't reduce this code further to reliably crash. Here's the backtrace:

(gdb) bt
#0  0x00000000004059cd in backtrace () at bone.c:1489
#1  0x0000000000401988 in type_error (x=2, t=t_cons) at bone.c:104
#2  0x0000000000401a0b in check (x=2, t=t_cons) at bone.c:113
#3  0x000000000040238f in car (x=2) at bone.c:304
#4  0x0000000000406a68 in compile_if (e=2, env=2, tail_context=true, state=0x7fffffffd1c0) at bone.c:1778
#5  0x0000000000407a16 in compile_expr (e=140737353285056, env=2, tail_context=true, state=0x7fffffffd1c0) at bone.c:1994
#6  0x0000000000407d57 in compile2list (expr=140737353285056, env=2, extra_offset=0, extra_locals=0x7fffffffd224) at bone.c:2047
#7  0x0000000000407dc4 in compile2sub_code (expr=140737353285056, env=2, argc=0, take_rest=0, env_size=0) at bone.c:2055
#8  0x0000000000407e88 in compile_toplevel_expr (e=140737353285056) at bone.c:2064
#9  0x0000000000407eaa in eval_toplevel_expr (e=140737353285056) at bone.c:2069
#10 0x00000000004097d2 in CSUB_eval (args=0x614648) at bone.c:2302
#11 0x0000000000406044 in call (subr=0x7ffff7fedad8, args_pos=5, locals_cnt=1) at bone.c:1577
#12 0x000000000040619c in call (subr=0x7ffff7f31018, args_pos=5, locals_cnt=0) at bone.c:1594
#13 0x0000000000406735 in apply (s=140737353289757, xs=2) at bone.c:1708
#14 0x0000000000406754 in call0 (subr=140737353289757) at bone.c:1711
#15 0x000000000040a3d7 in CSUB_protect (args=0x614640) at bone.c:2466
#16 0x0000000000406044 in call (subr=0x7ffff7fee0a0, args_pos=4, locals_cnt=1) at bone.c:1577
#17 0x000000000040619c in call (subr=0x7ffff7f301c8, args_pos=4, locals_cnt=0) at bone.c:1594
#18 0x0000000000406735 in apply (s=140737353286093, xs=2) at bone.c:1708
#19 0x0000000000406754 in call0 (subr=140737353286093) at bone.c:1711
#20 0x000000000040939d in CSUB_in_reg (args=0x614638) at bone.c:2280
#21 0x0000000000406044 in call (subr=0x7ffff7fed7c0, args_pos=3, locals_cnt=1) at bone.c:1577
#22 0x0000000000406735 in apply (s=140737488345245, xs=2) at bone.c:1708
#23 0x0000000000406754 in call0 (subr=140737488345245) at bone.c:1711
#24 0x0000000000407ec2 in eval_toplevel_expr (e=140737353284128) at bone.c:2070
#25 0x000000000040b7b0 in bone_repl () at bone.c:2747
#26 0x000000000040b8b2 in main (argc=1, argv=0x7fffffffd9d8) at main.c:31
wolfgangj commented 8 years ago

A sub (which is created by the evaluator) is being overwritten by temporary data of the compiler - which at that point compiles a toplevel expression (i.e. we're not inside of an eval). This is pretty weird.

wolfgangj commented 8 years ago

Found and fixed the problem. Will push it and describe in more detail later when I get home.

wolfgangj commented 8 years ago

Okay, so what caused this? I found this kind of funny in a weird way:

throw()/catch will not change anything about call_stack_pos. This is not a problem usually because bone_repl() resets it to 0 when an error occured. But with _protect, it can become a problem if we invoke the compiler (via eval) and compilation fails. Because if it fails as it tried to access e.g. the car of something which is not a cons cell (as it happens when compiling (if)), this error will call backtrace().

Now backtrace() did not check for silence_errors because all the eprint/eprintf calls it does will do that anyway and backtrace() is not performance critical, so it wouldn't matter if it does some unnecessary calls. However, backtrace now iterates over the call stack, which still is in an invalid state because _protect did not reset call_stack_pos. The calls which used to be on the call stack are still there and the sub objects may have been overwritten - in this case, by some temporary data of the compiler.

Congratulations for finding such an obscure bug. ;-)