diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index 27f97548b3..774f0777c3 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -1,4 +1,4 @@ -*spell.txt* For Vim version 9.2. Last change: 2026 Feb 14 +*spell.txt* For Vim version 9.2. Last change: 2026 May 23 VIM REFERENCE MANUAL by Bram Moolenaar @@ -562,6 +562,11 @@ then Vim will try to guess. avoid running out of memory compression will be done now and then. This can be tuned with the 'mkspellmem' option. + *E1578* + There is a limit on how many postponed prefix and + compound flags can be stored for one word. Reduce the + number of affix/compound flags on a word in the .dic + file that exceeds it. After the spell file was written and it was being used in a buffer it will be reloaded automatically. diff --git a/runtime/doc/tags b/runtime/doc/tags index 9c0298aedd..7cd0074a6a 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -4776,6 +4776,7 @@ E1574 channel.txt /*E1574* E1575 builtin.txt /*E1575* E1576 tagsrch.txt /*E1576* E1577 options.txt /*E1577* +E1578 spell.txt /*E1578* E158 sign.txt /*E158* E159 sign.txt /*E159* E16 cmdline.txt /*E16* diff --git a/src/errors.h b/src/errors.h index 0013a43ee8..9dbd7ac4db 100644 --- a/src/errors.h +++ b/src/errors.h @@ -3812,3 +3812,7 @@ EXTERN char e_tag_file_entry_must_not_be_url[] INIT(= N_("E1576: Tag file entry must not be a URL")); EXTERN char e_invalid_format_string_single_percent_s[] INIT(= N_("E1577: Invalid format string, only one \"%s\" is allowed")); +#ifdef FEAT_SPELL +EXTERN char e_too_many_postponed_prefixes_spell[] + INIT(= N_("E1578: Too many postponed prefixes and/or compound flags")); +#endif diff --git a/src/po/vim.pot b/src/po/vim.pot index 946ce08831..00c192a621 100644 --- a/src/po/vim.pot +++ b/src/po/vim.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Vim\n" "Report-Msgid-Bugs-To: vim-dev@vim.org\n" -"POT-Creation-Date: 2026-05-21 19:38+0000\n" +"POT-Creation-Date: 2026-05-23 19:49+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -8880,6 +8880,9 @@ msgstr "" msgid "E1577: Invalid format string, only one \"%s\" is allowed" msgstr "" +msgid "E1578: Too many postponed prefixes and/or compound flags" +msgstr "" + #. type of cmdline window or 0 #. result of cmdline window or 0 #. buffer of cmdline window or NULL diff --git a/src/spellfile.c b/src/spellfile.c index 9ee7031e55..f1841ddf37 100644 --- a/src/spellfile.c +++ b/src/spellfile.c @@ -2013,8 +2013,8 @@ static int str_equal(char_u *s1, char_u *s2); static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from, char_u *to); static int sal_to_bool(char_u *s); static int get_affix_flags(afffile_T *affile, char_u *afflist); -static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist); -static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_afflist); +static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist, int *cntp); +static int get_compflags(afffile_T *affile, char_u *afflist, char_u *store_afflist, int *cntp); static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afffile_T *affile, hashtab_T *ht, hashtab_T *xht, int condit, int flags, char_u *pfxlist, int pfxlen); static void *getroom(spellinfo_T *spin, size_t len, int align); static char_u *getroom_save(spellinfo_T *spin, char_u *s); @@ -3357,6 +3357,26 @@ check_renumber(spellinfo_T *spin) } } +/* + * Append one affix or compound ID to "store_afflist". + * Returns FAIL when this would overrun the fixed-size buffer. + */ + static int +store_afflist_add( + char_u *store_afflist, + int *cntp, + int id) +{ + if (*cntp >= MAXWLEN - 1) + { + emsg(_(e_too_many_postponed_prefixes_spell)); + return FAIL; + } + store_afflist[(*cntp)++] = id; + store_afflist[*cntp] = NUL; + return OK; +} + /* * Return TRUE if flag "flag" appears in affix list "afflist". */ @@ -3516,6 +3536,7 @@ spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) char_u *afflist; char_u store_afflist[MAXWLEN]; int pfxlen; + int totlen; int need_affix; char_u *dw; char_u *pc; @@ -3665,6 +3686,7 @@ spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) flags = 0; store_afflist[0] = NUL; pfxlen = 0; + totlen = 0; need_affix = FALSE; if (afflist != NULL) { @@ -3676,13 +3698,28 @@ spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) need_affix = TRUE; if (affile->af_pfxpostpone) + { // Need to store the list of prefix IDs with the word. - pfxlen = get_pfxlist(affile, afflist, store_afflist); + if (get_pfxlist(affile, afflist, store_afflist, &totlen) + == FAIL) + { + retval = FAIL; + break; + } + pfxlen = totlen; + } if (spin->si_compflags != NULL) + { // Need to store the list of compound flags with the word. // Concatenate them to the list of prefix IDs. - get_compflags(affile, afflist, store_afflist + pfxlen); + if (get_compflags(affile, afflist, store_afflist, &totlen) + == FAIL) + { + retval = FAIL; + break; + } + } } // Add the word to the word tree(s). @@ -3753,18 +3790,18 @@ get_affix_flags(afffile_T *affile, char_u *afflist) /* * Get the list of prefix IDs from the affix list "afflist". * Used for PFXPOSTPONE. - * Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL - * and return the number of affixes. + * Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL. + * Returns FAIL when the fixed-size buffer would overflow. */ static int get_pfxlist( afffile_T *affile, char_u *afflist, - char_u *store_afflist) + char_u *store_afflist, + int *cntp) { char_u *p; char_u *prevp; - int cnt = 0; int id; char_u key[AH_KEY_LEN]; hashitem_T *hi; @@ -3781,32 +3818,32 @@ get_pfxlist( if (!HASHITEM_EMPTY(hi)) { id = HI2AH(hi)->ah_newID; - if (id != 0) - store_afflist[cnt++] = id; + if (id != 0 && store_afflist_add(store_afflist, cntp, id) == FAIL) + return FAIL; } } if (affile->af_flagtype == AFT_NUM && *p == ',') ++p; } - store_afflist[cnt] = NUL; - return cnt; + return OK; } /* * Get the list of compound IDs from the affix list "afflist" that are used * for compound words. * Puts the flags in "store_afflist[]". + * Returns FAIL when the fixed-size buffer would overflow. */ - static void + static int get_compflags( afffile_T *affile, char_u *afflist, - char_u *store_afflist) + char_u *store_afflist, + int *cntp) { char_u *p; char_u *prevp; - int cnt = 0; char_u key[AH_KEY_LEN]; hashitem_T *hi; @@ -3818,14 +3855,16 @@ get_compflags( // A flag is a compound flag if it appears in "af_comp". vim_strncpy(key, prevp, p - prevp); hi = hash_find(&affile->af_comp, key); - if (!HASHITEM_EMPTY(hi)) - store_afflist[cnt++] = HI2CI(hi)->ci_newID; + if (!HASHITEM_EMPTY(hi) + && store_afflist_add(store_afflist, cntp, + HI2CI(hi)->ci_newID) == FAIL) + return FAIL; } if (affile->af_flagtype == AFT_NUM && *p == ',') ++p; } - store_afflist[cnt] = NUL; + return OK; } /* @@ -3983,10 +4022,20 @@ store_aff_word( if (affile->af_pfxpostpone || spin->si_compflags != NULL) { + int listlen = 0; + if (affile->af_pfxpostpone) + { // Get prefix IDS from the affix list. - use_pfxlen = get_pfxlist(affile, - ae->ae_flags, store_afflist); + if (get_pfxlist(affile, ae->ae_flags, + store_afflist, &listlen) + == FAIL) + { + retval = FAIL; + break; + } + use_pfxlen = listlen; + } else use_pfxlen = 0; use_pfxlist = store_afflist; @@ -3998,14 +4047,30 @@ store_aff_word( for (j = 0; j < use_pfxlen; ++j) if (pfxlist[i] == use_pfxlist[j]) break; - if (j == use_pfxlen) - use_pfxlist[use_pfxlen++] = pfxlist[i]; + if (j == use_pfxlen + && store_afflist_add(use_pfxlist, + &listlen, pfxlist[i]) + == FAIL) + { + retval = FAIL; + break; + } + use_pfxlen = listlen; } + if (retval == FAIL) + break; if (spin->si_compflags != NULL) // Get compound IDS from the affix list. - get_compflags(affile, ae->ae_flags, - use_pfxlist + use_pfxlen); + if (get_compflags(affile, ae->ae_flags, + use_pfxlist, &listlen) + == FAIL) + { + retval = FAIL; + break; + } + if (retval == FAIL) + break; // Combine the list of compound flags. // Concatenate them to the prefix IDs list. @@ -4016,12 +4081,17 @@ store_aff_word( use_pfxlist[j] != NUL; ++j) if (pfxlist[i] == use_pfxlist[j]) break; - if (use_pfxlist[j] == NUL) + if (use_pfxlist[j] == NUL + && store_afflist_add(use_pfxlist, + &listlen, pfxlist[i]) + == FAIL) { - use_pfxlist[j++] = pfxlist[i]; - use_pfxlist[j] = NUL; + retval = FAIL; + break; } } + if (retval == FAIL) + break; } } @@ -6236,6 +6306,7 @@ spell_add_word( char_u line[MAXWLEN * 2]; long fpos, fpos_next = 0; int i; + size_t linelen; char_u *spf; if (!valid_spell_word(word, word + len)) @@ -6312,7 +6383,9 @@ spell_add_word( fpos_next = ftell(fd); if (fpos_next < 0) break; // should never happen - if (STRNCMP(word, line, len) == 0 + linelen = STRLEN(line); + if (linelen >= (size_t)len + && STRNCMP(word, line, len) == 0 && (line[len] == '/' || line[len] < ' ')) { // Found duplicate word. Remove it by writing a '#' at diff --git a/src/testdir/test_spellfile.vim b/src/testdir/test_spellfile.vim index cf75eb4ef9..07156818d9 100644 --- a/src/testdir/test_spellfile.vim +++ b/src/testdir/test_spellfile.vim @@ -1214,5 +1214,37 @@ func Test_mkspell_no_buffer_overflow() defer delete('Xbof2.spl') endfunc +func Test_mkspell_no_affixlist_overflow() + let aff_lines = [ + \ 'SET ISO8859-1', + \ 'PFXPOSTPONE', + \ 'PFX A Y 1', + \ 'PFX A 0 pre .', + \ ] + call writefile(aff_lines, 'Xaffbof.aff', 'D') + call writefile(['1', 'word/' .. repeat('A', 300)], 'Xaffbof.dic', 'D') + + call assert_fails('mkspell! Xaffbof.spl Xaffbof', + \ 'Too many postponed prefixes and/or compound flags') + call assert_false(filereadable('Xaffbof.spl')) +endfunc + +func Test_mkspell_no_compflag_overflow() + " Overflow the compound-flag path in get_compflags(): a word whose + " affix list repeats a compound flag many times accumulates one ID per + " occurrence, overrunning store_afflist[MAXWLEN]. + let aff_lines = [ + \ 'SET ISO8859-1', + \ 'COMPOUNDFLAG c', + \ ] + call writefile(aff_lines, 'Xcompbof.aff', 'D') + + " Repeat the compound flag 'c' far past MAXWLEN. + call writefile(['1', 'word/' .. repeat('c', 300)], 'Xcompbof.dic', 'D') + + call assert_fails('mkspell! Xcompbof.spl Xcompbof', + \ 'Too many postponed prefixes and/or compound flags') + call assert_false(filereadable('Xcompbof.spl')) +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index af37e33da1..f3cfeb60a8 100644 --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 524, /**/ 523, /**/