Subject: bug#60237: 30.0.50; tree sitter core dumps when I edebug view a node

From: Eli Zaretskii <[email-masked]>
Date: Sat, 25 Feb 2023 09:51:32 +0200

> From: Yuan Fu <[email-masked]>
> Date: Fri, 24 Feb 2023 15:22:00 -0800
> Cc: [email-masked],
>  [email-masked]
> 
> I’m stumbled on a reliably way to trigger a crash, of possibly the same cause as
> this one, by enabling the profiler and M-x garbage-collect in a
> tree-sitter mode on Mac. I tried to reproduce this on Linux but with no
> success.
> 
> I was also able to trigger infinite loop by the same recipe on time, but I
> didn’t run that session under lldb. Anyway, we can focus on the crash
> first.
> 
> Below’s the backtrace. Eli, could you see anything from this?
> [...]
> (lldb) down
> frame #0: 0x0000000100250f3d emacs`ASIZE(array=0x00000001a1889245) at lisp.h:1768:3
>    1765	ASIZE (Lisp_Object array)
>    1766	{
>    1767	  ptrdiff_t size = XVECTOR (array)->header.size;
> -> 1768	  eassume (0 <= size);
>    1769	  return size;
>    1770	}
>    1771
> (lldb) p size
> (ptrdiff_t) $0 = -9223372036854775792

It looks like we are calling ASIZE in the context of GC, when the
vectors have their mark bit set, which makes ASIZE return negative
values: do

  (gdb) p/x (unsigned long long)-9223372036854775792
  $1 = 0x8000000000000010

So this is 16 (10 hex) with the array mark flag bit set.  The fix is
simple:

diff --git a/src/eval.c b/src/eval.c
index 2dd0c35..7e6b742 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -4190,7 +4190,7 @@ mark_specpdl (union specbinding *first, union specbinding *ptr)
 get_backtrace (Lisp_Object array)
 {
   union specbinding *pdl = backtrace_next (backtrace_top ());
-  ptrdiff_t i = 0, asize = ASIZE (array);
+  ptrdiff_t i = 0, asize = gc_asize (array);
 
   /* Copy the backtrace contents into working memory.  */
   for (; i < asize; i++)

> I suspect there is some stupid mistake that I made concerning gcing
> tree-sitter objects. Could you see anything suspicious from the
> following description:
> 
> A Lisp_TS_Parser contains a TSParser and a TSTree, which are freed when
> the Lisp_TS_Parser is collected. A Lisp_TS_Node references the parser
> from which it is created, so that a Lisp_TS_Parser is only collected
> when no live node references it, because the Lisp_TS_Node references the
> TSTree stored in the Lisp_TS_Parser.

Sounds good, but do you understand why tree-sitter calls malloc when
you GC a parser?  This is what we see in the backtrace:

>   * frame #0: 0x0000000100250f3d emacs`ASIZE(array=0x00000001a1889245) at lisp.h:1768:3
>     frame #1: 0x0000000100250e5e emacs`get_backtrace(array=0x00000001a1889245) at eval.c:4193:28
>     frame #2: 0x00000001003001ce emacs`record_backtrace(log=0x00000001a1887d68, count=64) at profiler.c:162:3
>     frame #3: 0x000000010030016d emacs`malloc_probe(size=64) at profiler.c:509:3
>     frame #4: 0x0000000100204e6d emacs`xmalloc(size=64) at alloc.c:760:3
>     frame #5: 0x0000000100e6c0c9 libtree-sitter.0.dylib`ts_subtree_release + 158
>     frame #6: 0x0000000100e6f004 libtree-sitter.0.dylib`ts_tree_delete + 44
>     frame #7: 0x0000000100307379 emacs`treesit_delete_parser(lisp_parser=0x00000001a2c0f0e0) at treesit.c:1182:3

As you see, when we call ts_tree_delete, it calls ts_subtree_release,
which in turn calls malloc (redirected into our xmalloc).  Is this
expected?  Can you look in the tree-sitter sources and verify that
this is OK?