Coverage Report

Created: 2026-06-13 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hunspell/src/hunspell/hunspell.cxx
Line
Count
Source
1
/* ***** BEGIN LICENSE BLOCK *****
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3
 *
4
 * Copyright (C) 2002-2022 Németh László
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version
7
 * 1.1 (the "License"); you may not use this file except in compliance with
8
 * the License. You may obtain a copy of the License at
9
 * http://www.mozilla.org/MPL/
10
 *
11
 * Software distributed under the License is distributed on an "AS IS" basis,
12
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13
 * for the specific language governing rights and limitations under the
14
 * License.
15
 *
16
 * Hunspell is based on MySpell which is Copyright (C) 2002 Kevin Hendricks.
17
 *
18
 * Contributor(s): David Einstein, Davide Prina, Giuseppe Modugno,
19
 * Gianluca Turconi, Simon Brouwer, Noll János, Bíró Árpád,
20
 * Goldman Eleonóra, Sarlós Tamás, Bencsáth Boldizsár, Halácsy Péter,
21
 * Dvornik László, Gefferth András, Nagy Viktor, Varga Dániel, Chris Halls,
22
 * Rene Engelhard, Bram Moolenaar, Dafydd Jones, Harri Pitkänen
23
 *
24
 * Alternatively, the contents of this file may be used under the terms of
25
 * either the GNU General Public License Version 2 or later (the "GPL"), or
26
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27
 * in which case the provisions of the GPL or the LGPL are applicable instead
28
 * of those above. If you wish to allow use of your version of this file only
29
 * under the terms of either the GPL or the LGPL, and not to allow others to
30
 * use your version of this file under the terms of the MPL, indicate your
31
 * decision by deleting the provisions above and replace them with the notice
32
 * and other provisions required by the GPL or the LGPL. If you do not delete
33
 * the provisions above, a recipient may use your version of this file under
34
 * the terms of any one of the MPL, the GPL or the LGPL.
35
 *
36
 * ***** END LICENSE BLOCK ***** */
37
/*
38
 * Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada
39
 * And Contributors.  All rights reserved.
40
 *
41
 * Redistribution and use in source and binary forms, with or without
42
 * modification, are permitted provided that the following conditions
43
 * are met:
44
 *
45
 * 1. Redistributions of source code must retain the above copyright
46
 *    notice, this list of conditions and the following disclaimer.
47
 *
48
 * 2. Redistributions in binary form must reproduce the above copyright
49
 *    notice, this list of conditions and the following disclaimer in the
50
 *    documentation and/or other materials provided with the distribution.
51
 *
52
 * 3. All modifications to the source code must be clearly marked as
53
 *    such.  Binary redistributions based on modified source code
54
 *    must be clearly marked as modified versions in the documentation
55
 *    and/or other materials provided with the distribution.
56
 *
57
 * THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS AND CONTRIBUTORS
58
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
59
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
60
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
61
 * KEVIN B. HENDRICKS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
62
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
63
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
64
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
65
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
66
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
67
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
68
 * SUCH DAMAGE.
69
 */
70
71
#include <cstdlib>
72
#include <cstring>
73
#include <cstdio>
74
#include <ctime>
75
76
#include "affixmgr.hxx"
77
#include "hunspell.hxx"
78
#include "suggestmgr.hxx"
79
#include "hunspell.h"
80
#include "csutil.hxx"
81
82
#include <limits>
83
#include <memory>
84
#include <string>
85
86
3.96M
#define MAXWORDUTF8LEN (MAXWORDLEN * 3)
87
25.1k
#define MAXSPELLMLLEN 8192
88
89
class HunspellImpl
90
{
91
public:
92
 HunspellImpl(const char* affpath, const char* dpath, const char* key = nullptr);
93
 HunspellImpl(const HunspellImpl&) = delete;
94
 HunspellImpl& operator=(const HunspellImpl&) = delete;
95
 ~HunspellImpl();
96
 int add_dic(const char* dpath, const char* key = nullptr);
97
 std::vector<std::string> suffix_suggest(const std::string& root_word);
98
 std::vector<std::string> generate(const std::string& word, const std::vector<std::string>& pl);
99
 std::vector<std::string> generate(const std::string& word, const std::string& pattern);
100
 std::vector<std::string> stem(const std::string& word);
101
 std::vector<std::string> stem(const std::vector<std::string>& morph);
102
 std::vector<std::string> analyze(const std::string& word);
103
 int get_langnum() const;
104
 bool input_conv(const std::string& word, std::string& dest);
105
 bool spell(const std::string& word,
106
            std::vector<std::string>& candidate_stack,
107
            int* info = nullptr,
108
            std::string* root = nullptr,
109
            std::chrono::steady_clock::time_point suggest_start = std::chrono::steady_clock::time_point::max());
110
 std::vector<std::string> suggest(const std::string& word);
111
 std::vector<std::string> suggest(const std::string& word,
112
                                  std::vector<std::string>& suggest_candidate_stack,
113
                                  std::chrono::steady_clock::time_point suggest_start);
114
 const std::string& get_wordchars_cpp() const;
115
 const std::vector<w_char>& get_wordchars_utf16() const;
116
 const std::string& get_dict_encoding() const;
117
 int add(const std::string& word);
118
 int add_with_flags(const std::string& word, const std::string& flags, const std::string& desc = "");
119
 int add_with_affix(const std::string& word, const std::string& example);
120
 int remove(const std::string& word);
121
 const std::string& get_version_cpp() const;
122
 struct cs_info* get_csconv();
123
124
 int spell(const char* word, int* info = nullptr, char** root = nullptr);
125
 int suggest(char*** slst, const char* word);
126
 int suffix_suggest(char*** slst, const char* root_word);
127
 void free_list(char*** slst, int n);
128
 char* get_dic_encoding();
129
 int analyze(char*** slst, const char* word);
130
 int stem(char*** slst, const char* word);
131
 int stem(char*** slst, char** morph, int n);
132
 int generate(char*** slst, const char* word, const char* word2);
133
 int generate(char*** slst, const char* word, char** desc, int n);
134
 const char* get_wordchars() const;
135
 const char* get_version() const;
136
 int input_conv(const char* word, char* dest, size_t destsize);
137
138
private:
139
  std::vector<std::unique_ptr<HashMgr>> m_HMgrs;
140
  std::unique_ptr<AffixMgr> pAMgr; // pAMgr depends on m_HMgrs
141
  std::unique_ptr<SuggestMgr> pSMgr; // pSMgr depends on pAMgr
142
  std::string affixpath;
143
  std::string encoding;
144
  const struct cs_info* csconv;
145
  int langnum;
146
  int utf8;
147
  int complexprefixes;
148
  std::vector<std::string> wordbreak;
149
150
private:
151
  std::vector<std::string> analyze_internal(const std::string& word);
152
  bool spell_internal(const std::string& word,
153
                      std::vector<std::string>& candidate_stack,
154
                      int* info = nullptr,
155
                      std::string* root = nullptr,
156
                      std::chrono::steady_clock::time_point suggest_start = std::chrono::steady_clock::time_point::max());
157
  std::vector<std::string> suggest_internal(const std::string& word,
158
                                            std::vector<std::string>& spell_candidate_stack,
159
                                            std::vector<std::string>& suggest_candidate_stack,
160
                                            bool& capitalized, size_t& abbreviated, int& captype,
161
                                            std::chrono::steady_clock::time_point suggest_start);
162
  void cleanword(std::string& dest, const std::string&, int* pcaptype, int* pabbrev);
163
  size_t cleanword2(std::string& dest,
164
                    std::vector<w_char>& dest_u,
165
                    const std::string& src,
166
                    int* pcaptype,
167
                    size_t* pabbrev);
168
  void clean_ignore(std::string& dest, const std::string& src);
169
  void mkinitcap(std::string& u8);
170
  int mkinitcap2(std::string& u8, std::vector<w_char>& u16);
171
  int mkinitsmall2(std::string& u8, std::vector<w_char>& u16);
172
  void mkallcap(std::string& u8);
173
  int mkallsmall2(std::string& u8, std::vector<w_char>& u16);
174
  struct hentry* checkword(const std::string& source, int* info, std::string* root,
175
                           std::chrono::steady_clock::time_point suggest_start = std::chrono::steady_clock::time_point::max());
176
  std::string sharps_u8_l1(const std::string& source);
177
  hentry*
178
  spellsharps(std::string& base, size_t start_pos, int, int, int* info, std::string* root,
179
              std::chrono::steady_clock::time_point suggest_start = std::chrono::steady_clock::time_point::max());
180
  int is_keepcase(const hentry* rv);
181
  void insert_sug(std::vector<std::string>& slst, const std::string& word);
182
  void cat_result(std::string& result, const std::string& st);
183
  std::vector<std::string> spellml(const std::string& word);
184
  std::string get_xml_par(const std::string& par, std::string::size_type pos);
185
  std::string::size_type get_xml_pos(const std::string& s, std::string::size_type pos, const char* attr);
186
  std::vector<std::string> get_xml_list(const std::string& list, std::string::size_type pos, const char* tag);
187
  int check_xml_par(const std::string& q, std::string::size_type pos, const char* attr, const char* value);
188
};
189
190
HunspellImpl::HunspellImpl(const char* affpath, const char* dpath, const char* key)
191
36.1k
  : affixpath(affpath) {
192
36.1k
  csconv = nullptr;
193
36.1k
  utf8 = 0;
194
36.1k
  complexprefixes = 0;
195
196
  /* first set up the hash manager */
197
36.1k
  m_HMgrs.push_back(std::make_unique<HashMgr>(dpath, affpath, key));
198
199
  /* next set up the affix manager */
200
  /* it needs access to the hash manager lookup methods */
201
36.1k
  pAMgr = std::make_unique<AffixMgr>(affpath, m_HMgrs, key);
202
203
  /* get the preferred try string and the dictionary */
204
  /* encoding from the Affix Manager for that dictionary */
205
36.1k
  std::string try_string = pAMgr->get_try_string();
206
36.1k
  encoding = pAMgr->get_encoding();
207
36.1k
  langnum = pAMgr->get_langnum();
208
36.1k
  utf8 = pAMgr->get_utf8();
209
36.1k
  if (!utf8)
210
27.7k
    csconv = get_current_cs(encoding);
211
36.1k
  complexprefixes = pAMgr->get_complexprefixes();
212
36.1k
  wordbreak = pAMgr->get_breaktable();
213
214
  /* and finally set up the suggestion manager */
215
36.1k
  pSMgr = std::make_unique<SuggestMgr>(try_string, MAXSUGGESTION, pAMgr.get());
216
36.1k
}
217
218
36.1k
HunspellImpl::~HunspellImpl() {
219
#ifdef MOZILLA_CLIENT
220
  delete[] csconv;
221
#endif
222
36.1k
  csconv = nullptr;
223
36.1k
}
224
225
// load extra dictionaries
226
0
int HunspellImpl::add_dic(const char* dpath, const char* key) {
227
0
  m_HMgrs.push_back(std::make_unique<HashMgr>(dpath, affixpath.c_str(), key));
228
0
  return 0;
229
0
}
230
231
232
// make a copy of src at dest while removing all characters
233
// specified in IGNORE rule
234
void HunspellImpl::clean_ignore(std::string& dest,
235
31.3M
                                const std::string& src) {
236
31.3M
  dest.clear();
237
31.3M
  dest.assign(src);
238
31.3M
  const char* ignoredchars = pAMgr ? pAMgr->get_ignore() : nullptr;
239
31.3M
  if (ignoredchars != nullptr) {
240
2.26M
    if (utf8) {
241
576k
      const std::vector<w_char>& ignoredchars_utf16 =
242
576k
          pAMgr->get_ignore_utf16();
243
576k
      remove_ignored_chars_utf(dest, ignoredchars_utf16);
244
1.69M
    } else {
245
1.69M
      remove_ignored_chars(dest, ignoredchars);
246
1.69M
    }
247
2.26M
  }
248
31.3M
}
249
250
251
// make a copy of src at destination while removing all leading
252
// blanks and removing any trailing periods after recording
253
// their presence with the abbreviation flag
254
// also since already going through character by character,
255
// set the capitalization type
256
// return the length of the "cleaned" (and UTF-8 encoded) word
257
258
size_t HunspellImpl::cleanword2(std::string& dest,
259
                         std::vector<w_char>& dest_utf,
260
                         const std::string& src,
261
                         int* pcaptype,
262
14.7M
                         size_t* pabbrev) {
263
14.7M
  dest.clear();
264
14.7M
  dest_utf.clear();
265
266
  // remove IGNORE characters from the string
267
14.7M
  std::string w2;
268
14.7M
  clean_ignore(w2, src);
269
270
14.7M
  const char* q = w2.c_str();
271
14.7M
  int nl = (int)w2.size();
272
273
  // first skip over any leading blanks
274
15.0M
  while (*q == ' ') {
275
270k
    ++q;
276
270k
    nl--;
277
270k
  }
278
279
  // now strip off any trailing periods (recording their presence)
280
14.7M
  *pabbrev = 0;
281
282
16.2M
  while ((nl > 0) && (*(q + nl - 1) == '.')) {
283
1.47M
    nl--;
284
1.47M
    (*pabbrev)++;
285
1.47M
  }
286
287
  // if no characters are left it can't be capitalized
288
14.7M
  if (nl <= 0) {
289
471k
    *pcaptype = NOCAP;
290
471k
    return 0;
291
471k
  }
292
293
14.2M
  dest.append(q, nl);
294
14.2M
  nl = dest.size();
295
14.2M
  if (utf8) {
296
3.81M
    u8_u16(dest_utf, dest);
297
3.81M
    *pcaptype = get_captype_utf8(dest_utf, langnum);
298
10.4M
  } else {
299
10.4M
    *pcaptype = get_captype(dest, csconv);
300
10.4M
  }
301
14.2M
  return nl;
302
14.7M
}
303
304
void HunspellImpl::cleanword(std::string& dest,
305
                        const std::string& src,
306
                        int* pcaptype,
307
7.39k
                        int* pabbrev) {
308
7.39k
  dest.clear();
309
7.39k
  const unsigned char* q = (const unsigned char*)src.c_str();
310
7.39k
  int firstcap = 0, nl = (int)src.size();
311
312
  // first skip over any leading blanks
313
10.8k
  while (*q == ' ') {
314
3.44k
    ++q;
315
3.44k
    nl--;
316
3.44k
  }
317
318
  // now strip off any trailing periods (recording their presence)
319
7.39k
  *pabbrev = 0;
320
321
22.4k
  while ((nl > 0) && (*(q + nl - 1) == '.')) {
322
15.0k
    nl--;
323
15.0k
    (*pabbrev)++;
324
15.0k
  }
325
326
  // if no characters are left it can't be capitalized
327
7.39k
  if (nl <= 0) {
328
194
    *pcaptype = NOCAP;
329
194
    return;
330
194
  }
331
332
  // now determine the capitalization type of the first nl letters
333
7.20k
  int ncap = 0;
334
7.20k
  int nneutral = 0;
335
7.20k
  int nc = 0;
336
337
7.20k
  if (!utf8) {
338
529k
    while (nl > 0) {
339
525k
      nc++;
340
525k
      if (csconv[(*q)].ccase)
341
456k
        ncap++;
342
525k
      if (csconv[(*q)].cupper == csconv[(*q)].clower)
343
60.1k
        nneutral++;
344
525k
      dest.push_back(*q++);
345
525k
      nl--;
346
525k
    }
347
    // remember to terminate the destination string
348
3.87k
    firstcap = csconv[static_cast<unsigned char>(dest[0])].ccase;
349
3.87k
  } else {
350
3.32k
    std::vector<w_char> t;
351
3.32k
    u8_u16(t, src);
352
146k
    for (auto& wc : t) {
353
146k
      const auto idx = (unsigned short)wc;
354
146k
      const auto low = unicodetolower(idx, langnum);
355
146k
      if (idx != low)
356
98.5k
        ncap++;
357
146k
      if (unicodetoupper(idx, langnum) == low)
358
44.4k
        nneutral++;
359
146k
    }
360
3.32k
    u16_u8(dest, t);
361
3.32k
    if (ncap) {
362
2.08k
      const auto idx = (unsigned short)t[0];
363
2.08k
      firstcap = (idx != unicodetolower(idx, langnum));
364
2.08k
    }
365
3.32k
  }
366
367
  // now finally set the captype
368
7.20k
  if (ncap == 0) {
369
2.21k
    *pcaptype = NOCAP;
370
4.98k
  } else if ((ncap == 1) && firstcap) {
371
1.40k
    *pcaptype = INITCAP;
372
3.57k
  } else if ((ncap == nc) || ((ncap + nneutral) == nc)) {
373
1.22k
    *pcaptype = ALLCAP;
374
2.34k
  } else if ((ncap > 1) && firstcap) {
375
647
    *pcaptype = HUHINITCAP;
376
1.70k
  } else {
377
1.70k
    *pcaptype = HUHCAP;
378
1.70k
  }
379
7.20k
}
380
381
15.4k
void HunspellImpl::mkallcap(std::string& u8) {
382
15.4k
  if (utf8) {
383
8.55k
    std::vector<w_char> u16;
384
8.55k
    u8_u16(u16, u8);
385
8.55k
    ::mkallcap_utf(u16, langnum);
386
8.55k
    u16_u8(u8, u16);
387
8.55k
  } else {
388
6.84k
    ::mkallcap(u8, csconv);
389
6.84k
  }
390
15.4k
}
391
392
1.58M
int HunspellImpl::mkallsmall2(std::string& u8, std::vector<w_char>& u16) {
393
1.58M
  if (utf8) {
394
360k
    ::mkallsmall_utf(u16, langnum);
395
360k
    u16_u8(u8, u16);
396
1.22M
  } else {
397
1.22M
    ::mkallsmall(u8, csconv);
398
1.22M
  }
399
1.58M
  return u8.size();
400
1.58M
}
401
402
// convert UTF-8 sharp S codes to latin 1
403
151k
std::string HunspellImpl::sharps_u8_l1(const std::string& source) {
404
151k
  std::string dest(source);
405
151k
  mystrrep(dest, "\xC3\x9F", "\xDF");
406
151k
  return dest;
407
151k
}
408
409
// recursive search for right ss - sharp s permutations
410
hentry* HunspellImpl::spellsharps(std::string& base,
411
                              size_t n_pos,
412
                              int n,
413
                              int repnum,
414
                              int* info,
415
                              std::string* root,
416
351k
                              std::chrono::steady_clock::time_point suggest_start) {
417
351k
  size_t pos = base.find("ss", n_pos);
418
351k
  if (pos != std::string::npos && (n < MAXSHARPS)) {
419
152k
    base[pos] = '\xC3';
420
152k
    base[pos + 1] = '\x9F';
421
152k
    hentry* h = spellsharps(base, pos + 2, n + 1, repnum + 1, info, root, suggest_start);
422
152k
    if (h)
423
2.92k
      return h;
424
150k
    base[pos] = 's';
425
150k
    base[pos + 1] = 's';
426
150k
    h = spellsharps(base, pos + 2, n + 1, repnum, info, root, suggest_start);
427
150k
    if (h)
428
1.24k
      return h;
429
198k
  } else if (repnum > 0) {
430
152k
    if (utf8)
431
750
      return checkword(base, info, root, suggest_start);
432
151k
    std::string tmp(sharps_u8_l1(base));
433
151k
    return checkword(tmp, info, root, suggest_start);
434
152k
  }
435
194k
  return nullptr;
436
351k
}
437
438
38.7k
int HunspellImpl::is_keepcase(const hentry* rv) {
439
38.7k
  return pAMgr && rv->astr && pAMgr->get_keepcase() &&
440
8.63k
         TESTAFF(rv->astr, pAMgr->get_keepcase(), rv->alen);
441
38.7k
}
442
443
/* insert a word to the beginning of the suggestion array */
444
413k
void HunspellImpl::insert_sug(std::vector<std::string>& slst, const std::string& word) {
445
413k
  slst.insert(slst.begin(), word);
446
413k
}
447
448
bool HunspellImpl::spell(const std::string& word, std::vector<std::string>& candidate_stack,
449
                         int* info, std::string* root,
450
17.1M
                         std::chrono::steady_clock::time_point suggest_start) {
451
  // something very broken if spell ends up calling itself with the same word
452
17.1M
  if (std::find(candidate_stack.begin(), candidate_stack.end(), word) != candidate_stack.end())
453
0
    return false;
454
455
17.1M
  if (candidate_stack.size() >= MAXBREAKDEPTH)
456
2.36M
    return false;
457
458
14.7M
  if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
459
5.01k
    return false;
460
461
14.7M
  candidate_stack.push_back(word);
462
14.7M
  bool r = spell_internal(word, candidate_stack, info, root, suggest_start);
463
14.7M
  candidate_stack.pop_back();
464
465
14.7M
  if (r && root) {
466
    // output conversion
467
0
    RepList* rl = (pAMgr) ? pAMgr->get_oconvtable() : nullptr;
468
0
    if (rl) {
469
0
      std::string wspace;
470
0
      if (rl->conv(*root, wspace)) {
471
0
        *root = std::move(wspace);
472
0
      }
473
0
    }
474
0
  }
475
14.7M
  return r;
476
14.7M
}
477
478
bool HunspellImpl::spell_internal(const std::string& word, std::vector<std::string>& candidate_stack,
479
                                  int* info, std::string* root,
480
14.7M
                                  std::chrono::steady_clock::time_point suggest_start) {
481
14.7M
  struct hentry* rv = nullptr;
482
483
14.7M
  int info2 = 0;
484
14.7M
  if (!info)
485
14.7M
    info = &info2;
486
5.49k
  else
487
5.49k
    *info = 0;
488
489
  // Hunspell supports XML input of the simplified API (see manual)
490
14.7M
  if (word == SPELL_XML)
491
139k
    return true;
492
14.6M
  if (utf8) {
493
3.87M
    if (word.size() >= MAXWORDUTF8LEN)
494
8.24k
      return false;
495
10.7M
  } else {
496
10.7M
    if (word.size() >= MAXWORDLEN)
497
22.4k
      return false;
498
10.7M
  }
499
14.5M
  int captype = NOCAP;
500
14.5M
  size_t abbv = 0;
501
14.5M
  size_t wl = 0;
502
503
14.5M
  std::string scw;
504
14.5M
  std::vector<w_char> sunicw;
505
506
  // input conversion
507
14.5M
  RepList* rl = pAMgr ? pAMgr->get_iconvtable() : nullptr;
508
14.5M
  {
509
14.5M
    std::string wspace;
510
511
14.5M
    bool convstatus = rl ? rl->conv(word, wspace) : false;
512
14.5M
    if (convstatus)
513
81.1k
      wl = cleanword2(scw, sunicw, wspace, &captype, &abbv);
514
14.4M
    else
515
14.4M
      wl = cleanword2(scw, sunicw, word, &captype, &abbv);
516
14.5M
  }
517
518
14.5M
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
519
14.5M
    if (wl > 32768)
520
307
      return false;
521
14.5M
#endif
522
523
#ifdef MOZILLA_CLIENT
524
  // accept the abbreviated words without dots
525
  // workaround for the incomplete tokenization of Mozilla
526
  abbv = 1;
527
#endif
528
529
14.5M
  if (wl == 0 || m_HMgrs.empty())
530
470k
    return true;
531
14.1M
  if (root)
532
0
    root->clear();
533
534
  // allow numbers with dots, dashes and commas (but forbid double separators:
535
  // "..", "--" etc.)
536
14.1M
  enum { NBEGIN, NNUM, NSEP };
537
14.1M
  int nstate = NBEGIN;
538
14.1M
  size_t i;
539
540
19.9M
  for (i = 0; (i < wl); i++) {
541
19.0M
    if ((scw[i] <= '9') && (scw[i] >= '0')) {
542
5.38M
      nstate = NNUM;
543
13.7M
    } else if ((scw[i] == ',') || (scw[i] == '.') || (scw[i] == '-')) {
544
3.36M
      if ((nstate == NSEP) || (i == 0))
545
2.85M
        break;
546
509k
      nstate = NSEP;
547
509k
    } else
548
10.3M
      break;
549
19.0M
  }
550
14.1M
  if ((i == wl) && (nstate == NNUM))
551
708k
    return true;
552
553
13.3M
  switch (captype) {
554
1.10M
    case HUHCAP:
555
    /* FALLTHROUGH */
556
1.46M
    case HUHINITCAP:
557
1.46M
      *info |= SPELL_ORIGCAP;
558
    /* FALLTHROUGH */
559
12.4M
    case NOCAP:
560
12.4M
      rv = checkword(scw, info, root, suggest_start);
561
12.4M
      if ((abbv) && !(rv)) {
562
929k
        std::string u8buffer(scw);
563
929k
        u8buffer.push_back('.');
564
929k
        rv = checkword(u8buffer, info, root, suggest_start);
565
929k
      }
566
12.4M
      break;
567
371k
    case ALLCAP: {
568
371k
      *info |= SPELL_ORIGCAP;
569
371k
      rv = checkword(scw, info, root, suggest_start);
570
371k
      if (rv)
571
11.4k
        break;
572
359k
      if (abbv) {
573
30.3k
        std::string u8buffer(scw);
574
30.3k
        u8buffer.push_back('.');
575
30.3k
        rv = checkword(u8buffer, info, root, suggest_start);
576
30.3k
        if (rv)
577
414
          break;
578
30.3k
      }
579
      // Spec. prefix handling for Catalan, French, Italian:
580
      // prefixes separated by apostrophe (SANT'ELIA -> Sant'+Elia).
581
359k
      size_t apos = pAMgr ? scw.find('\'') : std::string::npos;
582
359k
      if (apos != std::string::npos) {
583
98.3k
        mkallsmall2(scw, sunicw);
584
        //conversion may result in string with different len to pre-mkallsmall2
585
        //so re-scan
586
98.3k
        if (apos != std::string::npos && apos < scw.size() - 1) {
587
86.7k
          std::string part1 = scw.substr(0, apos + 1), part2 = scw.substr(apos + 1);
588
86.7k
          if (utf8) {
589
14.9k
            std::vector<w_char> part1u, part2u;
590
14.9k
            u8_u16(part1u, part1);
591
14.9k
            u8_u16(part2u, part2);
592
14.9k
            mkinitcap2(part2, part2u);
593
14.9k
            scw = part1 + part2;
594
14.9k
            sunicw = std::move(part1u);
595
14.9k
            sunicw.insert(sunicw.end(), part2u.begin(), part2u.end());
596
14.9k
            rv = checkword(scw, info, root, suggest_start);
597
14.9k
            if (rv)
598
442
              break;
599
71.7k
          } else {
600
71.7k
            mkinitcap2(part2, sunicw);
601
71.7k
            scw = part1 + part2;
602
71.7k
            rv = checkword(scw, info, root, suggest_start);
603
71.7k
            if (rv)
604
769
              break;
605
71.7k
          }
606
85.5k
          mkinitcap2(scw, sunicw);
607
85.5k
          rv = checkword(scw, info, root, suggest_start);
608
85.5k
          if (rv)
609
1.07k
            break;
610
85.5k
        }
611
98.3k
      }
612
356k
      if (pAMgr && pAMgr->get_checksharps() && scw.find("SS") != std::string::npos) {
613
614
24.3k
        mkallsmall2(scw, sunicw);
615
24.3k
        std::string u8buffer(scw);
616
24.3k
        rv = spellsharps(u8buffer, 0, 0, 0, info, root, suggest_start);
617
24.3k
        if (!rv) {
618
22.9k
          mkinitcap2(scw, sunicw);
619
22.9k
          rv = spellsharps(scw, 0, 0, 0, info, root, suggest_start);
620
22.9k
        }
621
24.3k
        if ((abbv) && !(rv)) {
622
469
          u8buffer.push_back('.');
623
469
          rv = spellsharps(u8buffer, 0, 0, 0, info, root, suggest_start);
624
469
          if (!rv) {
625
385
            u8buffer = std::string(scw);
626
385
            u8buffer.push_back('.');
627
385
            rv = spellsharps(u8buffer, 0, 0, 0, info, root, suggest_start);
628
385
          }
629
469
        }
630
24.3k
        if (rv)
631
2.04k
          break;
632
24.3k
      }
633
356k
    }
634
      /* FALLTHROUGH */
635
950k
    case INITCAP: {
636
      // handle special capitalization of dotted I
637
950k
      bool Idot = (utf8 && (unsigned char) scw[0] == 0xc4 && (unsigned char) scw[1] == 0xb0);
638
950k
      *info |= SPELL_ORIGCAP;
639
950k
      if (captype == ALLCAP) {
640
354k
          mkallsmall2(scw, sunicw);
641
354k
          mkinitcap2(scw, sunicw);
642
354k
          if (Idot)
643
2.24k
             scw.replace(0, 1, "\xc4\xb0");
644
354k
      }
645
950k
      if (captype == INITCAP)
646
596k
        *info |= SPELL_INITCAP;
647
950k
      rv = checkword(scw, info, root, suggest_start);
648
950k
      if (captype == INITCAP)
649
596k
        *info &= ~SPELL_INITCAP;
650
      // forbid bad capitalization
651
      // (for example, ijs -> Ijs instead of IJs in Dutch)
652
      // use explicit forms in dic: Ijs/F (F = FORBIDDENWORD flag)
653
950k
      if (*info & SPELL_FORBIDDEN) {
654
2.42k
        rv = nullptr;
655
2.42k
        break;
656
2.42k
      }
657
948k
      if (rv && is_keepcase(rv) && (captype == ALLCAP))
658
1.23k
        rv = nullptr;
659
948k
      if (rv || (Idot && langnum != LANG_az && langnum != LANG_tr && langnum != LANG_crh))
660
21.8k
        break;
661
662
926k
      mkallsmall2(scw, sunicw);
663
926k
      std::string u8buffer(scw);
664
926k
      mkinitcap2(scw, sunicw);
665
666
926k
      rv = checkword(u8buffer, info, root, suggest_start);
667
926k
      if (abbv && !rv) {
668
113k
        u8buffer.push_back('.');
669
113k
        rv = checkword(u8buffer, info, root, suggest_start);
670
113k
        if (!rv) {
671
113k
          u8buffer = scw;
672
113k
          u8buffer.push_back('.');
673
113k
          if (captype == INITCAP)
674
83.6k
            *info |= SPELL_INITCAP;
675
113k
          rv = checkword(u8buffer, info, root, suggest_start);
676
113k
          if (captype == INITCAP)
677
83.6k
            *info &= ~SPELL_INITCAP;
678
113k
          if (rv && is_keepcase(rv) && (captype == ALLCAP))
679
40
            rv = nullptr;
680
113k
          break;
681
113k
        }
682
113k
      }
683
813k
      if (rv && is_keepcase(rv) &&
684
4.84k
          ((captype == ALLCAP) ||
685
           // if CHECKSHARPS: KEEPCASE words with \xDF  are allowed
686
           // in INITCAP form, too.
687
2.15k
           !(pAMgr->get_checksharps() &&
688
1.45k
             ((utf8 && u8buffer.find("\xC3\x9F") != std::string::npos) ||
689
1.45k
              (!utf8 && u8buffer.find('\xDF') != std::string::npos)))))
690
4.26k
        rv = nullptr;
691
813k
      break;
692
926k
    }
693
13.3M
  }
694
695
13.3M
  if (rv) {
696
354k
    if (pAMgr && pAMgr->get_warn() && rv->astr &&
697
12.7k
        TESTAFF(rv->astr, pAMgr->get_warn(), rv->alen)) {
698
1.42k
      *info |= SPELL_WARN;
699
1.42k
      if (pAMgr->get_forbidwarn())
700
159
        return false;
701
1.26k
      return true;
702
1.42k
    }
703
352k
    return true;
704
354k
  }
705
706
  // recursive breaking at break points
707
13.0M
  if (!wordbreak.empty() && !(*info & SPELL_FORBIDDEN)) {
708
709
12.9M
    int nbr = 0;
710
12.9M
    wl = scw.size();
711
712
    // calculate break points for recursion limit
713
41.0M
    for (auto& j : wordbreak) {
714
41.0M
      size_t pos = 0;
715
54.2M
      while ((pos = scw.find(j, pos)) != std::string::npos) {
716
13.1M
        ++nbr;
717
13.1M
        pos += j.size();
718
13.1M
      }
719
41.0M
    }
720
12.9M
    if (nbr >= MAXBREAKDEPTH)
721
3.43k
      return false;
722
723
    // check boundary patterns (^begin and end$)
724
40.8M
    for (auto& j : wordbreak) {
725
40.8M
      size_t plen = j.size();
726
40.8M
      if (plen == 1 || plen > wl)
727
18.0M
        continue;
728
729
22.8M
      if (j[0] == '^' && scw.compare(0, plen - 1, j, 1, plen - 1) == 0 &&
730
3.62M
          spell(scw.substr(plen - 1), candidate_stack, nullptr, nullptr, suggest_start)) {
731
142k
        *info |= SPELL_COMPOUND;
732
142k
        return true;
733
142k
      }
734
735
22.6M
      if (j[plen - 1] == '$' &&
736
9.80M
          scw.compare(wl - plen + 1, plen - 1, j, 0, plen - 1) == 0) {
737
3.57M
        std::string suffix(scw.substr(wl - plen + 1));
738
3.57M
        scw.resize(wl - plen + 1);
739
3.57M
        if (spell(scw, candidate_stack, nullptr, nullptr, suggest_start)) {
740
751k
          *info |= SPELL_COMPOUND;
741
751k
          return true;
742
751k
        }
743
2.82M
        scw.append(suffix);
744
2.82M
      }
745
22.6M
    }
746
747
    // other patterns
748
38.2M
    for (auto& j : wordbreak) {
749
38.2M
      size_t plen = j.size();
750
38.2M
      size_t found = scw.find(j);
751
38.2M
      if ((found > 0) && (found < wl - plen)) {
752
3.88M
        size_t found2 = scw.find(j, found + 1);
753
        // try to break at the second occurance
754
        // to recognize dictionary words with wordbreak
755
3.88M
        if (found2 > 0 && (found2 < wl - plen))
756
1.34M
            found = found2;
757
3.88M
        std::string substring(scw.substr(found + plen));
758
3.88M
        if (!spell(substring, candidate_stack, nullptr, nullptr, suggest_start))
759
3.05M
          continue;
760
828k
        std::string suffix(scw.substr(found));
761
828k
        scw.resize(found);
762
        // examine 2 sides of the break point
763
828k
        if (spell(scw, candidate_stack, nullptr, nullptr, suggest_start)) {
764
55.5k
          *info |= SPELL_COMPOUND;
765
55.5k
          return true;
766
55.5k
        }
767
772k
        scw.append(suffix);
768
769
        // LANG_hu: spec. dash rule
770
772k
        if (langnum == LANG_hu && j == "-") {
771
88.1k
          suffix = scw.substr(found + 1);
772
88.1k
          scw.resize(found + 1);
773
88.1k
          if (spell(scw, candidate_stack, nullptr, nullptr, suggest_start)) {
774
7.81k
            *info |= SPELL_COMPOUND;
775
7.81k
            return true;  // check the first part with dash
776
7.81k
          }
777
80.3k
          scw.append(suffix);
778
80.3k
        }
779
        // end of LANG specific region
780
772k
      }
781
38.2M
    }
782
783
    // other patterns (break at first break point)
784
38.1M
    for (auto& j : wordbreak) {
785
38.1M
      size_t plen = j.size(), found = scw.find(j);
786
38.1M
      if ((found > 0) && (found < wl - plen)) {
787
3.79M
        if (!spell(scw.substr(found + plen), candidate_stack, nullptr, nullptr, suggest_start))
788
3.14M
          continue;
789
650k
        std::string suffix(scw.substr(found));
790
650k
        scw.resize(found);
791
        // examine 2 sides of the break point
792
650k
        if (spell(scw, candidate_stack, nullptr, nullptr, suggest_start)) {
793
7.71k
          *info |= SPELL_COMPOUND;
794
7.71k
          return true;
795
7.71k
        }
796
643k
        scw.append(suffix);
797
798
        // LANG_hu: spec. dash rule
799
643k
        if (langnum == LANG_hu && j == "-") {
800
78.1k
          suffix = scw.substr(found + 1);
801
78.1k
          scw.resize(found + 1);
802
78.1k
          if (spell(scw, candidate_stack, nullptr, nullptr, suggest_start)) {
803
1.45k
            *info |= SPELL_COMPOUND;
804
1.45k
            return true;  // check the first part with dash
805
1.45k
          }
806
76.6k
          scw.append(suffix);
807
76.6k
        }
808
        // end of LANG specific region
809
643k
      }
810
38.1M
    }
811
11.9M
  }
812
813
12.0M
  return false;
814
13.0M
}
815
816
struct hentry* HunspellImpl::checkword(const std::string& w, int* info, std::string* root,
817
16.5M
                                       std::chrono::steady_clock::time_point suggest_start) {
818
  // check overall suggest time limit
819
16.5M
  if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
820
485
    return nullptr;
821
822
16.5M
  std::string word;
823
824
  // remove IGNORE characters from the string
825
16.5M
  clean_ignore(word, w);
826
827
16.5M
  if (word.empty())
828
1.56k
    return nullptr;
829
830
  // word reversing wrapper for complex prefixes
831
16.5M
  if (complexprefixes) {
832
3.97M
    if (utf8)
833
157k
      reverseword_utf(word);
834
3.81M
    else
835
3.81M
      reverseword(word);
836
3.97M
  }
837
838
16.5M
  int len = word.size();
839
840
  // look word in hash table
841
16.5M
  struct hentry* he = nullptr;
842
33.1M
  for (size_t i = 0; (i < m_HMgrs.size()) && !he; ++i) {
843
16.5M
    he = m_HMgrs[i]->lookup(word.c_str(), word.size());
844
845
    // check forbidden and onlyincompound words
846
16.5M
    if ((he) && (he->astr) && (pAMgr) &&
847
48.5k
        TESTAFF(he->astr, pAMgr->get_forbiddenword(), he->alen)) {
848
15.4k
      if (info)
849
15.4k
        *info |= SPELL_FORBIDDEN;
850
      // LANG_hu section: set dash information for suggestions
851
15.4k
      if (langnum == LANG_hu) {
852
9.22k
        if (pAMgr->get_compoundflag() &&
853
5.24k
            TESTAFF(he->astr, pAMgr->get_compoundflag(), he->alen)) {
854
1.52k
          if (info)
855
1.52k
            *info |= SPELL_COMPOUND;
856
1.52k
        }
857
9.22k
      }
858
15.4k
      return nullptr;
859
15.4k
    }
860
861
    // he = next not needaffix, onlyincompound homonym or onlyupcase word
862
16.5M
    while (he && (he->astr) && pAMgr &&
863
34.2k
           ((pAMgr->get_needaffix() &&
864
12.9k
             TESTAFF(he->astr, pAMgr->get_needaffix(), he->alen)) ||
865
31.9k
            (pAMgr->get_onlyincompound() &&
866
5.35k
             TESTAFF(he->astr, pAMgr->get_onlyincompound(), he->alen)) ||
867
30.6k
            (info && (*info & SPELL_INITCAP) &&
868
4.83k
             TESTAFF(he->astr, ONLYUPCASEFLAG, he->alen))))
869
5.66k
      he = he->next_homonym;
870
16.5M
  }
871
872
  // check with affixes
873
16.5M
  if (!he && pAMgr) {
874
    // try stripping off affixes
875
16.3M
    AffixScratch scratch;
876
16.3M
    he = pAMgr->affix_check(word, 0, len, scratch, 0);
877
878
    // check compound restriction and onlyupcase
879
16.3M
    if (he && he->astr &&
880
86.2k
        ((pAMgr->get_onlyincompound() &&
881
7.70k
          TESTAFF(he->astr, pAMgr->get_onlyincompound(), he->alen)) ||
882
85.3k
         (info && (*info & SPELL_INITCAP) &&
883
3.64k
          TESTAFF(he->astr, ONLYUPCASEFLAG, he->alen)))) {
884
1.39k
      he = nullptr;
885
1.39k
    }
886
887
16.3M
    if (he) {
888
87.1k
      if ((he->astr) && (pAMgr) &&
889
84.8k
          TESTAFF(he->astr, pAMgr->get_forbiddenword(), he->alen)) {
890
2.00k
        if (info)
891
2.00k
          *info |= SPELL_FORBIDDEN;
892
2.00k
        return nullptr;
893
2.00k
      }
894
85.0k
      if (root) {
895
0
        root->assign(he->word);
896
0
        if (complexprefixes) {
897
0
          if (utf8)
898
0
            reverseword_utf(*root);
899
0
          else
900
0
            reverseword(*root);
901
0
        }
902
0
      }
903
      // try check compound word
904
16.2M
    } else if (pAMgr->get_compound()) {
905
3.43M
      struct hentry* rwords[100] = {};  // buffer for COMPOUND pattern checking
906
907
      // first allow only 2 words in the compound
908
3.43M
      int setinfo = SPELL_COMPOUND_2;
909
3.43M
      if (info)
910
3.43M
        setinfo |= *info;
911
3.43M
      he = pAMgr->compound_check(word, 0, 0, 100, 0, nullptr, (hentry**)&rwords, 0, 0, &setinfo, scratch);
912
3.43M
      if (info)
913
3.43M
        *info = setinfo & ~SPELL_COMPOUND_2;
914
      // if not 2-word compoud word, try with 3 or more words
915
      // (only if original info didn't forbid it)
916
3.43M
      if (!he && info && !(*info & SPELL_COMPOUND_2)) {
917
3.43M
        *info &= ~SPELL_COMPOUND_2;
918
3.43M
        he = pAMgr->compound_check(word, 0, 0, 100, 0, nullptr, (hentry**)&rwords, 0, 0, info, scratch);
919
        // accept the compound with 3 or more words only if it is
920
        // - not a dictionary word with a typo and
921
        // - not two words written separately,
922
        // - or if it's an arbitrary number accepted by compound rules (e.g. 999%)
923
3.43M
        if (he && !isdigit(word[0]))
924
32.1k
        {
925
32.1k
          std::vector<std::string> slst;
926
32.1k
          if (pSMgr->suggest(slst, word, nullptr, /*test_simplesug=*/true))
927
24.2k
            he = nullptr;
928
32.1k
        }
929
3.43M
      }
930
931
      // LANG_hu section: `moving rule' with last dash
932
3.43M
      if ((!he) && (langnum == LANG_hu) && (word[len - 1] == '-')) {
933
95.9k
        std::string dup(word, 0, len - 1);
934
95.9k
        he = pAMgr->compound_check(dup, -5, 0, 100, 0, nullptr, (hentry**)&rwords, 1, 0, info, scratch);
935
95.9k
      }
936
      // end of LANG specific region
937
3.43M
      if (he) {
938
13.7k
        if (root) {
939
0
          root->assign(he->word);
940
0
          if (complexprefixes) {
941
0
            if (utf8)
942
0
              reverseword_utf(*root);
943
0
            else
944
0
              reverseword(*root);
945
0
          }
946
0
        }
947
13.7k
        if (info)
948
13.7k
          *info |= SPELL_COMPOUND;
949
13.7k
      }
950
3.43M
    }
951
16.3M
  }
952
953
16.5M
  return he;
954
16.5M
}
955
956
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
957
360k
#define MAX_CANDIDATE_STACK_DEPTH 512
958
#else
959
#define MAX_CANDIDATE_STACK_DEPTH 2048
960
#endif
961
962
180k
std::vector<std::string> HunspellImpl::suggest(const std::string& word, std::vector<std::string>& suggest_candidate_stack, std::chrono::steady_clock::time_point suggest_start) {
963
964
180k
  if (suggest_candidate_stack.size() > MAX_CANDIDATE_STACK_DEPTH || // apply a fairly arbitrary depth limit
965
      // something very broken if suggest ends up calling itself with the same word
966
180k
      std::find(suggest_candidate_stack.begin(), suggest_candidate_stack.end(), word) != suggest_candidate_stack.end()) {
967
72
    return { };
968
72
  }
969
970
180k
  if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
971
3
    return { };
972
973
180k
  bool capwords;
974
180k
  size_t abbv;
975
180k
  int captype;
976
180k
  std::vector<std::string> spell_candidate_stack;
977
180k
  suggest_candidate_stack.push_back(word);
978
180k
  std::vector<std::string> slst = suggest_internal(word, spell_candidate_stack, suggest_candidate_stack,
979
180k
                                                   capwords, abbv, captype, suggest_start);
980
180k
  suggest_candidate_stack.pop_back();
981
  // word reversing wrapper for complex prefixes
982
180k
  if (complexprefixes) {
983
88.5k
    for (auto& j : slst) {
984
88.5k
      if (utf8)
985
69.5k
        reverseword_utf(j);
986
19.0k
      else
987
19.0k
        reverseword(j);
988
88.5k
    }
989
18.8k
  }
990
991
  // capitalize
992
180k
  if (capwords) {
993
32.9k
    for (auto& j : slst) {
994
22.7k
      std::string capitalized(j);
995
22.7k
      mkinitcap(capitalized);
996
22.7k
      if (capitalized == word)
997
71
        continue;  // capitalizing would just reproduce the misspelled word
998
22.7k
      j = std::move(capitalized);
999
22.7k
    }
1000
32.9k
  }
1001
1002
  // expand suggestions with dot(s)
1003
180k
  if (abbv && pAMgr && pAMgr->get_sugswithdots() && word.size() >= abbv) {
1004
33.3k
    for (auto& j : slst) {
1005
33.3k
      j.append(word.substr(word.size() - abbv));
1006
33.3k
    }
1007
1.06k
  }
1008
1009
  // remove bad capitalized and forbidden forms
1010
180k
  if (pAMgr && (pAMgr->get_keepcase() || pAMgr->get_forbiddenword())) {
1011
179k
    switch (captype) {
1012
16.3k
      case INITCAP:
1013
38.2k
      case ALLCAP: {
1014
38.2k
        size_t l = 0;
1015
76.5k
        for (size_t j = 0; j < slst.size(); ++j) {
1016
38.3k
          if (slst[j].find(' ') == std::string::npos && !spell(slst[j], spell_candidate_stack, nullptr, nullptr, suggest_start)) {
1017
15.6k
            std::string s;
1018
15.6k
            std::vector<w_char> w;
1019
15.6k
            if (utf8) {
1020
12.9k
              u8_u16(w, slst[j]);
1021
12.9k
            } else {
1022
2.62k
              s = slst[j];
1023
2.62k
            }
1024
15.6k
            mkallsmall2(s, w);
1025
15.6k
            if (spell(s, spell_candidate_stack, nullptr, nullptr, suggest_start)) {
1026
1.65k
              slst[l] = std::move(s);
1027
1.65k
              ++l;
1028
13.9k
            } else {
1029
13.9k
              mkinitcap2(s, w);
1030
13.9k
              if (spell(s, spell_candidate_stack, nullptr, nullptr, suggest_start)) {
1031
628
                slst[l] = std::move(s);
1032
628
                ++l;
1033
628
              }
1034
13.9k
            }
1035
22.7k
          } else {
1036
22.7k
            slst[l] = slst[j];
1037
22.7k
            ++l;
1038
22.7k
          }
1039
38.3k
        }
1040
38.2k
        slst.resize(l);
1041
38.2k
      }
1042
179k
    }
1043
179k
  }
1044
1045
  // remove duplications
1046
180k
  size_t l = 0;
1047
671k
  for (size_t j = 0; j < slst.size(); ++j) {
1048
491k
    slst[l] = slst[j];
1049
23.1M
    for (size_t k = 0; k < l; ++k) {
1050
22.6M
      if (slst[k] == slst[j]) {
1051
17.9k
        --l;
1052
17.9k
        break;
1053
17.9k
      }
1054
22.6M
    }
1055
491k
    ++l;
1056
491k
  }
1057
180k
  slst.resize(l);
1058
1059
  // output conversion
1060
180k
  RepList* rl = (pAMgr) ? pAMgr->get_oconvtable() : nullptr;
1061
180k
  if (rl) {
1062
6.92k
    size_t l = 0;
1063
11.2k
    for (size_t i = 0; i < slst.size(); ++i) {
1064
4.30k
      std::string wspace;
1065
4.30k
      if (rl->conv(slst[i], wspace)) {
1066
1.54k
        slst[i] = std::move(wspace);
1067
1.54k
      }
1068
      // gh#1002: OCONV can map a generated form back to the input word
1069
      // (e.g. "románórum" -> "romanórum" when the user typed "romanórum"),
1070
      // leaving the misspelled word as its own suggestion.
1071
4.30k
      if (slst[i] == word)
1072
3
        continue;
1073
4.30k
      if (l != i)
1074
11
        slst[l] = std::move(slst[i]);
1075
4.30k
      ++l;
1076
4.30k
    }
1077
6.92k
    slst.resize(l);
1078
6.92k
  }
1079
180k
  return slst;
1080
180k
}
1081
1082
80.3k
std::vector<std::string> HunspellImpl::suggest(const std::string& word) {
1083
80.3k
  std::vector<std::string> suggest_candidate_stack;
1084
80.3k
  auto suggest_start = std::chrono::steady_clock::now();
1085
80.3k
  return suggest(word, suggest_candidate_stack, suggest_start);
1086
80.3k
}
1087
1088
std::vector<std::string> HunspellImpl::suggest_internal(const std::string& word,
1089
        std::vector<std::string>& spell_candidate_stack,
1090
        std::vector<std::string>& suggest_candidate_stack,
1091
        bool& capwords, size_t& abbv, int& captype,
1092
180k
        std::chrono::steady_clock::time_point suggest_start) {
1093
180k
  captype = NOCAP;
1094
180k
  abbv = 0;
1095
180k
  capwords = false;
1096
1097
180k
  std::vector<std::string> slst;
1098
1099
180k
  int onlycmpdsug = 0;
1100
180k
  if (!pSMgr || m_HMgrs.empty())
1101
0
    return slst;
1102
1103
  // process XML input of the simplified API (see manual)
1104
180k
  if (word.compare(0, sizeof(SPELL_XML) - 3, SPELL_XML, sizeof(SPELL_XML) - 3) == 0) {
1105
25.1k
    if (word.size() > MAXSPELLMLLEN)
1106
553
      return slst;
1107
24.6k
    return spellml(word);
1108
25.1k
  }
1109
155k
  if (utf8) {
1110
90.6k
    if (word.size() >= MAXWORDUTF8LEN)
1111
409
      return slst;
1112
90.6k
  } else {
1113
64.5k
    if (word.size() >= MAXWORDLEN)
1114
413
      return slst;
1115
64.5k
  }
1116
154k
  size_t wl = 0;
1117
1118
154k
  std::string scw;
1119
154k
  std::vector<w_char> sunicw;
1120
1121
  // input conversion
1122
154k
  RepList* rl = (pAMgr) ? pAMgr->get_iconvtable() : nullptr;
1123
154k
  {
1124
154k
    std::string wspace;
1125
1126
154k
    bool convstatus = rl ? rl->conv(word, wspace) : false;
1127
154k
    if (convstatus)
1128
64.7k
      wl = cleanword2(scw, sunicw, wspace, &captype, &abbv);
1129
89.6k
    else
1130
89.6k
      wl = cleanword2(scw, sunicw, word, &captype, &abbv);
1131
1132
154k
    if (wl == 0)
1133
0
      return slst;
1134
1135
154k
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
1136
154k
    if (wl > 32768)
1137
14
      return slst;
1138
154k
#endif
1139
154k
  }
1140
1141
154k
  bool good = false;
1142
1143
  // check capitalized form for FORCEUCASE
1144
154k
  if (pAMgr && captype == NOCAP && pAMgr->get_forceucase()) {
1145
656
    int info = SPELL_ORIGCAP;
1146
656
    if (checkword(scw, &info, nullptr, suggest_start)) {
1147
2
      std::string form(std::move(scw));
1148
2
      mkinitcap(form);
1149
2
      slst.push_back(std::move(form));
1150
2
      return slst;
1151
2
    }
1152
656
  }
1153
1154
154k
  switch (captype) {
1155
72.7k
    case NOCAP: {
1156
72.7k
      good |= pSMgr->suggest(slst, scw, &onlycmpdsug);
1157
72.7k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1158
23
          return slst;
1159
72.7k
      if (abbv) {
1160
3.88k
        std::string wspace(scw);
1161
3.88k
        wspace.push_back('.');
1162
3.88k
        good |= pSMgr->suggest(slst, wspace, &onlycmpdsug);
1163
3.88k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1164
1
            return slst;
1165
3.88k
      }
1166
72.7k
      break;
1167
72.7k
    }
1168
1169
72.7k
    case INITCAP: {
1170
16.3k
      capwords = true;
1171
16.3k
      good |= pSMgr->suggest(slst, scw, &onlycmpdsug);
1172
16.3k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1173
0
          return slst;
1174
16.3k
      std::string wspace(scw);
1175
16.3k
      mkallsmall2(wspace, sunicw);
1176
16.3k
      good |= pSMgr->suggest(slst, wspace, &onlycmpdsug);
1177
16.3k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1178
3
          return slst;
1179
16.3k
      break;
1180
16.3k
    }
1181
16.6k
    case HUHINITCAP:
1182
16.6k
      capwords = true;
1183
      /* FALLTHROUGH */
1184
43.3k
    case HUHCAP: {
1185
43.3k
      good |= pSMgr->suggest(slst, scw, &onlycmpdsug);
1186
43.3k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1187
21
          return slst;
1188
      // something.The -> something. The
1189
43.3k
      size_t dot_pos = scw.find('.');
1190
43.3k
      if (dot_pos != std::string::npos) {
1191
11.0k
        std::string postdot = scw.substr(dot_pos + 1);
1192
11.0k
        int captype_;
1193
11.0k
        if (utf8) {
1194
2.97k
          std::vector<w_char> postdotu;
1195
2.97k
          u8_u16(postdotu, postdot);
1196
2.97k
          captype_ = get_captype_utf8(postdotu, langnum);
1197
8.03k
        } else {
1198
8.03k
          captype_ = get_captype(postdot, csconv);
1199
8.03k
        }
1200
11.0k
        if (captype_ == INITCAP) {
1201
2.31k
          std::string str(scw);
1202
2.31k
          str.insert(dot_pos + 1, 1, ' ');
1203
2.31k
          insert_sug(slst, str);
1204
2.31k
        }
1205
11.0k
      }
1206
1207
43.3k
      std::string wspace;
1208
1209
43.3k
      if (captype == HUHINITCAP) {
1210
        // TheOpenOffice.org -> The OpenOffice.org
1211
16.6k
        wspace = scw;
1212
16.6k
        mkinitsmall2(wspace, sunicw);
1213
16.6k
        good |= pSMgr->suggest(slst, wspace, &onlycmpdsug);
1214
16.6k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1215
11
            return slst;
1216
16.6k
      }
1217
43.3k
      wspace = scw;
1218
43.3k
      mkallsmall2(wspace, sunicw);
1219
43.3k
      if (spell(wspace, spell_candidate_stack, nullptr, nullptr, suggest_start))
1220
945
        insert_sug(slst, wspace);
1221
43.3k
      size_t prevns = slst.size();
1222
43.3k
      good |= pSMgr->suggest(slst, wspace, &onlycmpdsug);
1223
43.3k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1224
71
          return slst;
1225
43.2k
      if (captype == HUHINITCAP) {
1226
16.5k
        mkinitcap2(wspace, sunicw);
1227
16.5k
        if (spell(wspace, spell_candidate_stack, nullptr, nullptr, suggest_start))
1228
320
          insert_sug(slst, wspace);
1229
16.5k
        good |= pSMgr->suggest(slst, wspace, &onlycmpdsug);
1230
16.5k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1231
6
            return slst;
1232
16.5k
      }
1233
      // aNew -> "a New" (instead of "a new")
1234
51.5k
      for (size_t j = prevns; j < slst.size(); ++j) {
1235
8.25k
        const char* space = strchr(slst[j].c_str(), ' ');
1236
8.25k
        if (space) {
1237
1.97k
          size_t slen = strlen(space + 1);
1238
          // different case after space (need capitalisation)
1239
1.97k
          if ((slen < wl) && strcmp(scw.c_str() + wl - slen, space + 1) != 0) {
1240
1.65k
            std::string first(slst[j].c_str(), space + 1);
1241
1.65k
            std::string second(space + 1);
1242
1.65k
            std::vector<w_char> w;
1243
1.65k
            if (utf8)
1244
836
              u8_u16(w, second);
1245
1.65k
            mkinitcap2(second, w);
1246
            // set as first suggestion
1247
1.65k
            slst.erase(slst.begin() + j);
1248
1.65k
            slst.insert(slst.begin(), first + second);
1249
1.65k
          }
1250
1.97k
        }
1251
8.25k
      }
1252
43.2k
      break;
1253
43.2k
    }
1254
1255
21.9k
    case ALLCAP: {
1256
21.9k
      std::string wspace(scw);
1257
21.9k
      mkallsmall2(wspace, sunicw);
1258
21.9k
      good |= pSMgr->suggest(slst, wspace, &onlycmpdsug);
1259
21.9k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1260
11
          return slst;
1261
21.9k
      if (pAMgr && pAMgr->get_keepcase() && spell(wspace, spell_candidate_stack, nullptr, nullptr, suggest_start))
1262
37
        insert_sug(slst, wspace);
1263
21.9k
      mkinitcap2(wspace, sunicw);
1264
21.9k
      good |= pSMgr->suggest(slst, wspace, &onlycmpdsug);
1265
21.9k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1266
16
          return slst;
1267
21.8k
      for (auto& j : slst) {
1268
9.81k
        mkallcap(j);
1269
9.81k
        if (pAMgr && pAMgr->get_checksharps()) {
1270
769
          if (utf8) {
1271
242
            mystrrep(j, "\xC3\x9F", "SS");
1272
527
          } else {
1273
527
            mystrrep(j, "\xDF", "SS");
1274
527
          }
1275
769
        }
1276
9.81k
      }
1277
21.8k
      break;
1278
21.9k
    }
1279
154k
  }
1280
1281
  // LANG_hu section: replace '-' with ' ' in Hungarian
1282
154k
  if (langnum == LANG_hu) {
1283
7.65k
    for (auto& j : slst) {
1284
7.65k
      size_t pos = j.find('-');
1285
7.65k
      if (pos != std::string::npos) {
1286
5.59k
        int info = 0;
1287
5.59k
        std::string w(j.substr(0, pos));
1288
5.59k
        w.append(j.substr(pos + 1));
1289
5.59k
        (void)spell(w, spell_candidate_stack, &info, nullptr, suggest_start);
1290
5.59k
        if ((info & SPELL_COMPOUND) && (info & SPELL_FORBIDDEN)) {
1291
19
          j[pos] = ' ';
1292
19
        } else
1293
5.57k
          j[pos] = '-';
1294
5.59k
      }
1295
7.65k
    }
1296
6.92k
  }
1297
  // END OF LANG_hu section
1298
  // try ngram approach since found nothing good suggestion
1299
154k
  if (!good && pAMgr && (slst.empty() || onlycmpdsug) && (pAMgr->get_maxngramsugs() != 0)) {
1300
144k
    switch (captype) {
1301
69.4k
      case NOCAP: {
1302
69.4k
        pSMgr->ngsuggest(slst, scw.c_str(), m_HMgrs, NOCAP);
1303
69.4k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1304
93
            return slst;
1305
69.3k
        break;
1306
69.4k
      }
1307
      /* FALLTHROUGH */
1308
69.3k
      case HUHINITCAP:
1309
15.8k
        capwords = true;
1310
      /* FALLTHROUGH */
1311
40.5k
      case HUHCAP: {
1312
40.5k
        std::string wspace(scw);
1313
40.5k
        mkallsmall2(wspace, sunicw);
1314
40.5k
        pSMgr->ngsuggest(slst, wspace.c_str(), m_HMgrs, HUHCAP);
1315
40.5k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1316
6
            return slst;
1317
40.5k
        break;
1318
40.5k
      }
1319
40.5k
      case INITCAP: {
1320
14.4k
        capwords = true;
1321
14.4k
        std::string wspace(scw);
1322
14.4k
        mkallsmall2(wspace, sunicw);
1323
14.4k
        pSMgr->ngsuggest(slst, wspace.c_str(), m_HMgrs, INITCAP);
1324
14.4k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1325
0
            return slst;
1326
14.4k
        break;
1327
14.4k
      }
1328
20.2k
      case ALLCAP: {
1329
20.2k
        std::string wspace(scw);
1330
20.2k
        mkallsmall2(wspace, sunicw);
1331
20.2k
        size_t oldns = slst.size();
1332
20.2k
        pSMgr->ngsuggest(slst, wspace.c_str(), m_HMgrs, ALLCAP);
1333
20.2k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1334
2
            return slst;
1335
25.8k
        for (size_t j = oldns; j < slst.size(); ++j) {
1336
5.59k
          mkallcap(slst[j]);
1337
5.59k
        }
1338
20.2k
        break;
1339
20.2k
      }
1340
144k
    }
1341
144k
  }
1342
1343
  // try dash suggestion (Afo-American -> Afro-American)
1344
  // Note: LibreOffice was modified to treat dashes as word
1345
  // characters to check "scot-free" etc. word forms, but
1346
  // we need to handle suggestions for "Afo-American", etc.,
1347
  // while "Afro-American" is missing from the dictionary.
1348
  // TODO avoid possible overgeneration
1349
154k
  size_t dash_pos = scw.find('-');
1350
154k
  if (dash_pos != std::string::npos) {
1351
105k
    int nodashsug = 1;
1352
164k
    for (size_t j = 0; j < slst.size() && nodashsug == 1; ++j) {
1353
58.8k
      if (slst[j].find('-') != std::string::npos)
1354
4.06k
        nodashsug = 0;
1355
58.8k
    }
1356
1357
105k
    size_t prev_pos = 0;
1358
105k
    bool last = false;
1359
1360
313k
    while (!good && nodashsug && !last) {
1361
230k
      if (dash_pos == scw.size())
1362
59.4k
        last = true;
1363
230k
      std::string chunk = scw.substr(prev_pos, dash_pos - prev_pos);
1364
230k
      if (chunk != word && !spell(chunk, spell_candidate_stack, nullptr, nullptr, suggest_start)) {
1365
100k
        std::vector<std::string> nlst = suggest(chunk, suggest_candidate_stack, suggest_start);
1366
100k
        if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1367
21.9k
            return slst;
1368
487k
        for (auto j = nlst.rbegin(); j != nlst.rend(); ++j) {
1369
409k
          std::string wspace = scw.substr(0, prev_pos);
1370
409k
          wspace.append(*j);
1371
409k
          if (!last) {
1372
183k
            wspace.append("-");
1373
183k
            wspace.append(scw.substr(dash_pos + 1));
1374
183k
          }
1375
409k
          int info = 0;
1376
409k
          if (pAMgr && pAMgr->get_forbiddenword())
1377
409k
            checkword(wspace, &info, nullptr, suggest_start);
1378
409k
          if (!(info & SPELL_FORBIDDEN))
1379
409k
            insert_sug(slst, wspace);
1380
409k
        }
1381
78.1k
        nodashsug = 0;
1382
78.1k
      }
1383
208k
      if (!last) {
1384
169k
        prev_pos = dash_pos + 1;
1385
169k
        dash_pos = scw.find('-', prev_pos);
1386
169k
      }
1387
208k
      if (dash_pos == std::string::npos)
1388
71.2k
        dash_pos = scw.size();
1389
208k
    }
1390
105k
  }
1391
132k
  return slst;
1392
154k
}
1393
1394
0
const std::string& HunspellImpl::get_dict_encoding() const {
1395
0
  return encoding;
1396
0
}
1397
1398
1.28k
std::vector<std::string> HunspellImpl::stem(const std::vector<std::string>& desc) {
1399
1.28k
  std::vector<std::string> slst;
1400
1401
1.28k
  std::string result2;
1402
1.28k
  if (desc.empty())
1403
1.17k
    return slst;
1404
2.74k
  for (const auto& i : desc) {
1405
2.74k
    std::string result;
1406
1407
    // add compound word parts (except the last one)
1408
2.74k
    const char* s = i.c_str();
1409
2.74k
    const char* part = strstr(s, MORPH_PART);
1410
2.74k
    if (part) {
1411
0
      const char* nextpart = strstr(part + 1, MORPH_PART);
1412
0
      while (nextpart) {
1413
0
        std::string field;
1414
0
        copy_field(field, part, MORPH_PART);
1415
0
        result.append(field);
1416
0
        part = nextpart;
1417
0
        nextpart = strstr(part + 1, MORPH_PART);
1418
0
      }
1419
0
      s = part;
1420
0
    }
1421
1422
2.74k
    std::string tok(s);
1423
2.74k
    size_t alt = 0;
1424
794k
    while ((alt = tok.find(" | ", alt)) != std::string::npos) {
1425
791k
      tok[alt + 1] = MSEP_ALT;
1426
791k
    }
1427
2.74k
    std::vector<std::string> pl = line_tok(tok, MSEP_ALT);
1428
794k
    for (auto& k : pl) {
1429
      // add derivational suffixes
1430
794k
      if (k.find(MORPH_DERI_SFX) != std::string::npos) {
1431
        // remove inflectional suffixes
1432
1
        const size_t is = k.find(MORPH_INFL_SFX);
1433
1
        if (is != std::string::npos)
1434
0
          k.resize(is);
1435
1
        std::vector<std::string> singlepl;
1436
1
        singlepl.push_back(k);
1437
1
        std::string sg = pSMgr->suggest_gen(singlepl, k);
1438
1
        if (!sg.empty()) {
1439
1
          std::vector<std::string> gen = line_tok(sg, MSEP_REC);
1440
1
          for (auto& j : gen) {
1441
1
            result2.push_back(MSEP_REC);
1442
1
            result2.append(result);
1443
1
            result2.append(j);
1444
1
          }
1445
1
        }
1446
794k
      } else {
1447
794k
        result2.push_back(MSEP_REC);
1448
794k
        result2.append(result);
1449
794k
        if (k.find(MORPH_SURF_PFX) != std::string::npos) {
1450
1
          std::string field;
1451
1
          copy_field(field, k, MORPH_SURF_PFX);
1452
1
          result2.append(field);
1453
1
        }
1454
794k
        std::string field;
1455
794k
        copy_field(field, k, MORPH_STEM);
1456
794k
        result2.append(field);
1457
794k
      }
1458
794k
    }
1459
2.74k
  }
1460
110
  slst = line_tok(result2, MSEP_REC);
1461
110
  uniqlist(slst);
1462
110
  return slst;
1463
1.28k
}
1464
1465
1.28k
std::vector<std::string> HunspellImpl::stem(const std::string& word) {
1466
1.28k
  return stem(analyze(word));
1467
1.28k
}
1468
1469
0
const std::string& HunspellImpl::get_wordchars_cpp() const {
1470
0
  return pAMgr->get_wordchars();
1471
0
}
1472
1473
0
const std::vector<w_char>& HunspellImpl::get_wordchars_utf16() const {
1474
0
  return pAMgr->get_wordchars_utf16();
1475
0
}
1476
1477
22.7k
void HunspellImpl::mkinitcap(std::string& u8) {
1478
22.7k
  if (utf8) {
1479
8.50k
    std::vector<w_char> u16;
1480
8.50k
    u8_u16(u16, u8);
1481
8.50k
    ::mkinitcap_utf(u16, langnum);
1482
8.50k
    u16_u8(u8, u16);
1483
14.2k
  } else {
1484
14.2k
    ::mkinitcap(u8, csconv);
1485
14.2k
  }
1486
22.7k
}
1487
1488
1.53M
int HunspellImpl::mkinitcap2(std::string& u8, std::vector<w_char>& u16) {
1489
1.53M
  if (utf8) {
1490
325k
    ::mkinitcap_utf(u16, langnum);
1491
325k
    u16_u8(u8, u16);
1492
1.21M
  } else {
1493
1.21M
    ::mkinitcap(u8, csconv);
1494
1.21M
  }
1495
1.53M
  return u8.size();
1496
1.53M
}
1497
1498
16.6k
int HunspellImpl::mkinitsmall2(std::string& u8, std::vector<w_char>& u16) {
1499
16.6k
  if (utf8) {
1500
4.04k
    ::mkinitsmall_utf(u16, langnum);
1501
4.04k
    u16_u8(u8, u16);
1502
12.5k
  } else {
1503
12.5k
    ::mkinitsmall(u8, csconv);
1504
12.5k
  }
1505
16.6k
  return u8.size();
1506
16.6k
}
1507
1508
38.8k
int HunspellImpl::add(const std::string& word) {
1509
38.8k
  if (!m_HMgrs.empty())
1510
38.8k
    return m_HMgrs[0]->add(word);
1511
0
  return 0;
1512
38.8k
}
1513
1514
0
int HunspellImpl::add_with_flags(const std::string& word, const std::string& flags, const std::string& desc) {
1515
0
  if (!m_HMgrs.empty())
1516
0
    return m_HMgrs[0]->add_with_flags(word, flags, desc);
1517
0
  return 0;
1518
0
}
1519
1520
37.1k
int HunspellImpl::add_with_affix(const std::string& word, const std::string& example) {
1521
37.1k
  if (!m_HMgrs.empty())
1522
37.1k
    return m_HMgrs[0]->add_with_affix(word, example);
1523
0
  return 0;
1524
37.1k
}
1525
1526
31.2k
int HunspellImpl::remove(const std::string& word) {
1527
31.2k
  if (!m_HMgrs.empty())
1528
31.2k
    return m_HMgrs[0]->remove(word);
1529
0
  return 0;
1530
31.2k
}
1531
1532
0
const std::string& HunspellImpl::get_version_cpp() const {
1533
0
  return pAMgr->get_version();
1534
0
}
1535
1536
0
struct cs_info* HunspellImpl::get_csconv() {
1537
  // Preserve pre-1.7.3 ABI: returned pointer is now to read-only data,
1538
  // but the public signature still says non-const. Callers must not
1539
  // write through it.
1540
0
  return const_cast<struct cs_info*>(csconv);
1541
0
}
1542
1543
1.06M
void HunspellImpl::cat_result(std::string& result, const std::string& st) {
1544
1.06M
  if (!st.empty()) {
1545
6.88k
    if (!result.empty())
1546
2.13k
      result.append("\n");
1547
6.88k
    result.append(st);
1548
6.88k
  }
1549
1.06M
}
1550
1551
12.4k
std::vector<std::string> HunspellImpl::analyze(const std::string& word) {
1552
12.4k
  std::vector<std::string> slst = analyze_internal(word);
1553
  // output conversion
1554
12.4k
  RepList* rl = (pAMgr) ? pAMgr->get_oconvtable() : nullptr;
1555
12.4k
  if (rl) {
1556
1.98k
    for (size_t i = 0; rl && i < slst.size(); ++i) {
1557
1.45k
      std::string wspace;
1558
1.45k
      if (rl->conv(slst[i], wspace)) {
1559
594
        slst[i] = std::move(wspace);
1560
594
      }
1561
1.45k
    }
1562
530
  }
1563
12.4k
  return slst;
1564
12.4k
}
1565
1566
12.4k
std::vector<std::string> HunspellImpl::analyze_internal(const std::string& word) {
1567
12.4k
  std::vector<std::string> candidate_stack, slst;
1568
12.4k
  if (!pSMgr || m_HMgrs.empty())
1569
0
    return slst;
1570
12.4k
  if (utf8) {
1571
5.30k
    if (word.size() >= MAXWORDUTF8LEN)
1572
288
      return slst;
1573
7.18k
  } else {
1574
7.18k
    if (word.size() >= MAXWORDLEN)
1575
572
      return slst;
1576
7.18k
  }
1577
11.6k
  int captype = NOCAP;
1578
11.6k
  size_t abbv = 0;
1579
11.6k
  size_t wl = 0;
1580
1581
11.6k
  std::string scw;
1582
11.6k
  std::vector<w_char> sunicw;
1583
1584
  // input conversion
1585
11.6k
  RepList* rl = (pAMgr) ? pAMgr->get_iconvtable() : nullptr;
1586
11.6k
  {
1587
11.6k
    std::string wspace;
1588
1589
11.6k
    bool convstatus = rl ? rl->conv(word, wspace) : false;
1590
11.6k
    if (convstatus)
1591
575
      wl = cleanword2(scw, sunicw, wspace, &captype, &abbv);
1592
11.0k
    else
1593
11.0k
      wl = cleanword2(scw, sunicw, word, &captype, &abbv);
1594
1595
11.6k
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
1596
11.6k
    if (wl > 32768)
1597
20
      return slst;
1598
11.6k
#endif
1599
11.6k
  }
1600
1601
11.6k
  if (wl == 0) {
1602
756
    if (abbv) {
1603
291
      scw.clear();
1604
4.91k
      for (wl = 0; wl < abbv; wl++)
1605
4.62k
        scw.push_back('.');
1606
291
      abbv = 0;
1607
291
    } else
1608
465
      return slst;
1609
756
  }
1610
1611
11.1k
  std::string result;
1612
1613
11.1k
  auto suggest_start = std::chrono::steady_clock::now();
1614
1615
11.1k
  size_t n = 0;
1616
  // test numbers
1617
  // LANG_hu section: set dash information for suggestions
1618
11.1k
  if (langnum == LANG_hu) {
1619
1.42k
    size_t n2 = 0;
1620
1.42k
    size_t n3 = 0;
1621
1622
3.19k
    while ((n < wl) && (((scw[n] <= '9') && (scw[n] >= '0')) ||
1623
1.78k
                        (((scw[n] == '.') || (scw[n] == ',')) && (n > 0)))) {
1624
1.78k
      n++;
1625
1.78k
      if ((scw[n] == '.') || (scw[n] == ',')) {
1626
67
        if (((n2 == 0) && (n > 3)) ||
1627
66
            ((n2 > 0) && ((scw[n - 1] == '.') || (scw[n - 1] == ','))))
1628
10
          break;
1629
57
        n2++;
1630
57
        n3 = n;
1631
57
      }
1632
1.78k
    }
1633
1634
1.42k
    if ((n == wl) && (n3 > 0) && (n - n3 > 3))
1635
1
      return slst;
1636
1.41k
    if ((n == wl) || ((n > 0) && ((scw[n] == '%') || (scw[n] == '\xB0')) && checkword(scw.substr(n), nullptr, nullptr, suggest_start))) {
1637
106
      result.append(scw);
1638
106
      result.resize(n - 1);
1639
106
      if (n == wl)
1640
102
        cat_result(result, pSMgr->suggest_morph(scw.substr(n - 1)));
1641
4
      else {
1642
4
        std::string chunk = scw.substr(n - 1, 1);
1643
4
        cat_result(result, pSMgr->suggest_morph(chunk));
1644
4
        result.push_back('+');  // XXX SPEC. MORPHCODE
1645
4
        cat_result(result, pSMgr->suggest_morph(scw.substr(n)));
1646
4
      }
1647
106
      return line_tok(result, MSEP_REC);
1648
106
    }
1649
1.41k
  }
1650
  // END OF LANG_hu section
1651
1652
11.0k
  switch (captype) {
1653
2.02k
    case HUHCAP:
1654
2.40k
    case HUHINITCAP:
1655
6.11k
    case NOCAP: {
1656
6.11k
      cat_result(result, pSMgr->suggest_morph(scw));
1657
6.11k
      if (abbv) {
1658
361
        std::string u8buffer(scw);
1659
361
        u8buffer.push_back('.');
1660
361
        cat_result(result, pSMgr->suggest_morph(u8buffer));
1661
361
      }
1662
6.11k
      break;
1663
2.40k
    }
1664
1.93k
    case INITCAP: {
1665
1.93k
      mkallsmall2(scw, sunicw);
1666
1.93k
      std::string u8buffer(scw);
1667
1.93k
      mkinitcap2(scw, sunicw);
1668
1.93k
      cat_result(result, pSMgr->suggest_morph(u8buffer));
1669
1.93k
      cat_result(result, pSMgr->suggest_morph(scw));
1670
1.93k
      if (abbv) {
1671
315
        u8buffer.push_back('.');
1672
315
        cat_result(result, pSMgr->suggest_morph(u8buffer));
1673
1674
315
        u8buffer = scw;
1675
315
        u8buffer.push_back('.');
1676
1677
315
        cat_result(result, pSMgr->suggest_morph(u8buffer));
1678
315
      }
1679
1.93k
      break;
1680
2.40k
    }
1681
2.99k
    case ALLCAP: {
1682
2.99k
      cat_result(result, pSMgr->suggest_morph(scw));
1683
2.99k
      if (abbv) {
1684
236
        std::string u8buffer(scw);
1685
236
        u8buffer.push_back('.');
1686
236
        cat_result(result, pSMgr->suggest_morph(u8buffer));
1687
236
      }
1688
2.99k
      mkallsmall2(scw, sunicw);
1689
2.99k
      std::string u8buffer(scw);
1690
2.99k
      mkinitcap2(scw, sunicw);
1691
1692
2.99k
      cat_result(result, pSMgr->suggest_morph(u8buffer));
1693
2.99k
      cat_result(result, pSMgr->suggest_morph(scw));
1694
2.99k
      if (abbv) {
1695
236
        u8buffer.push_back('.');
1696
236
        cat_result(result, pSMgr->suggest_morph(u8buffer));
1697
1698
236
        u8buffer = scw;
1699
236
        u8buffer.push_back('.');
1700
1701
236
        cat_result(result, pSMgr->suggest_morph(u8buffer));
1702
236
      }
1703
2.99k
      break;
1704
2.40k
    }
1705
11.0k
  }
1706
1707
11.0k
  if (!result.empty()) {
1708
    // word reversing wrapper for complex prefixes
1709
4.59k
    if (complexprefixes) {
1710
1.01k
      if (utf8)
1711
104
        reverseword_utf(result);
1712
907
      else
1713
907
        reverseword(result);
1714
1.01k
    }
1715
4.59k
    return line_tok(result, MSEP_REC);
1716
4.59k
  }
1717
1718
  // compound word with dash (HU) I18n
1719
  // LANG_hu section: set dash information for suggestions
1720
1721
6.44k
  size_t dash_pos = langnum == LANG_hu ? scw.find('-') : std::string::npos;
1722
6.44k
  if (dash_pos != std::string::npos) {
1723
464
    int nresult = 0;
1724
1725
464
    std::string part1 = scw.substr(0, dash_pos), part2 = scw.substr(dash_pos + 1);
1726
1727
    // examine 2 sides of the dash
1728
464
    if (part2.empty()) {  // base word ending with dash
1729
30
      if (spell(part1, candidate_stack, nullptr, nullptr, suggest_start)) {
1730
15
        std::string p = pSMgr->suggest_morph(part1);
1731
15
        if (!p.empty()) {
1732
13
          slst = line_tok(p, MSEP_REC);
1733
13
          return slst;
1734
13
        }
1735
15
      }
1736
434
    } else if (part2.size() == 1 && part2[0] == 'e') {  // XXX (HU) -e hat.
1737
1
      if (spell(part1, candidate_stack, nullptr, nullptr, suggest_start) &&
1738
0
          (spell("-e", candidate_stack, nullptr, nullptr, suggest_start))) {
1739
0
        std::string st = pSMgr->suggest_morph(part1);
1740
0
        if (!st.empty()) {
1741
0
          result.append(st);
1742
0
        }
1743
0
        result.push_back('+');  // XXX spec. separator in MORPHCODE
1744
0
        st = pSMgr->suggest_morph("-e");
1745
0
        if (!st.empty()) {
1746
0
          result.append(st);
1747
0
        }
1748
0
        return line_tok(result, MSEP_REC);
1749
0
      }
1750
433
    } else {
1751
      // first word ending with dash: word- XXX ???
1752
433
      part1.push_back(' ');
1753
433
      nresult = spell(part1, candidate_stack, nullptr, nullptr, suggest_start);
1754
433
      part1.erase(part1.size() - 1);
1755
433
      if (nresult && spell(part2, candidate_stack, nullptr, nullptr, suggest_start) &&
1756
253
          ((part2.size() > 1) || ((part2[0] > '0') && (part2[0] < '9')))) {
1757
252
        std::string st = pSMgr->suggest_morph(part1);
1758
252
        if (!st.empty()) {
1759
208
          result.append(st);
1760
208
          result.push_back('+');  // XXX spec. separator in MORPHCODE
1761
208
        }
1762
252
        st = pSMgr->suggest_morph(part2);
1763
252
        if (!st.empty()) {
1764
129
          result.append(st);
1765
129
        }
1766
252
        return line_tok(result, MSEP_REC);
1767
252
      }
1768
433
    }
1769
    // affixed number in correct word
1770
199
    if (nresult && (dash_pos > 0) &&
1771
32
        (((scw[dash_pos - 1] <= '9') && (scw[dash_pos - 1] >= '0')) ||
1772
29
         (scw[dash_pos - 1] == '.'))) {
1773
29
      n = 1;
1774
29
      if (scw[dash_pos - n] == '.')
1775
9
        n++;
1776
      // search first not a number character to left from dash
1777
78
      while ((dash_pos >= n) && ((scw[dash_pos - n] == '0') || (n < 3)) &&
1778
49
             (n < 6)) {
1779
49
        n++;
1780
49
      }
1781
29
      if (dash_pos < n)
1782
0
        n--;
1783
      // numbers: valami1000000-hoz
1784
      // examine 100000-hoz, 10000-hoz 1000-hoz, 10-hoz,
1785
      // 56-hoz, 6-hoz
1786
85
      for (; n >= 1; n--) {
1787
76
        if (scw[dash_pos - n] < '0' || scw[dash_pos - n] > '9') {
1788
39
            continue;
1789
39
        }
1790
37
        std::string chunk = scw.substr(dash_pos - n);
1791
37
        if (checkword(chunk, nullptr, nullptr, suggest_start)) {
1792
20
          result.append(chunk);
1793
20
          std::string st = pSMgr->suggest_morph(chunk);
1794
20
          if (!st.empty()) {
1795
18
            result.append(st);
1796
18
          }
1797
20
          return line_tok(result, MSEP_REC);
1798
20
        }
1799
37
      }
1800
29
    }
1801
199
  }
1802
6.16k
  return slst;
1803
6.44k
}
1804
1805
9.42k
std::vector<std::string> HunspellImpl::generate(const std::string& word, const std::vector<std::string>& pl) {
1806
9.42k
  std::vector<std::string> slst;
1807
9.42k
  if (!pSMgr || pl.empty())
1808
2.02k
    return slst;
1809
7.39k
  std::vector<std::string> pl2 = analyze(word);
1810
7.39k
  int captype = NOCAP, abbv = 0;
1811
7.39k
  std::string cw;
1812
7.39k
  cleanword(cw, word, &captype, &abbv);
1813
7.39k
  std::string result;
1814
1815
7.39k
  auto suggest_start = std::chrono::steady_clock::now();
1816
1.04M
  for (const auto& i : pl) {
1817
1.04M
    cat_result(result, pSMgr->suggest_gen(pl2, i, suggest_start));
1818
1.04M
  }
1819
1820
7.39k
  if (!result.empty()) {
1821
    // allcap
1822
150
    if (captype == ALLCAP)
1823
0
      mkallcap(result);
1824
1825
    // line split
1826
150
    slst = line_tok(result, MSEP_REC);
1827
1828
    // capitalize
1829
150
    if (captype == INITCAP || captype == HUHINITCAP) {
1830
0
      for (auto& str : slst) {
1831
0
        mkinitcap(str);
1832
0
      }
1833
0
    }
1834
1835
    // temporary filtering of prefix related errors (eg.
1836
    // generate("undrinkable", "eats") --> "undrinkables" and "*undrinks")
1837
150
    auto it = slst.begin();
1838
8.92k
    while (it != slst.end()) {
1839
8.77k
      if (std::chrono::steady_clock::now() - suggest_start > TIMELIMIT_GLOBAL_MS)
1840
0
        break;
1841
8.77k
      std::vector<std::string> candidate_stack;
1842
8.77k
      if (!spell(*it, candidate_stack, nullptr, nullptr, suggest_start)) {
1843
835
        it = slst.erase(it);
1844
7.94k
      } else  {
1845
7.94k
        ++it;
1846
7.94k
      }
1847
8.77k
    }
1848
150
  }
1849
7.39k
  return slst;
1850
9.42k
}
1851
1852
3.32k
std::vector<std::string> HunspellImpl::generate(const std::string& word, const std::string& pattern) {
1853
3.32k
  std::vector<std::string> pl = analyze(pattern);
1854
3.32k
  std::vector<std::string> slst = generate(word, pl);
1855
3.32k
  uniqlist(slst);
1856
3.32k
  return slst;
1857
3.32k
}
1858
1859
// minimal XML parser functions
1860
501k
std::string HunspellImpl::get_xml_par(const std::string& in_par, std::string::size_type pos) {
1861
501k
  std::string dest;
1862
501k
  if (pos == std::string::npos)
1863
5.48k
    return dest;
1864
495k
  const char* par = in_par.c_str() + pos;
1865
495k
  char end = *par;
1866
495k
  if (end == '>')
1867
492k
    end = '<';
1868
3.06k
  else if (end != '\'' && end != '"')
1869
1.45k
    return dest;  // bad XML
1870
10.4M
  for (par++; *par != '\0' && *par != end; ++par) {
1871
9.95M
    dest.push_back(*par);
1872
9.95M
  }
1873
494k
  mystrrep(dest, "&lt;", "<");
1874
494k
  mystrrep(dest, "&amp;", "&");
1875
494k
  return dest;
1876
495k
}
1877
1878
0
int HunspellImpl::get_langnum() const {
1879
0
  return langnum;
1880
0
}
1881
1882
0
bool HunspellImpl::input_conv(const std::string& word, std::string& dest) {
1883
0
  RepList* rl = pAMgr ? pAMgr->get_iconvtable() : nullptr;
1884
0
  if (rl) {
1885
0
    return rl->conv(word, dest);
1886
0
  }
1887
0
  dest.assign(word);
1888
0
  return false;
1889
0
}
1890
1891
// return the beginning of the element (attr == NULL) or the attribute
1892
78.6k
std::string::size_type HunspellImpl::get_xml_pos(const std::string& s, std::string::size_type pos, const char* attr) {
1893
78.6k
  if (pos == std::string::npos)
1894
0
    return std::string::npos;
1895
1896
78.6k
  std::string::size_type endpos = s.find('>', pos);
1897
78.6k
  if (attr == nullptr)
1898
0
    return endpos;
1899
240k
  while (true) {
1900
240k
    pos = s.find(attr, pos);
1901
240k
    if (pos == std::string::npos || pos >= endpos)
1902
4.26k
      return std::string::npos;
1903
236k
    if (pos == 0 || s[pos - 1] == ' ' || s[pos - 1] == '\n')
1904
74.4k
      break;
1905
161k
    pos += strlen(attr);
1906
161k
  }
1907
74.4k
  return pos + strlen(attr);
1908
78.6k
}
1909
1910
int HunspellImpl::check_xml_par(const std::string& q, std::string::size_type pos,
1911
                                const char* attr,
1912
78.6k
                                const char* value) {
1913
78.6k
  const std::string cw = get_xml_par(q, get_xml_pos(q, pos, attr));
1914
78.6k
  return cw == value ? 1 : 0;
1915
78.6k
}
1916
1917
6.73k
std::vector<std::string> HunspellImpl::get_xml_list(const std::string& list, std::string::size_type pos, const char* tag) {
1918
6.73k
  std::vector<std::string> slst;
1919
6.73k
  if (pos == std::string::npos)
1920
21
    return slst;
1921
401k
  while (true) {
1922
401k
    pos = list.find(tag, pos);
1923
401k
    if (pos == std::string::npos)
1924
4.82k
        break;
1925
396k
    std::string cw = get_xml_par(list, pos + strlen(tag) - 1);
1926
396k
    if (cw.empty()) {
1927
1.88k
      break;
1928
1.88k
    }
1929
394k
    slst.push_back(std::move(cw));
1930
394k
    ++pos;
1931
394k
  }
1932
6.71k
  return slst;
1933
6.73k
}
1934
1935
24.6k
std::vector<std::string> HunspellImpl::spellml(const std::string& in_word) {
1936
24.6k
  std::vector<std::string> slst;
1937
1938
24.6k
  std::string::size_type qpos = in_word.find("<query");
1939
24.6k
  if (qpos == std::string::npos)
1940
737
    return slst;  // bad XML input
1941
1942
23.8k
  std::string::size_type q2pos = in_word.find('>', qpos);
1943
23.8k
  if (q2pos == std::string::npos)
1944
148
    return slst;  // bad XML input
1945
1946
23.7k
  q2pos = in_word.find("<word", q2pos);
1947
23.7k
  if (q2pos == std::string::npos)
1948
296
    return slst;  // bad XML input
1949
1950
23.4k
  if (check_xml_par(in_word, qpos, "type=", "analyze")) {
1951
609
    std::string cw = get_xml_par(in_word, in_word.find('>', q2pos));
1952
609
    if (!cw.empty())
1953
304
      slst = analyze(cw);
1954
609
    if (slst.empty())
1955
559
      return slst;
1956
    // convert the result to <code><a>ana1</a><a>ana2</a></code> format
1957
50
    std::string r;
1958
50
    r.append("<code>");
1959
229
    for (auto entry : slst) {
1960
229
      r.append("<a>");
1961
1962
229
      mystrrep(entry, "\t", " ");
1963
229
      mystrrep(entry, "&", "&amp;");
1964
229
      mystrrep(entry, "<", "&lt;");
1965
229
      r.append(entry);
1966
1967
229
      r.append("</a>");
1968
229
    }
1969
50
    r.append("</code>");
1970
50
    slst.clear();
1971
50
    slst.push_back(std::move(r));
1972
50
    return slst;
1973
22.8k
  } else if (check_xml_par(in_word, qpos, "type=", "stem")) {
1974
1.46k
    std::string cw = get_xml_par(in_word, in_word.find('>', q2pos));
1975
1.46k
    if (!cw.empty())
1976
1.10k
      return stem(cw);
1977
21.3k
  } else if (check_xml_par(in_word, qpos, "type=", "generate")) {
1978
10.2k
    std::string cw = get_xml_par(in_word, in_word.find('>', q2pos));
1979
10.2k
    if (cw.empty())
1980
178
      return slst;
1981
10.0k
    std::string::size_type q3pos = in_word.find("<word", q2pos + 1);
1982
10.0k
    if (q3pos != std::string::npos) {
1983
3.21k
      std::string cw2 = get_xml_par(in_word, in_word.find('>', q3pos));
1984
3.21k
      if (!cw2.empty()) {
1985
3.13k
        return generate(cw, cw2);
1986
3.13k
      }
1987
6.85k
    } else {
1988
6.85k
      q2pos = in_word.find("<code", q2pos + 1);
1989
6.85k
      if (q2pos != std::string::npos) {
1990
6.73k
        std::vector<std::string> slst2 = get_xml_list(in_word, in_word.find('>', q2pos), "<a>");
1991
6.73k
        if (!slst2.empty()) {
1992
6.09k
          slst = generate(cw, slst2);
1993
6.09k
          uniqlist(slst);
1994
6.09k
          return slst;
1995
6.09k
        }
1996
6.73k
      }
1997
6.85k
    }
1998
11.1k
  } else if (check_xml_par(in_word, qpos, "type=", "add")) {
1999
8.10k
    std::string cw = get_xml_par(in_word, in_word.find('>', q2pos));
2000
8.10k
    if (cw.empty())
2001
172
      return slst;
2002
7.93k
    std::string::size_type q3pos = in_word.find("<word", q2pos + 1);
2003
7.93k
    if (q3pos != std::string::npos) {
2004
2.41k
      std::string cw2 = get_xml_par(in_word, in_word.find('>', q3pos));
2005
2.41k
      if (!cw2.empty()) {
2006
2.07k
        add_with_affix(cw, cw2);
2007
2.07k
      } else {
2008
343
        add(cw);
2009
343
      }
2010
5.52k
    } else {
2011
5.52k
        add(cw);
2012
5.52k
    }
2013
7.93k
  }
2014
12.1k
  return slst;
2015
23.4k
}
2016
2017
0
std::vector<std::string> HunspellImpl::suffix_suggest(const std::string& root_word) {
2018
0
  std::vector<std::string> slst;
2019
0
  struct hentry* he = nullptr;
2020
0
  int len;
2021
0
  std::string w2;
2022
0
  const char* word;
2023
0
  const char* ignoredchars = pAMgr->get_ignore();
2024
0
  if (ignoredchars != nullptr) {
2025
0
    w2.assign(root_word);
2026
0
    if (utf8) {
2027
0
      const std::vector<w_char>& ignoredchars_utf16 =
2028
0
          pAMgr->get_ignore_utf16();
2029
0
      remove_ignored_chars_utf(w2, ignoredchars_utf16);
2030
0
    } else {
2031
0
      remove_ignored_chars(w2, ignoredchars);
2032
0
    }
2033
0
    word = w2.c_str();
2034
0
    len = (int)w2.size();
2035
0
  } else {
2036
0
    word = root_word.c_str();
2037
0
    len = (int)root_word.size();
2038
0
  }
2039
2040
0
  if (!len)
2041
0
    return slst;
2042
2043
0
  for (size_t i = 0; (i < m_HMgrs.size()) && !he; ++i) {
2044
0
    he = m_HMgrs[i]->lookup(word, len);
2045
0
  }
2046
0
  if (he) {
2047
0
    slst = pAMgr->get_suffix_words(he->astr, he->alen, root_word);
2048
0
  }
2049
0
  return slst;
2050
0
}
2051
2052
namespace {
2053
  // using malloc because this is for the c-api where the callers
2054
  // expect to be able to use free
2055
0
  char* stringdup(const std::string& s) {
2056
0
    size_t sl = s.size() + 1;
2057
0
    char* d = (char*)malloc(sl);
2058
0
    if (d)
2059
0
      memcpy(d, s.c_str(), sl);
2060
0
    return d;
2061
0
  }
2062
2063
0
  int munge_vector(char*** slst, const std::vector<std::string>& items) {
2064
0
    if (items.empty()) {
2065
0
      *slst = nullptr;
2066
0
      return 0;
2067
0
    } else {
2068
0
      *slst = new char*[items.size()];
2069
0
      for (size_t i = 0; i < items.size(); ++i)
2070
0
        (*slst)[i] = stringdup(items[i]);
2071
0
    }
2072
0
    return items.size();
2073
0
  }
2074
}
2075
2076
0
int HunspellImpl::spell(const char* word, int* info, char** root) {
2077
0
  std::string sroot;
2078
0
  std::vector<std::string> candidate_stack;
2079
0
  bool ret = spell(word, candidate_stack, info, root ? &sroot : nullptr);
2080
0
  if (root) {
2081
0
    if (sroot.empty()) {
2082
0
      *root = nullptr;
2083
0
    } else {
2084
0
      *root = stringdup(sroot);
2085
0
    }
2086
0
  }
2087
0
  return ret;
2088
0
}
2089
2090
0
int HunspellImpl::suggest(char*** slst, const char* word) {
2091
0
  std::vector<std::string> suggests = suggest(word);
2092
0
  return munge_vector(slst, suggests);
2093
0
}
2094
2095
0
int HunspellImpl::suffix_suggest(char*** slst, const char* root_word) {
2096
0
  std::vector<std::string> stems = suffix_suggest(root_word);
2097
0
  return munge_vector(slst, stems);
2098
0
}
2099
2100
0
void HunspellImpl::free_list(char*** slst, int n) {
2101
0
  if (slst && *slst) {
2102
0
    for (int i = 0; i < n; i++)
2103
0
      free((*slst)[i]);
2104
0
    delete[] *slst;
2105
0
    *slst = nullptr;
2106
0
  }
2107
0
}
2108
2109
0
char* HunspellImpl::get_dic_encoding() {
2110
0
  return &encoding[0];
2111
0
}
2112
2113
0
int HunspellImpl::analyze(char*** slst, const char* word) {
2114
0
  std::vector<std::string> stems = analyze(word);
2115
0
  return munge_vector(slst, stems);
2116
0
}
2117
2118
0
int HunspellImpl::stem(char*** slst, const char* word) {
2119
0
  std::vector<std::string> stems = stem(word);
2120
0
  return munge_vector(slst, stems);
2121
0
}
2122
2123
0
int HunspellImpl::stem(char*** slst, char** desc, int n) {
2124
0
  std::vector<std::string> morph;
2125
0
  morph.reserve(n);
2126
0
  for (int i = 0; i < n; ++i) morph.emplace_back(desc[i]);
2127
2128
0
  std::vector<std::string> stems = stem(morph);
2129
0
  return munge_vector(slst, stems);
2130
0
}
2131
2132
0
int HunspellImpl::generate(char*** slst, const char* word, const char* pattern) {
2133
0
  std::vector<std::string> stems = generate(word, pattern);
2134
0
  return munge_vector(slst, stems);
2135
0
}
2136
2137
0
int HunspellImpl::generate(char*** slst, const char* word, char** pl, int pln) {
2138
0
  std::vector<std::string> morph;
2139
0
  morph.reserve(pln);
2140
0
  for (int i = 0; i < pln; ++i) morph.emplace_back(pl[i]);
2141
2142
0
  std::vector<std::string> stems = generate(word, morph);
2143
0
  return munge_vector(slst, stems);
2144
0
}
2145
2146
0
const char* HunspellImpl::get_wordchars() const {
2147
0
  return get_wordchars_cpp().c_str();
2148
0
}
2149
2150
0
const char* HunspellImpl::get_version() const {
2151
0
  return get_version_cpp().c_str();
2152
0
}
2153
2154
0
int HunspellImpl::input_conv(const char* word, char* dest, size_t destsize) {
2155
0
  std::string d;
2156
0
  bool ret = input_conv(word, d);
2157
0
  if (ret && d.size() < destsize) {
2158
0
    strncpy(dest, d.c_str(), destsize);
2159
0
    return 1;
2160
0
  }
2161
0
  return 0;
2162
0
}
2163
2164
Hunspell::Hunspell(const char* affpath, const char* dpath, const char* key)
2165
36.1k
  : m_Impl(new HunspellImpl(affpath, dpath, key)) {
2166
36.1k
}
2167
2168
36.1k
Hunspell::~Hunspell() {
2169
36.1k
  delete m_Impl;
2170
36.1k
}
2171
2172
// load extra dictionaries
2173
0
int Hunspell::add_dic(const char* dpath, const char* key) {
2174
0
  return m_Impl->add_dic(dpath, key);
2175
0
}
2176
2177
222k
bool Hunspell::spell(const std::string& word, int* info, std::string* root) {
2178
222k
  std::vector<std::string> candidate_stack;
2179
222k
  return m_Impl->spell(word, candidate_stack, info, root,
2180
222k
                       std::chrono::steady_clock::now());
2181
222k
}
2182
2183
80.3k
std::vector<std::string> Hunspell::suggest(const std::string& word) {
2184
80.3k
  return m_Impl->suggest(word);
2185
80.3k
}
2186
2187
0
std::vector<std::string> Hunspell::suffix_suggest(const std::string& root_word) {
2188
0
  return m_Impl->suffix_suggest(root_word);
2189
0
}
2190
2191
0
const std::string& Hunspell::get_dict_encoding() const {
2192
0
  return m_Impl->get_dict_encoding();
2193
0
}
2194
2195
0
std::vector<std::string> Hunspell::stem(const std::vector<std::string>& desc) {
2196
0
  return m_Impl->stem(desc);
2197
0
}
2198
2199
186
std::vector<std::string> Hunspell::stem(const std::string& word) {
2200
186
  return m_Impl->stem(word);
2201
186
}
2202
2203
0
const std::string& Hunspell::get_wordchars_cpp() const {
2204
0
  return m_Impl->get_wordchars_cpp();
2205
0
}
2206
2207
0
const std::vector<w_char>& Hunspell::get_wordchars_utf16() const {
2208
0
  return m_Impl->get_wordchars_utf16();
2209
0
}
2210
2211
33.0k
int Hunspell::add(const std::string& word) {
2212
33.0k
  return m_Impl->add(word);
2213
33.0k
}
2214
2215
0
int Hunspell::add_with_flags(const std::string& word, const std::string& flags, const std::string& desc) {
2216
0
  return m_Impl->add_with_flags(word, flags, desc);
2217
0
}
2218
2219
35.0k
int Hunspell::add_with_affix(const std::string& word, const std::string& example) {
2220
35.0k
  return m_Impl->add_with_affix(word, example);
2221
35.0k
}
2222
2223
31.2k
int Hunspell::remove(const std::string& word) {
2224
31.2k
  return m_Impl->remove(word);
2225
31.2k
}
2226
2227
0
const std::string& Hunspell::get_version_cpp() const {
2228
0
  return m_Impl->get_version_cpp();
2229
0
}
2230
2231
0
struct cs_info* Hunspell::get_csconv() {
2232
0
  return m_Impl->get_csconv();
2233
0
}
2234
2235
186
std::vector<std::string> Hunspell::analyze(const std::string& word) {
2236
186
  return m_Impl->analyze(word);
2237
186
}
2238
2239
0
std::vector<std::string> Hunspell::generate(const std::string& word, const std::vector<std::string>& pl) {
2240
0
  return m_Impl->generate(word, pl);
2241
0
}
2242
2243
186
std::vector<std::string> Hunspell::generate(const std::string& word, const std::string& pattern) {
2244
186
  return m_Impl->generate(word, pattern);
2245
186
}
2246
2247
0
int Hunspell::get_langnum() const {
2248
0
  return m_Impl->get_langnum();
2249
0
}
2250
2251
0
bool Hunspell::input_conv(const std::string& word, std::string& dest) {
2252
0
  return m_Impl->input_conv(word, dest);
2253
0
}
2254
2255
0
int Hunspell::spell(const char* word, int* info, char** root) {
2256
0
  return m_Impl->spell(word, info, root);
2257
0
}
2258
2259
0
int Hunspell::suggest(char*** slst, const char* word) {
2260
0
  return m_Impl->suggest(slst, word);
2261
0
}
2262
2263
0
int Hunspell::suffix_suggest(char*** slst, const char* root_word) {
2264
0
  return m_Impl->suffix_suggest(slst, root_word);
2265
0
}
2266
2267
0
void Hunspell::free_list(char*** slst, int n) {
2268
0
  m_Impl->free_list(slst, n);
2269
0
}
2270
2271
0
char* Hunspell::get_dic_encoding() {
2272
0
  return m_Impl->get_dic_encoding();
2273
0
}
2274
2275
0
int Hunspell::analyze(char*** slst, const char* word) {
2276
0
  return m_Impl->analyze(slst, word);
2277
0
}
2278
2279
0
int Hunspell::stem(char*** slst, const char* word) {
2280
0
  return m_Impl->stem(slst, word);
2281
0
}
2282
2283
0
int Hunspell::stem(char*** slst, char** desc, int n) {
2284
0
  return m_Impl->stem(slst, desc, n);
2285
0
}
2286
2287
0
int Hunspell::generate(char*** slst, const char* word, const char* pattern) {
2288
0
  return m_Impl->generate(slst, word, pattern);
2289
0
}
2290
2291
0
int Hunspell::generate(char*** slst, const char* word, char** pl, int pln) {
2292
0
  return m_Impl->generate(slst, word, pl, pln);
2293
0
}
2294
2295
0
const char* Hunspell::get_wordchars() const {
2296
0
  return m_Impl->get_wordchars();
2297
0
}
2298
2299
0
const char* Hunspell::get_version() const {
2300
0
  return m_Impl->get_version();
2301
0
}
2302
2303
0
int Hunspell::input_conv(const char* word, char* dest, size_t destsize) {
2304
0
  return m_Impl->input_conv(word, dest, destsize);
2305
0
}
2306
2307
0
Hunhandle* Hunspell_create(const char* affpath, const char* dpath) {
2308
0
  return reinterpret_cast<Hunhandle*>(new HunspellImpl(affpath, dpath));
2309
0
}
2310
2311
Hunhandle* Hunspell_create_key(const char* affpath,
2312
                               const char* dpath,
2313
0
                               const char* key) {
2314
0
  return reinterpret_cast<Hunhandle*>(new HunspellImpl(affpath, dpath, key));
2315
0
}
2316
2317
0
void Hunspell_destroy(Hunhandle* pHunspell) {
2318
0
  delete reinterpret_cast<HunspellImpl*>(pHunspell);
2319
0
}
2320
2321
0
int Hunspell_add_dic(Hunhandle* pHunspell, const char* dpath) {
2322
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->add_dic(dpath);
2323
0
}
2324
2325
0
int Hunspell_spell(Hunhandle* pHunspell, const char* word) {
2326
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->spell(word);
2327
0
}
2328
2329
0
char* Hunspell_get_dic_encoding(Hunhandle* pHunspell) {
2330
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->get_dic_encoding();
2331
0
}
2332
2333
0
int Hunspell_suggest(Hunhandle* pHunspell, char*** slst, const char* word) {
2334
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->suggest(slst, word);
2335
0
}
2336
2337
0
int Hunspell_suffix_suggest(Hunhandle* pHunspell, char*** slst, const char* root_word) {
2338
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->suffix_suggest(slst, root_word);
2339
0
}
2340
2341
0
int Hunspell_analyze(Hunhandle* pHunspell, char*** slst, const char* word) {
2342
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->analyze(slst, word);
2343
0
}
2344
2345
0
int Hunspell_stem(Hunhandle* pHunspell, char*** slst, const char* word) {
2346
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->stem(slst, word);
2347
0
}
2348
2349
0
int Hunspell_stem2(Hunhandle* pHunspell, char*** slst, char** desc, int n) {
2350
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->stem(slst, desc, n);
2351
0
}
2352
2353
int Hunspell_generate(Hunhandle* pHunspell,
2354
                      char*** slst,
2355
                      const char* word,
2356
                      const char* pattern)
2357
0
{
2358
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->generate(slst, word, pattern);
2359
0
}
2360
2361
int Hunspell_generate2(Hunhandle* pHunspell,
2362
                       char*** slst,
2363
                       const char* word,
2364
                       char** desc,
2365
                       int n)
2366
0
{
2367
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->generate(slst, word, desc, n);
2368
0
}
2369
2370
/* functions for run-time modification of the dictionary */
2371
2372
/* add word to the run-time dictionary */
2373
2374
0
int Hunspell_add(Hunhandle* pHunspell, const char* word) {
2375
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->add(word);
2376
0
}
2377
2378
0
int Hunspell_add_with_flags(Hunhandle* pHunspell, const char* word, const char* flags, const char* desc) {
2379
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->add_with_flags(word, flags, desc);
2380
0
}
2381
2382
/* add word to the run-time dictionary with affix flags of
2383
 * the example (a dictionary word): Hunspell will recognize
2384
 * affixed forms of the new word, too.
2385
 */
2386
2387
int Hunspell_add_with_affix(Hunhandle* pHunspell,
2388
                            const char* word,
2389
0
                            const char* example) {
2390
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->add_with_affix(word, example);
2391
0
}
2392
2393
/* remove word from the run-time dictionary */
2394
2395
0
int Hunspell_remove(Hunhandle* pHunspell, const char* word) {
2396
0
  return reinterpret_cast<HunspellImpl*>(pHunspell)->remove(word);
2397
0
}
2398
2399
0
void Hunspell_free_list(Hunhandle* pHunspell, char*** list, int n) {
2400
0
  reinterpret_cast<HunspellImpl*>(pHunspell)->free_list(list, n);
2401
0
}