Coverage Report

Created: 2025-08-29 06:09

/src/aspell-fuzz/aspell_fuzzer.cpp
Line
Count
Source (jump to first uncovered line)
1
#include <stdint.h>
2
#include <stdio.h>
3
#include <stdlib.h>
4
#include <string.h>
5
#include <sys/types.h>
6
#include <libgen.h>
7
#include <aspell.h>
8
#include <algorithm>
9
10
static int enable_diags;
11
static char data_dir[1024];
12
13
#define FUZZ_DEBUG(FMT, ...)                                                  \
14
583k
        if (enable_diags) {                                                   \
15
0
          fprintf(stderr, FMT, ##__VA_ARGS__);                                \
16
0
          fprintf(stderr, "\n");                                              \
17
0
        }
18
static const size_t MAX_CONFIG_LEN = 10*1024;
19
20
int parse_config(AspellConfig *spell_config,
21
                 uint8_t *config,
22
                 size_t config_len);
23
24
// On startup, this function is called once. Use it to access argv.
25
2
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
26
2
  char *argv0_copy = strdup((*argv)[0]);
27
28
  // Create the data dir.
29
2
  snprintf(data_dir, sizeof(data_dir), "%s/dict", dirname(argv0_copy));
30
31
  // Free off the temporary variable.
32
2
  free(argv0_copy);
33
34
2
  printf("Init: Running with data-dir: %s\n", data_dir);
35
36
2
  return 0;
37
2
}
38
39
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
40
473
{
41
473
  AspellCanHaveError *possible_err = NULL;
42
473
  AspellSpeller *spell_checker = NULL;
43
473
  AspellConfig *spell_config = NULL;
44
473
  AspellDocumentChecker *doc_checker = NULL;
45
473
  AspellCanHaveError *doc_err = NULL;
46
473
  AspellToken token;
47
473
  const char *data_str = reinterpret_cast<const char *>(data);
48
473
  uint8_t config[MAX_CONFIG_LEN];
49
473
  size_t config_len;
50
473
  int rc;
51
52
  // Enable or disable diagnostics based on the FUZZ_VERBOSE environment flag.
53
473
  enable_diags = (getenv("FUZZ_VERBOSE") != NULL);
54
55
  // Copy up to MAX_CONFIG_LEN bytes from the data.
56
473
  config_len = std::min(size, MAX_CONFIG_LEN);
57
473
  memcpy(config, data, config_len);
58
59
  // Create a new configuration class.
60
473
  spell_config = new_aspell_config();
61
62
  // Parse configuration. Exit if the configuration was bad.
63
473
  rc = parse_config(spell_config, config, config_len);
64
473
  if (rc == -1)
65
8
  {
66
8
    FUZZ_DEBUG("Configuration parsing failed");
67
8
    goto EXIT_LABEL;
68
8
  }
69
70
  // Move the data pointer past the config.
71
465
  data_str += rc;
72
465
  size -= rc;
73
74
465
  FUZZ_DEBUG("Document: %.*s", (int)size, data_str);
75
76
  // Replace the data dir with the relative directory so that it works wherever
77
  // it is run from, so long as dictionary files are installed relative to it.
78
465
  FUZZ_DEBUG("Overriding data-dir to %s", data_dir);
79
465
  aspell_config_replace(spell_config, "data-dir", data_dir);
80
81
  // Convert the configuration to a spell checker.
82
465
  possible_err = new_aspell_speller(spell_config);
83
465
  if (aspell_error_number(possible_err) != 0) {
84
    // Failed on configuration.
85
71
    FUZZ_DEBUG("Failed to create speller: %s",
86
71
               aspell_error_message(possible_err));
87
71
    delete_aspell_can_have_error(possible_err);
88
71
    goto EXIT_LABEL;
89
71
  }
90
91
  // Create a spell checker.
92
394
  spell_checker = to_aspell_speller(possible_err);
93
94
  // Convert the spell checker to a document checker.
95
394
  doc_err = new_aspell_document_checker(spell_checker);
96
394
  if (aspell_error(doc_err) != 0) {
97
    // Failed to convert to a document checker.
98
4
    FUZZ_DEBUG("Failed to create document checker: %s",
99
4
               aspell_error_message(doc_err));
100
4
    delete_aspell_can_have_error(doc_err);
101
4
    goto EXIT_LABEL;
102
4
  }
103
104
390
  doc_checker = to_aspell_document_checker(doc_err);
105
106
  // Process the remainder of the document.
107
390
  aspell_document_checker_process(doc_checker, data_str, size);
108
109
  // Iterate over all misspellings.
110
390
  token = aspell_document_checker_next_misspelling(doc_checker);
111
112
390
  FUZZ_DEBUG("Token len %d", token.len);
113
114
390
  for (;
115
9.28k
       token.len != 0;
116
8.89k
       token = aspell_document_checker_next_misspelling(doc_checker))
117
8.89k
  {
118
    // Get spelling suggestions for the misspelling.
119
8.89k
    auto word_list = aspell_speller_suggest(spell_checker,
120
8.89k
                                            data_str + token.offset,
121
8.89k
                                            token.len);
122
123
    // Iterate over the suggested replacement words in the word list.
124
8.89k
    AspellStringEnumeration *els = aspell_word_list_elements(word_list);
125
126
8.89k
    for (const char *word = aspell_string_enumeration_next(els);
127
568k
         word != 0;
128
559k
         word = aspell_string_enumeration_next(els))
129
559k
    {
130
      // Conditionally print out the suggested replacement words.
131
559k
      FUZZ_DEBUG("Suggesting replacement for word at offset %d len %d: %s",
132
559k
                 token.offset,
133
559k
                 token.len,
134
559k
                 word);
135
559k
    }
136
8.89k
    delete_aspell_string_enumeration(els);
137
8.89k
  }
138
139
473
EXIT_LABEL:
140
141
473
  if (doc_checker != NULL) {
142
390
    delete_aspell_document_checker(doc_checker);
143
390
  }
144
145
473
  if (spell_checker != NULL) {
146
394
    delete_aspell_speller(spell_checker);
147
394
  }
148
149
473
  if (spell_config != NULL) {
150
473
    delete_aspell_config(spell_config);
151
473
  }
152
153
473
  return 0;
154
390
}
155
156
// Returns -1 on error, or the number of bytes consumed from the config string
157
// otherwise.
158
int parse_config(AspellConfig *spell_config,
159
                 uint8_t *config,
160
                 size_t config_len)
161
473
{
162
473
  uint8_t line[MAX_CONFIG_LEN];
163
164
473
  uint8_t *config_ptr = config;
165
473
  size_t config_ptr_used = 0;
166
167
473
  uint8_t *delimiter;
168
169
  // Iterate over the lines.
170
473
  for (delimiter = (uint8_t *)memchr(config_ptr,
171
473
                                     '\n',
172
473
                                     config_len - config_ptr_used);
173
14.2k
       delimiter != NULL;
174
13.7k
       delimiter = (uint8_t *)memchr(config_ptr,
175
13.7k
                                     '\n',
176
13.7k
                                     config_len - config_ptr_used))
177
13.9k
  {
178
13.9k
    int line_len = delimiter - config_ptr;
179
180
13.9k
    if (line_len == 0) {
181
      // The line is zero-length; it's the end of configuration. Skip over the
182
      // delimiter and break out.
183
216
      FUZZ_DEBUG("Breaking out of config");
184
216
      config_ptr++;
185
216
      config_ptr_used++;
186
216
      break;
187
216
    }
188
189
    // Copy the line into the line array. Replace the newline by a null.
190
13.7k
    memcpy(line, config_ptr, line_len);
191
13.7k
    line[line_len] = 0;
192
193
    // Try and split the line by =.
194
13.7k
    uint8_t *kv_delim = (uint8_t *)memchr(line, '=', line_len);
195
196
13.7k
    if (kv_delim == NULL) {
197
      // Can't split as a k/v pair. Exit early.
198
8
      return -1;
199
8
    }
200
201
    // Convert the line into a key, value pair.
202
13.7k
    kv_delim[0] = 0;
203
204
13.7k
    char *keyword = reinterpret_cast<char *>(line);
205
13.7k
    char *value = reinterpret_cast<char *>(kv_delim + 1);
206
207
13.7k
    FUZZ_DEBUG("Key: %s; Value: %s", keyword, value);
208
13.7k
    int ok = aspell_config_replace(spell_config, keyword, value);
209
13.7k
    if (!ok) {
210
      // Log any errors and continue.
211
8.25k
      FUZZ_DEBUG("Config error from aspell_config_replace: %s",
212
8.25k
                 aspell_config_error_message(spell_config));
213
8.25k
    }
214
215
    // Advance the config pointers.  Make sure to add 1 for the delimiter.
216
13.7k
    config_ptr += (line_len + 1);
217
13.7k
    config_ptr_used += (line_len + 1);
218
13.7k
  }
219
220
  // Return how much data  was used.
221
465
  FUZZ_DEBUG("Used %zu bytes of configuration data", config_ptr_used);
222
223
465
  return config_ptr_used;
224
473
}