aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/spellfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/spellfile.c')
-rw-r--r--src/nvim/spellfile.c241
1 files changed, 146 insertions, 95 deletions
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 4d7ff558ad..dab9a2aacd 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -1,3 +1,6 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
// spellfile.c: code for reading and writing spell files.
//
// See spell.c for information about spell checking.
@@ -223,6 +226,7 @@
// few bytes as possible, see offset2bytes())
#include <stdio.h>
+#include <stdint.h>
#include <wctype.h>
#include "nvim/vim.h"
@@ -266,7 +270,7 @@
#define SAL_REM_ACCENTS 4
#define VIMSPELLMAGIC "VIMspell" // string at start of Vim spell file
-#define VIMSPELLMAGICL 8
+#define VIMSPELLMAGICL (sizeof(VIMSPELLMAGIC) - 1)
#define VIMSPELLVERSION 50
// Section IDs. Only renumber them when VIMSPELLVERSION changes!
@@ -493,6 +497,64 @@ typedef struct spellinfo_S {
# include "spellfile.c.generated.h"
#endif
+/// Read n bytes from fd to buf, returning on errors
+///
+/// @param[out] buf Buffer to read to, must be at least n bytes long.
+/// @param[in] n Amount of bytes to read.
+/// @param fd FILE* to read from.
+/// @param exit_code Code to run before returning.
+///
+/// @return Allows to proceed if everything is OK, returns SP_TRUNCERROR if
+/// there are not enough bytes, returns SP_OTHERERROR if reading failed.
+#define SPELL_READ_BYTES(buf, n, fd, exit_code) \
+ do { \
+ const size_t n__SPRB = (n); \
+ FILE *const fd__SPRB = (fd); \
+ char *const buf__SPRB = (buf); \
+ const size_t read_bytes__SPRB = fread(buf__SPRB, 1, n__SPRB, fd__SPRB); \
+ if (read_bytes__SPRB != n__SPRB) { \
+ exit_code; \
+ return feof(fd__SPRB) ? SP_TRUNCERROR : SP_OTHERERROR; \
+ } \
+ } while (0)
+
+/// Like #SPELL_READ_BYTES, but also error out if NUL byte was read
+///
+/// @return Allows to proceed if everything is OK, returns SP_TRUNCERROR if
+/// there are not enough bytes, returns SP_OTHERERROR if reading failed,
+/// returns SP_FORMERROR if read out a NUL byte.
+#define SPELL_READ_NONNUL_BYTES(buf, n, fd, exit_code) \
+ do { \
+ const size_t n__SPRNB = (n); \
+ FILE *const fd__SPRNB = (fd); \
+ char *const buf__SPRNB = (buf); \
+ SPELL_READ_BYTES(buf__SPRNB, n__SPRNB, fd__SPRNB, exit_code); \
+ if (memchr(buf__SPRNB, NUL, (size_t)n__SPRNB)) { \
+ exit_code; \
+ return SP_FORMERROR; \
+ } \
+ } while (0)
+
+/// Check that spell file starts with a magic string
+///
+/// Does not check for version of the file.
+///
+/// @param fd File to check.
+///
+/// @return 0 in case of success, SP_TRUNCERROR if file contains not enough
+/// bytes, SP_FORMERROR if it does not match magic string and
+/// SP_OTHERERROR if reading file failed.
+static inline int spell_check_magic_string(FILE *const fd)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
+{
+ char buf[VIMSPELLMAGICL];
+ SPELL_READ_BYTES(buf, VIMSPELLMAGICL, fd, ;);
+ if (memcmp(buf, VIMSPELLMAGIC, VIMSPELLMAGICL) != 0) {
+ return SP_FORMERROR;
+ }
+ return 0;
+}
+
// Load one spell file and store the info into a slang_T.
//
// This is invoked in three ways:
@@ -513,9 +575,7 @@ spell_load_file (
)
{
FILE *fd;
- char_u buf[VIMSPELLMAGICL];
char_u *p;
- int i;
int n;
int len;
char_u *save_sourcing_name = sourcing_name;
@@ -557,11 +617,20 @@ spell_load_file (
sourcing_lnum = 0;
// <HEADER>: <fileID>
- for (i = 0; i < VIMSPELLMAGICL; ++i)
- buf[i] = getc(fd); // <fileID>
- if (STRNCMP(buf, VIMSPELLMAGIC, VIMSPELLMAGICL) != 0) {
- EMSG(_("E757: This does not look like a spell file"));
- goto endFAIL;
+ const int scms_ret = spell_check_magic_string(fd);
+ switch (scms_ret) {
+ case SP_FORMERROR:
+ case SP_TRUNCERROR: {
+ emsgf(_("E757: This does not look like a spell file"));
+ goto endFAIL;
+ }
+ case SP_OTHERERROR: {
+ emsgf(_("E5042: Failed to read spell file %s: %s"),
+ fname, strerror(ferror(fd)));
+ }
+ case 0: {
+ break;
+ }
}
c = getc(fd); // <versionnr>
if (c < VIMSPELLVERSION) {
@@ -934,12 +1003,10 @@ static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
// Return SP_*ERROR flags.
static int read_region_section(FILE *fd, slang_T *lp, int len)
{
- int i;
-
- if (len > 16)
+ if (len > 16) {
return SP_FORMERROR;
- for (i = 0; i < len; ++i)
- lp->sl_regions[i] = getc(fd); // <regionname>
+ }
+ SPELL_READ_NONNUL_BYTES((char *)lp->sl_regions, (size_t)len, fd, ;);
lp->sl_regions[len] = NUL;
return 0;
}
@@ -982,35 +1049,30 @@ static int read_charflags_section(FILE *fd)
// Return SP_*ERROR flags.
static int read_prefcond_section(FILE *fd, slang_T *lp)
{
- int cnt;
- int i;
- int n;
- char_u *p;
- char_u buf[MAXWLEN + 1];
-
// <prefcondcnt> <prefcond> ...
- cnt = get2c(fd); // <prefcondcnt>
- if (cnt <= 0)
+ const int cnt = get2c(fd); // <prefcondcnt>
+ if (cnt <= 0) {
return SP_FORMERROR;
+ }
lp->sl_prefprog = xcalloc(cnt, sizeof(regprog_T *));
lp->sl_prefixcnt = cnt;
- for (i = 0; i < cnt; ++i) {
+ for (int i = 0; i < cnt; i++) {
// <prefcond> : <condlen> <condstr>
- n = getc(fd); // <condlen>
- if (n < 0 || n >= MAXWLEN)
+ const int n = getc(fd); // <condlen>
+ if (n < 0 || n >= MAXWLEN) {
return SP_FORMERROR;
+ }
// When <condlen> is zero we have an empty condition. Otherwise
// compile the regexp program used to check for the condition.
if (n > 0) {
- buf[0] = '^'; // always match at one position only
- p = buf + 1;
- while (n-- > 0)
- *p++ = getc(fd); // <condstr>
- *p = NUL;
- lp->sl_prefprog[i] = vim_regcomp(buf, RE_MAGIC + RE_STRING);
+ char buf[MAXWLEN + 1];
+ buf[0] = '^'; // always match at one position only
+ SPELL_READ_NONNUL_BYTES(buf + 1, (size_t)n, fd, ;);
+ buf[n + 1] = NUL;
+ lp->sl_prefprog[i] = vim_regcomp((char_u *)buf, RE_MAGIC | RE_STRING);
}
}
return 0;
@@ -1063,7 +1125,6 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first)
// Return SP_*ERROR flags.
static int read_sal_section(FILE *fd, slang_T *slang)
{
- int i;
int cnt;
garray_T *gap;
salitem_T *smp;
@@ -1073,13 +1134,16 @@ static int read_sal_section(FILE *fd, slang_T *slang)
slang->sl_sofo = false;
- i = getc(fd); // <salflags>
- if (i & SAL_F0LLOWUP)
+ const int flags = getc(fd); // <salflags>
+ if (flags & SAL_F0LLOWUP) {
slang->sl_followup = true;
- if (i & SAL_COLLAPSE)
+ }
+ if (flags & SAL_COLLAPSE) {
slang->sl_collapse = true;
- if (i & SAL_REM_ACCENTS)
+ }
+ if (flags & SAL_REM_ACCENTS) {
slang->sl_rem_accents = true;
+ }
cnt = get2c(fd); // <salcount>
if (cnt < 0)
@@ -1099,7 +1163,8 @@ static int read_sal_section(FILE *fd, slang_T *slang)
smp->sm_lead = p;
// Read up to the first special char into sm_lead.
- for (i = 0; i < ccnt; ++i) {
+ int i = 0;
+ for (; i < ccnt; ++i) {
c = getc(fd); // <salfrom>
if (vim_strchr((char_u *)"0123456789(-<^$", c) != NULL)
break;
@@ -1125,11 +1190,17 @@ static int read_sal_section(FILE *fd, slang_T *slang)
// Any following chars go in sm_rules.
smp->sm_rules = p;
- if (i < ccnt)
+ if (i < ccnt) {
// store the char we got while checking for end of sm_lead
*p++ = c;
- for (++i; i < ccnt; ++i)
- *p++ = getc(fd); // <salfrom>
+ }
+ i++;
+ if (i < ccnt) {
+ SPELL_READ_NONNUL_BYTES( // <salfrom>
+ (char *)p, (size_t)(ccnt - i), fd, xfree(smp->sm_lead));
+ p += (ccnt - i);
+ i = ccnt;
+ }
*p++ = NUL;
// <saltolen> <salto>
@@ -1569,9 +1640,14 @@ spell_read_tree (
// The tree size was computed when writing the file, so that we can
// allocate it as one long block. <nodecount>
- int len = get4c(fd);
- if (len < 0)
+ long len = get4c(fd);
+ if (len < 0) {
return SP_TRUNCERROR;
+ }
+ if ((size_t)len >= SIZE_MAX / sizeof(int)) { // -V547
+ // Invalid length, multiply with sizeof(int) would overflow.
+ return SP_FORMERROR;
+ }
if (len > 0) {
// Allocate the byte array.
bp = xmalloc(len);
@@ -1873,7 +1949,6 @@ static void spell_print_tree(wordnode_T *root)
static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
{
FILE *fd;
- afffile_T *aff;
char_u rline[MAXLINELEN];
char_u *line;
char_u *pc = NULL;
@@ -1930,11 +2005,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
do_mapline = GA_EMPTY(&spin->si_map);
// Allocate and init the afffile_T structure.
- aff = (afffile_T *)getroom(spin, sizeof(afffile_T), true);
- if (aff == NULL) {
- fclose(fd);
- return NULL;
- }
+ afffile_T *aff = getroom(spin, sizeof(*aff), true);
hash_init(&aff->af_pref);
hash_init(&aff->af_suff);
hash_init(&aff->af_comp);
@@ -2022,20 +2093,18 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
smsg(_("FLAG after using flags in %s line %d: %s"),
fname, lnum, items[1]);
} else if (spell_info_item(items[0]) && itemcnt > 1) {
- p = (char_u *)getroom(spin,
- (spin->si_info == NULL ? 0 : STRLEN(spin->si_info))
- + STRLEN(items[0])
- + STRLEN(items[1]) + 3, false);
- if (p != NULL) {
- if (spin->si_info != NULL) {
- STRCPY(p, spin->si_info);
- STRCAT(p, "\n");
- }
- STRCAT(p, items[0]);
- STRCAT(p, " ");
- STRCAT(p, items[1]);
- spin->si_info = p;
+ p = getroom(spin,
+ (spin->si_info == NULL ? 0 : STRLEN(spin->si_info))
+ + STRLEN(items[0])
+ + STRLEN(items[1]) + 3, false);
+ if (spin->si_info != NULL) {
+ STRCPY(p, spin->si_info);
+ STRCAT(p, "\n");
}
+ STRCAT(p, items[0]);
+ STRCAT(p, " ");
+ STRCAT(p, items[1]);
+ spin->si_info = p;
} else if (is_aff_rule(items, itemcnt, "MIDWORD", 2)
&& midword == NULL) {
midword = getroom_save(spin, items[1]);
@@ -2215,14 +2284,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
fname, lnum, items[1]);
} else {
// New affix letter.
- cur_aff = (affheader_T *)getroom(spin,
- sizeof(affheader_T), true);
- if (cur_aff == NULL)
- break;
+ cur_aff = getroom(spin, sizeof(*cur_aff), true);
cur_aff->ah_flag = affitem2flag(aff->af_flagtype, items[1],
- fname, lnum);
- if (cur_aff->ah_flag == 0 || STRLEN(items[1]) >= AH_KEY_LEN)
+ fname, lnum);
+ if (cur_aff->ah_flag == 0 || STRLEN(items[1]) >= AH_KEY_LEN) {
break;
+ }
if (cur_aff->ah_flag == aff->af_bad
|| cur_aff->ah_flag == aff->af_rare
|| cur_aff->ah_flag == aff->af_keepcase
@@ -2230,11 +2297,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
|| cur_aff->ah_flag == aff->af_circumfix
|| cur_aff->ah_flag == aff->af_nosuggest
|| cur_aff->ah_flag == aff->af_needcomp
- || cur_aff->ah_flag == aff->af_comproot)
+ || cur_aff->ah_flag == aff->af_comproot) {
smsg(_("Affix also used for "
"BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST"
"in %s line %d: %s"),
- fname, lnum, items[1]);
+ fname, lnum, items[1]);
+ }
STRCPY(cur_aff->ah_key, items[1]);
hash_add(tp, cur_aff->ah_key);
@@ -2296,11 +2364,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
smsg(_(e_afftrailing), fname, lnum, items[lasti]);
// New item for an affix letter.
- --aff_todo;
- aff_entry = (affentry_T *)getroom(spin,
- sizeof(affentry_T), true);
- if (aff_entry == NULL)
- break;
+ aff_todo--;
+ aff_entry = getroom(spin, sizeof(*aff_entry), true);
if (STRCMP(items[2], "0") != 0)
aff_entry->ae_chop = getroom_save(spin, items[2]);
@@ -2391,8 +2456,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
}
}
- if (aff_entry->ae_chop == NULL
- && aff_entry->ae_flags == NULL) {
+ if (aff_entry->ae_chop == NULL) {
int idx;
char_u **pp;
int n;
@@ -2773,12 +2837,10 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla
// the existing ID. Otherwise add a new entry.
STRLCPY(key, prevp, p - prevp + 1);
hi = hash_find(&aff->af_comp, key);
- if (!HASHITEM_EMPTY(hi))
+ if (!HASHITEM_EMPTY(hi)) {
id = HI2CI(hi)->ci_newID;
- else {
- ci = (compitem_T *)getroom(spin, sizeof(compitem_T), true);
- if (ci == NULL)
- break;
+ } else {
+ ci = getroom(spin, sizeof(compitem_T), true);
STRCPY(ci->ci_key, key);
ci->ci_flag = flag;
// Avoid using a flag ID that has a special meaning in a
@@ -3580,7 +3642,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname)
flags |= WF_REGION;
l = *p - '0';
- if (l > spin->si_region_count) {
+ if (l == 0 || l > spin->si_region_count) {
smsg(_("Invalid region nr in %s line %d: %s"),
fname, lnum, p);
break;
@@ -3662,12 +3724,8 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align)
// Returns NULL when out of memory.
static char_u *getroom_save(spellinfo_T *spin, char_u *s)
{
- char_u *sc;
-
- sc = (char_u *)getroom(spin, STRLEN(s) + 1, false);
- if (sc != NULL)
- STRCPY(sc, s);
- return sc;
+ const size_t s_size = STRLEN(s) + 1;
+ return memcpy(getroom(spin, s_size, false), s, s_size);
}
@@ -3686,6 +3744,7 @@ static void free_blocks(sblock_T *bl)
// Allocate the root of a word tree.
// Returns NULL when out of memory.
static wordnode_T *wordtree_alloc(spellinfo_T *spin)
+ FUNC_ATTR_NONNULL_RET
{
return (wordnode_T *)getroom(spin, sizeof(wordnode_T), true);
}
@@ -4719,8 +4778,6 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
// We use si_foldroot for the soundfolded trie.
spin->si_foldroot = wordtree_alloc(spin);
- if (spin->si_foldroot == NULL)
- return FAIL;
// Let tree_add_word() know we're adding to the soundfolded tree
spin->si_sugtree = true;
@@ -5108,12 +5165,6 @@ mkspell (
spin.si_foldroot = wordtree_alloc(&spin);
spin.si_keeproot = wordtree_alloc(&spin);
spin.si_prefroot = wordtree_alloc(&spin);
- if (spin.si_foldroot == NULL
- || spin.si_keeproot == NULL
- || spin.si_prefroot == NULL) {
- free_blocks(spin.si_blocks);
- goto theend;
- }
// When not producing a .add.spl file clear the character table when
// we encounter one in the .aff file. This means we dump the current