rrthomas/enchant

MacOS: Crash after upgrade

Closed this issue · 4 comments

Please let me know if need other details...

Upgraded from enchant-2 version 2.6.9 -> 2.7.1 using homebrew, and crashes now when spell checking in emacs.

Computer is: Apple M1 Pro, running Sonoma 14.4.1 (23E224).

$ clang --version
Homebrew clang version 18.1.4
Target: arm64-apple-darwin23.4.0
Thread model: posix
InstalledDir: /opt/homebrew/opt/llvm/bin

Here's relevant lldb debug info...

Process 81951 stopped
* thread #2, name = 'org.gnu.Emacs.lisp-main', stop reason = EXC_BREAKPOINT (code=1, subcode=0x193e0d410)
    frame #0: 0x0000000193e0d410 CoreFoundation`__CFStringCreateImmutableFunnel3.cold.1 + 16
CoreFoundation`__CFStringCreateImmutableFunnel3.cold.1:
->  0x193e0d410 <+16>: brk    #0x1

CoreFoundation`__CFStringMakeConstantString.cold.1:
    0x193e0d414 <+0>:  pacibsp
    0x193e0d418 <+4>:  stp    x29, x30, [sp, #-0x10]!
    0x193e0d41c <+8>:  mov    x29, sp
(lldb) bt
* thread #2, name = 'org.gnu.Emacs.lisp-main', stop reason = EXC_BREAKPOINT (code=1, subcode=0x193e0d410)
  * frame #0: 0x0000000193e0d410 CoreFoundation`__CFStringCreateImmutableFunnel3.cold.1 + 16
    frame #1: 0x0000000193c26fcc CoreFoundation`__CFStringCreateImmutableFunnel3 + 96
    frame #2: 0x000000012e94f860 enchant_applespell.so`appleSpell_dict_check(_EnchantDict*, char const*, unsigned long) [inlined] AppleSpellChecker::checkWord(this=0x0000600003c34060, word="Automagically", len=18446744073709551615, lang="en") at applespell_checker.mm:171:19 [opt]
    frame #3: 0x000000012e94f820 enchant_applespell.so`appleSpell_dict_check(me=<unavailable>, word="Automagically", len=18446744073709551615) at applespell_checker.mm:293:31 [opt]
    frame #4: 0x000000012e93589c libenchant-2.2.dylib`enchant_dict_check(_self_=0x0000600001fa5b00, word_buf=<unavailable>, len=<unavailable>) at dict.vala:154:12 [opt]
    frame #5: 0x000000012e90b3f4 jinx-mod.dylib`jinx_check + 132
    frame #6: 0x00000001001776e4 Emacs`funcall_module + 300
    frame #7: 0x0000000100147b84 Emacs`Ffuncall + 316
    frame #8: 0x0000000148c03e90 jinx-a92c61a5-6c94e07d.eln`F6a696e782d2d776f72642d76616c69642d70_jinx__word_valid_p_0 + 448
    frame #9: 0x000000010014b524 Emacs`__funcall_subr_block_invoke + 272
    frame #10: 0x00000001001fad3c Emacs`mac_autorelease_loop + 48
    frame #11: 0x000000010014ae54 Emacs`funcall_subr + 96
    frame #12: 0x0000000100147b84 Emacs`Ffuncall + 316
    frame #13: 0x000000010014a730 Emacs`run_hook_with_args + 336
    frame #14: 0x0000000148c046fc jinx-a92c61a5-6c94e07d.eln`F6a696e782d2d636865636b2d726567696f6e_jinx__check_region_0 + 1036
    frame #15: 0x000000010014b548 Emacs`__funcall_subr_block_invoke + 308
    frame #16: 0x00000001001fad3c Emacs`mac_autorelease_loop + 48
    frame #17: 0x000000010014ae54 Emacs`funcall_subr + 96
    frame #18: 0x0000000100147b84 Emacs`Ffuncall + 316
    frame #19: 0x0000000148c04248 jinx-a92c61a5-6c94e07d.eln`F6a696e782d2d636865636b2d70656e64696e67_jinx__check_pending_0 + 376
    frame #20: 0x000000010014b47c Emacs`__funcall_subr_block_invoke + 104
    frame #21: 0x00000001001fad3c Emacs`mac_autorelease_loop + 48
    frame #22: 0x000000010014ae54 Emacs`funcall_subr + 96
    frame #23: 0x0000000100147b84 Emacs`Ffuncall + 316
    frame #24: 0x0000000148c05964 jinx-a92c61a5-6c94e07d.eln`F6a696e782d2d74696d65722d68616e646c6572_jinx__timer_handler_0 + 320
    frame #25: 0x000000010014b514 Emacs`__funcall_subr_block_invoke + 256
    frame #26: 0x00000001001fad3c Emacs`mac_autorelease_loop + 48
    frame #27: 0x000000010014ae54 Emacs`funcall_subr + 96
    frame #28: 0x0000000100147b84 Emacs`Ffuncall + 316
    frame #29: 0x000000010014b47c Emacs`__funcall_subr_block_invoke + 104
    frame #30: 0x00000001001fad3c Emacs`mac_autorelease_loop + 48
    frame #31: 0x000000010014ae54 Emacs`funcall_subr + 96
    frame #32: 0x0000000100147b84 Emacs`Ffuncall + 316
    frame #33: 0x0000000104430e44 timer-3ee7cfd9-226b3dc9.eln`F74696d65722d6576656e742d68616e646c6572_timer_event_handler_0 + 832
    frame #34: 0x000000010014b524 Emacs`__funcall_subr_block_invoke + 272
    frame #35: 0x00000001001fad3c Emacs`mac_autorelease_loop + 48
    frame #36: 0x000000010014ae54 Emacs`funcall_subr + 96
    frame #37: 0x0000000100147b84 Emacs`Ffuncall + 316
    frame #38: 0x00000001000d54fc Emacs`timer_check + 904
    frame #39: 0x00000001000d3c28 Emacs`readable_events + 36
    frame #40: 0x00000001000d511c Emacs`get_input_pending + 56
    frame #41: 0x00000001000d300c Emacs`detect_input_pending_run_timers + 48
    frame #42: 0x000000010019c498 Emacs`wait_reading_process_output + 3760
    frame #43: 0x0000000100013228 Emacs`sit_for + 380
    frame #44: 0x00000001000d0ae8 Emacs`read_char + 4796
    frame #45: 0x00000001000cdda4 Emacs`read_key_sequence + 1032
    frame #46: 0x00000001000dd538 Emacs`__command_loop_1_block_invoke + 492
    frame #47: 0x00000001001fad3c Emacs`mac_autorelease_loop + 48
    frame #48: 0x00000001000cd5f8 Emacs`command_loop_1 + 336
    frame #49: 0x00000001001487d4 Emacs`internal_condition_case + 96
    frame #50: 0x00000001000cd494 Emacs`command_loop_2 + 52
    frame #51: 0x00000001001481ec Emacs`internal_catch + 88
    frame #52: 0x0000000100232cc4 Emacs`command_loop.cold.1 + 88
    frame #53: 0x00000001000ccd58 Emacs`command_loop + 152
    frame #54: 0x00000001000ccc10 Emacs`recursive_edit_1 + 152
    frame #55: 0x00000001000ccf54 Emacs`Frecursive_edit + 264
    frame #56: 0x00000001000cbebc Emacs`emacs_main + 6792
    frame #57: 0x000000010021c39c Emacs`mac_start_lisp_main + 56
    frame #58: 0x0000000193bc2f94 libsystem_pthread.dylib`_pthread_start + 136
(lldb) f 0
frame #0: 0x0000000193e0d410 CoreFoundation`__CFStringCreateImmutableFunnel3.cold.1 + 16
CoreFoundation`__CFStringCreateImmutableFunnel3.cold.1:
->  0x193e0d410 <+16>: brk    #0x1

CoreFoundation`__CFStringMakeConstantString.cold.1:
    0x193e0d414 <+0>:  pacibsp
    0x193e0d418 <+4>:  stp    x29, x30, [sp, #-0x10]!
    0x193e0d41c <+8>:  mov    x29, sp
(lldb) image lookup -va $pc
      Address: CoreFoundation[0x0000000180691410] (CoreFoundation.__TEXT.__text + 1995616)
      Summary: CoreFoundation`__CFStringCreateImmutableFunnel3.cold.1 + 16
       Module: file = "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", arch = "arm64e"
       Symbol: id = {0x00001758}, range = [0x0000000193e0d400-0x0000000193e0d414), name="__CFStringCreateImmutableFunnel3.cold.1"

(lldb) frame variable
(lldb) f 1
frame #1: 0x0000000193c26fcc CoreFoundation`__CFStringCreateImmutableFunnel3 + 96
CoreFoundation`__CFStringCreateImmutableFunnel3:
->  0x193c26fcc <+96>:  mov    x25, x6
    0x193c26fd0 <+100>: mov    x26, x5
    0x193c26fd4 <+104>: mov    x24, x4
    0x193c26fd8 <+108>: mov    x22, x3
(lldb) image lookup -va $pc
      Address: CoreFoundation[0x00000001804aafcc] (CoreFoundation.__TEXT.__text + 3868)
      Summary: CoreFoundation`__CFStringCreateImmutableFunnel3 + 96
       Module: file = "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", arch = "arm64e"
       Symbol: id = {0x0000000a}, range = [0x0000000193c26f6c-0x0000000193c27aac), name="__CFStringCreateImmutableFunnel3"

(lldb) f 3
warning: enchant_applespell.so was compiled with optimization - stepping may behave oddly; variables may not be available.
frame #3: 0x000000012e94f820 enchant_applespell.so`appleSpell_dict_check(me=<unavailable>, word="Automagically", len=18446744073709551615) at applespell_checker.mm:293:31 [opt]
   290
   291 			if (AppleSpellDictionary * ASD = static_cast<AppleSpellDictionary *>(me->user_data))
   292 				{
-> 293 					result = ASD->AppleSpell->checkWord (word, len, ASD->DictionaryName);
   294 				}
   295 			return result;
   296 		}
(lldb) frame variable
(EnchantDict *) me = <Could not evaluate DW_OP_entry_value.>

(const char *const) word = 0x00006000038c4070 "Automagically"
(size_t) len = 18446744073709551615
(int) result = 0
(AppleSpellDictionary *) ASD = <variable not available>

(lldb) image lookup -va $pc
      Address: enchant_applespell.so[0x0000000000003860] (enchant_applespell.so.__TEXT.__text + 3816)
      Summary: enchant_applespell.so`appleSpell_dict_check(_EnchantDict*, char const*, unsigned long) + 128 [inlined] AppleSpellChecker::checkWord(char const*, unsigned long, NSString*) + 64 at applespell_checker.mm:172:6
               enchant_applespell.so`appleSpell_dict_check(_EnchantDict*, char const*, unsigned long) + 64 at applespell_checker.mm:293:31
       Module: file = "/opt/homebrew/Cellar/enchant/2.7.1/lib/enchant-2/enchant_applespell.so", arch = "arm64"
  CompileUnit: id = {0x00000000}, file = "/Users/jeffrey/Library/Caches/Homebrew/Sources/enchant/enchant-2.7.1/providers/applespell_checker.mm", language = "objective-c++"
     Function: id = {0x4000000000005089}, name = "appleSpell_dict_check(_EnchantDict*, char const*, unsigned long)", mangled = "_ZL21appleSpell_dict_checkP12_EnchantDictPKcm", range = [0x000000012e94f7e0-0x000000012e94f8d0)
     FuncType: id = {0x4000000000005089}, byte-size = 0, decl = applespell_checker.mm:279, compiler_type = "int (EnchantDict *, const char *const, size_t)"
       Blocks: id = {0x4000000000005089}, range = [0x12e94f7e0-0x12e94f8d0)
               id = {0x40000000000050d7}, range = [0x12e94f808-0x12e94f8b8)
               id = {0x40000000000050f4}, ranges = [0x12e94f814-0x12e94f818)[0x12e94f81c-0x12e94f8ac)
               id = {0x4000000000005109}, range = [0x12e94f820-0x12e94f8ac), name = "checkWord", decl = applespell_checker.mm:164, mangled = _ZN17AppleSpellChecker9checkWordEPKcmP8NSString, demangled = AppleSpellChecker::checkWord(char const*, unsigned long, NSString*)
    LineEntry: [0x000000012e94f860-0x000000012e94f864): /Users/jeffrey/Library/Caches/Homebrew/Sources/enchant/enchant-2.7.1/providers/applespell_checker.mm:172:6
       Symbol: id = {0x00000071}, range = [0x000000012e94f7e0-0x000000012e94f8d0), name="appleSpell_dict_check(_EnchantDict*, char const*, unsigned long)", mangled="_ZL21appleSpell_dict_checkP12_EnchantDictPKcm"
     Variable: id = {0x400000000000511e}, name = "this", type = "AppleSpellChecker *", valid ranges = <block>, location = [0x0000000000003838, 0x00000000000038ac) -> DW_OP_reg23 W23, decl =
     Variable: id = {0x4000000000005127}, name = "word", type = "const char *", valid ranges = <block>, location = [0x0000000000003838, 0x0000000000003898) -> DW_OP_reg20 W20, decl = applespell_checker.mm:164
     Variable: id = {0x4000000000005130}, name = "len", type = "size_t", valid ranges = <block>, location = [0x0000000000003838, 0x0000000000003868) -> DW_OP_reg19 W19, decl = applespell_checker.mm:164
     Variable: id = {0x4000000000005139}, name = "lang", type = "NSString *", valid ranges = <block>, location = [0x0000000000003838, 0x00000000000038a8) -> DW_OP_reg21 W21, decl = applespell_checker.mm:164
     Variable: id = {0x4000000000005142}, name = "str", type = "NSString *", valid ranges = <block>, location = [0x0000000000003860, 0x0000000000003868) -> DW_OP_reg0 W0, decl = applespell_checker.mm:171
     Variable: id = {0x40000000000050e4}, name = "result", type = "int", valid ranges = <block>, location = [0x0000000000003814, 0x00000000000038b4) -> DW_OP_consts +0, DW_OP_stack_value, decl = applespell_checker.mm:289
     Variable: id = {0x40000000000050a7}, name = "me", type = "EnchantDict *", valid ranges = <block>, location = [0x000000000000383c, 0x00000000000038b4) -> DW_OP_entry_value(DW_OP_reg0 W0), DW_OP_stack_value, decl = applespell_checker.mm:279
     Variable: id = {0x40000000000050b7}, name = "word", type = "const char *const", valid ranges = <block>, location = [0x00000000000037fc, 0x0000000000003898) -> DW_OP_reg20 W20, decl = applespell_checker.mm:279
     Variable: id = {0x40000000000050c7}, name = "len", type = "size_t", valid ranges = <block>, location = [0x00000000000037f8, 0x0000000000003868) -> DW_OP_reg19 W19, decl = applespell_checker.mm:279

(lldb) f 4
warning: libenchant-2.2.dylib was compiled with optimization - stepping may behave oddly; variables may not be available.
frame #4: 0x000000012e93589c libenchant-2.2.dylib`enchant_dict_check(_self_=0x0000600001fa5b00, word_buf=<unavailable>, len=<unavailable>) at dict.vala:154:12 [opt]
   151 				return 0;
   152
   153 			if (self.check_method != null)
-> 154 				return self.check_method(self, word, len);
   155 			else if (self.session.is_pwl)
   156 				return 1;
   157
(lldb) frame variable
(EnchantDict *) _self_ = 0x0000600001fa5b00
(const gchar *) word_buf = <Could not evaluate DW_OP_entry_value.>

(ssize_t) len = <Could not evaluate DW_OP_entry_value.>

(gchar *) word = 0x00006000038c4070 "Automagically"
(gboolean) _tmp0_ = <variable not available>

(const gchar *) _tmp2_ = 0x00006000038c4070 "Automagically"
(gchar *) _tmp1_ = 0x00006000038c4070 "Automagically"
(EnchantSession *) _tmp3_ = <variable not available>

(const gchar *) _tmp5_ = 0x00006000038c4070 "Automagically"
(EnchantSession *) _tmp4_ = <variable not available>

(gint) result = <variable not available>

(const gchar *) _tmp7_ = 0x00006000038c4070 "Automagically"
(EnchantSession *) _tmp6_ = <variable not available>

(DictCheck) _tmp8_ = <register x8 is not available>

(DictCheck) _tmp9_ = <no location, value may have been optimized out>

(const gchar *) _tmp10_ = <no location, value may have been optimized out>

(lldb) image lookup -va $pc
      Address: libenchant-2.2.dylib[0x000000000000589c] (libenchant-2.2.dylib.__TEXT.__text + 8696)
      Summary: libenchant-2.2.dylib`enchant_dict_check + 164 at dict.vala:154:12
       Module: file = "/opt/homebrew/Cellar/enchant/2.7.1/lib/libenchant-2.2.dylib", arch = "arm64"
  CompileUnit: id = {0x00000000}, file = "/Users/jeffrey/Library/Caches/Homebrew/Sources/enchant/enchant-2.7.1/lib/dict.c", language = "c11"
     Function: id = {0x40000200000015a3}, name = "enchant_dict_check", range = [0x000000012e9357f8-0x000000012e9358dc)
     FuncType: id = {0x40000200000015a3}, byte-size = 0, decl = dict.c:594, compiler_type = "int (EnchantDict *, const gchar *, ssize_t)"
       Blocks: id = {0x40000200000015a3}, range = [0x12e9357f8-0x12e9358dc)
               id = {0x400002000000169d}, range = [0x12e93588c-0x12e9358a8)
    LineEntry: [0x000000012e93588c-0x000000012e9358a0): /Users/jeffrey/Library/Caches/Homebrew/Sources/enchant/enchant-2.7.1/lib/dict.vala:154:12
       Symbol: id = {0x000000ee}, range = [0x000000012e9357f8-0x000000012e9358dc), name="enchant_dict_check"
     Variable: id = {0x40000200000016aa}, name = "_tmp9_", type = "DictCheck", valid ranges = <block>, location = <empty>, decl = dict.c:680
     Variable: id = {0x40000200000016b6}, name = "_tmp10_", type = "const gchar *", valid ranges = <block>, location = <empty>, decl = dict.c:681
     Variable: id = {0x40000200000015bd}, name = "_self_", type = "EnchantDict *", valid ranges = <block>, location = [0x000000000000580c, 0x00000000000058a0) -> DW_OP_reg20 W20, decl = dict.c:594
     Variable: id = {0x40000200000015cd}, name = "word_buf", type = "const gchar *", valid ranges = <block>, location = [0x0000000000005824, 0x00000000000058cc) -> DW_OP_entry_value(DW_OP_reg1 W1), DW_OP_stack_value, decl = dict.c:595
     Variable: id = {0x40000200000015dd}, name = "len", type = "ssize_t", valid ranges = <block>, location = [0x0000000000005828, 0x00000000000058cc) -> DW_OP_entry_value(DW_OP_reg2 W2), DW_OP_stack_value, decl = dict.c:596
     Variable: id = {0x40000200000015ed}, name = "word", type = "gchar *", valid ranges = <block>, location = [0x0000000000005884, 0x00000000000058a8) -> DW_OP_reg19 W19, decl = dict.c:599
     Variable: id = {0x400002000000160d}, name = "_tmp2_", type = "const gchar *", valid ranges = <block>, location = [0x0000000000005864, 0x00000000000058cc) -> DW_OP_reg19 W19, decl = dict.c:601
     Variable: id = {0x400002000000161d}, name = "_tmp1_", type = "gchar *", valid ranges = <block>, location = [0x0000000000005864, 0x00000000000058cc) -> DW_OP_reg19 W19, decl = dict.c:600
     Variable: id = {0x400002000000163d}, name = "_tmp5_", type = "const gchar *", valid ranges = <block>, location = [0x0000000000005864, 0x00000000000058cc) -> DW_OP_reg19 W19, decl = dict.c:604
     Variable: id = {0x400002000000166d}, name = "_tmp7_", type = "const gchar *", valid ranges = <block>, location = [0x0000000000005868, 0x00000000000058cc) -> DW_OP_reg19 W19, decl = dict.c:606

(lldb) f 5
frame #5: 0x000000012e90b3f4 jinx-mod.dylib`jinx_check + 132
jinx-mod.dylib`jinx_check:
->  0x12e90b3f4 <+132>: cbnz   w0, 0x12e90b3c0 ; <+80>
    0x12e90b3f8 <+136>: adrp   x0, 5
    0x12e90b3fc <+140>: ldr    x19, [x0, #0x10]
    0x12e90b400 <+144>: mov    x0, x20
(lldb)

Thanks for this! This looks like a straightforward bug, I shall try to have a fix out soon.

Out of interest, how were you spell-checking in Emacs? I cannot see how this bug could have been triggered by Emacs's ispell command, or similar, using the Enchant command-line interface. (The crash was caused by passing a negative length to the provider's check method, which the command-line enchant program never does.)

Out of interest, how were you spell-checking in Emacs?

It's the Jinx package for Emacs.

The crash was caused by passing a negative length to the provider's check method...

Oops, yes I see it. And a few other -1 calls for add and suggest:
https://github.com/minad/jinx/blob/77cac7f240f45d6c4836e844bd68151c7f863298/jinx-mod.c#L135

I'll let the maintainer know.

Thanks for such a quick turn-around fix!

@jeff-phil The jinx code is (most likely, I haven't checked) correct: Enchant's external APIs do accept -1 as a length. The bug in Enchant was that I had incorrectly made an internal call to an API that was expecting an unsigned length. Makes sense that this problem was showing up with jinx; I did wonder!