File: | build/gcc/spellcheck.h |
Warning: | line 150, column 32 The left operand of '!=' is a garbage value due to array index out of bounds |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* Find near-matches for strings. | |||
2 | Copyright (C) 2015-2023 Free Software Foundation, Inc. | |||
3 | ||||
4 | This file is part of GCC. | |||
5 | ||||
6 | GCC is free software; you can redistribute it and/or modify it under | |||
7 | the terms of the GNU General Public License as published by the Free | |||
8 | Software Foundation; either version 3, or (at your option) any later | |||
9 | version. | |||
10 | ||||
11 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY | |||
12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or | |||
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |||
14 | for more details. | |||
15 | ||||
16 | You should have received a copy of the GNU General Public License | |||
17 | along with GCC; see the file COPYING3. If not see | |||
18 | <http://www.gnu.org/licenses/>. */ | |||
19 | ||||
20 | #include "config.h" | |||
21 | #include "system.h" | |||
22 | #include "coretypes.h" | |||
23 | #include "tm.h" | |||
24 | #include "tree.h" | |||
25 | #include "spellcheck.h" | |||
26 | #include "selftest.h" | |||
27 | ||||
28 | /* Cost of a case transformation. */ | |||
29 | #define CASE_COST1 1 | |||
30 | ||||
31 | /* Cost of another kind of edit. */ | |||
32 | #define BASE_COST2 2 | |||
33 | ||||
34 | /* Get the edit distance between the two strings: the minimal | |||
35 | number of edits that are needed to change one string into another, | |||
36 | where edits can be one-character insertions, removals, or substitutions, | |||
37 | or transpositions of two adjacent characters (counting as one "edit"). | |||
38 | ||||
39 | This implementation uses a modified variant of the Wagner-Fischer | |||
40 | algorithm for the Damerau-Levenshtein distance; specifically, the | |||
41 | "optimal string alignment distance" or "restricted edit distance" | |||
42 | variant. This implementation has been further modified to take | |||
43 | case into account. */ | |||
44 | ||||
45 | edit_distance_t | |||
46 | get_edit_distance (const char *s, int len_s, | |||
47 | const char *t, int len_t) | |||
48 | { | |||
49 | const bool debug = false; | |||
50 | ||||
51 | if (debug) | |||
52 | { | |||
53 | printf ("s: \"%s\" (len_s=%i)\n", s, len_s); | |||
54 | printf ("t: \"%s\" (len_t=%i)\n", t, len_t); | |||
55 | } | |||
56 | ||||
57 | if (len_s == 0) | |||
58 | return BASE_COST2 * len_t; | |||
59 | if (len_t == 0) | |||
60 | return BASE_COST2 * len_s; | |||
61 | ||||
62 | /* We effectively build a matrix where each (i, j) contains the | |||
63 | distance between the prefix strings s[0:j] and t[0:i]. | |||
64 | Rather than actually build an (len_t + 1) * (len_s + 1) matrix, | |||
65 | we simply keep track of the last two rows, v_one_ago and v_two_ago, | |||
66 | and a new row, v_next, which avoids an (len_t + 1) * (len_s + 1) | |||
67 | allocation and memory accesses in favor of three (len_s + 1) | |||
68 | allocations. These could potentially be | |||
69 | statically-allocated if we impose a maximum length on the | |||
70 | strings of interest. */ | |||
71 | edit_distance_t *v_two_ago = new edit_distance_t[len_s + 1]; | |||
72 | edit_distance_t *v_one_ago = new edit_distance_t[len_s + 1]; | |||
73 | edit_distance_t *v_next = new edit_distance_t[len_s + 1]; | |||
74 | ||||
75 | /* The first row is for the case of an empty target string, which | |||
76 | we can reach by deleting every character in the source string. */ | |||
77 | for (int i = 0; i < len_s + 1; i++) | |||
78 | v_one_ago[i] = i * BASE_COST2; | |||
79 | ||||
80 | /* Build successive rows. */ | |||
81 | for (int i = 0; i < len_t; i++) | |||
82 | { | |||
83 | if (debug) | |||
84 | { | |||
85 | printf ("i:%i v_one_ago = ", i); | |||
86 | for (int j = 0; j < len_s + 1; j++) | |||
87 | printf ("%i ", v_one_ago[j]); | |||
88 | printf ("\n"); | |||
89 | } | |||
90 | ||||
91 | /* The initial column is for the case of an empty source string; we | |||
92 | can reach prefixes of the target string of length i | |||
93 | by inserting i characters. */ | |||
94 | v_next[0] = (i + 1) * BASE_COST2; | |||
95 | ||||
96 | /* Build the rest of the row by considering neighbors to | |||
97 | the north, west and northwest. */ | |||
98 | for (int j = 0; j < len_s; j++) | |||
99 | { | |||
100 | edit_distance_t cost; | |||
101 | ||||
102 | if (s[j] == t[i]) | |||
103 | cost = 0; | |||
104 | else if (TOLOWER (s[j])_sch_tolower[(s[j]) & 0xff] == TOLOWER (t[i])_sch_tolower[(t[i]) & 0xff]) | |||
105 | cost = CASE_COST1; | |||
106 | else | |||
107 | cost = BASE_COST2; | |||
108 | edit_distance_t deletion = v_next[j] + BASE_COST2; | |||
109 | edit_distance_t insertion = v_one_ago[j + 1] + BASE_COST2; | |||
110 | edit_distance_t substitution = v_one_ago[j] + cost; | |||
111 | edit_distance_t cheapest = MIN (deletion, insertion)((deletion) < (insertion) ? (deletion) : (insertion)); | |||
112 | cheapest = MIN (cheapest, substitution)((cheapest) < (substitution) ? (cheapest) : (substitution) ); | |||
113 | if (i > 0 && j > 0 && s[j] == t[i - 1] && s[j - 1] == t[i]) | |||
114 | { | |||
115 | edit_distance_t transposition = v_two_ago[j - 1] + BASE_COST2; | |||
116 | cheapest = MIN (cheapest, transposition)((cheapest) < (transposition) ? (cheapest) : (transposition )); | |||
117 | } | |||
118 | v_next[j + 1] = cheapest; | |||
119 | } | |||
120 | ||||
121 | /* Prepare to move on to next row. */ | |||
122 | for (int j = 0; j < len_s + 1; j++) | |||
123 | { | |||
124 | v_two_ago[j] = v_one_ago[j]; | |||
125 | v_one_ago[j] = v_next[j]; | |||
126 | } | |||
127 | } | |||
128 | ||||
129 | if (debug) | |||
130 | { | |||
131 | printf ("final v_next = "); | |||
132 | for (int j = 0; j < len_s + 1; j++) | |||
133 | printf ("%i ", v_next[j]); | |||
134 | printf ("\n"); | |||
135 | } | |||
136 | ||||
137 | edit_distance_t result = v_next[len_s]; | |||
138 | delete[] v_two_ago; | |||
139 | delete[] v_one_ago; | |||
140 | delete[] v_next; | |||
141 | return result; | |||
142 | } | |||
143 | ||||
144 | /* Get the edit distance between two nil-terminated strings. */ | |||
145 | ||||
146 | edit_distance_t | |||
147 | get_edit_distance (const char *s, const char *t) | |||
148 | { | |||
149 | return get_edit_distance (s, strlen (s), t, strlen (t)); | |||
150 | } | |||
151 | ||||
152 | /* Given TARGET, a non-NULL string, and CANDIDATES, a non-NULL ptr to | |||
153 | an autovec of non-NULL strings, determine which element within | |||
154 | CANDIDATES has the lowest edit distance to TARGET. If there are | |||
155 | multiple elements with the same minimal distance, the first in the | |||
156 | vector wins. | |||
157 | ||||
158 | If more than half of the letters were misspelled, the suggestion is | |||
159 | likely to be meaningless, so return NULL for this case. */ | |||
160 | ||||
161 | const char * | |||
162 | find_closest_string (const char *target, | |||
163 | const auto_vec<const char *> *candidates) | |||
164 | { | |||
165 | gcc_assert (target)((void)(!(target) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 165, __FUNCTION__), 0 : 0)); | |||
166 | gcc_assert (candidates)((void)(!(candidates) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 166, __FUNCTION__), 0 : 0)); | |||
167 | ||||
168 | int i; | |||
169 | const char *candidate; | |||
170 | best_match<const char *, const char *> bm (target); | |||
171 | FOR_EACH_VEC_ELT (*candidates, i, candidate)for (i = 0; (*candidates).iterate ((i), &(candidate)); ++ (i)) | |||
172 | { | |||
173 | gcc_assert (candidate)((void)(!(candidate) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 173, __FUNCTION__), 0 : 0)); | |||
174 | bm.consider (candidate); | |||
175 | } | |||
176 | ||||
177 | return bm.get_best_meaningful_candidate (); | |||
178 | } | |||
179 | ||||
180 | /* Generate the maximum edit distance for which we consider a suggestion | |||
181 | to be meaningful, given a goal of length GOAL_LEN and a candidate of | |||
182 | length CANDIDATE_LEN. | |||
183 | ||||
184 | This is a third of the length of the candidate or of the goal, | |||
185 | whichever is bigger. */ | |||
186 | ||||
187 | edit_distance_t | |||
188 | get_edit_distance_cutoff (size_t goal_len, size_t candidate_len) | |||
189 | { | |||
190 | size_t max_length = MAX (goal_len, candidate_len)((goal_len) > (candidate_len) ? (goal_len) : (candidate_len )); | |||
191 | size_t min_length = MIN (goal_len, candidate_len)((goal_len) < (candidate_len) ? (goal_len) : (candidate_len )); | |||
192 | ||||
193 | gcc_assert (max_length >= min_length)((void)(!(max_length >= min_length) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 193, __FUNCTION__), 0 : 0)); | |||
194 | ||||
195 | /* Special case: don't offer suggestions for a pair of | |||
196 | length == 1 strings (or empty strings). */ | |||
197 | if (max_length <= 1) | |||
198 | return 0; | |||
199 | ||||
200 | /* If the lengths are close, then round down. */ | |||
201 | if (max_length - min_length <= 1) | |||
202 | /* ...but allow an edit distance of at least 1. */ | |||
203 | return BASE_COST2 * MAX (max_length / 3, 1)((max_length / 3) > (1) ? (max_length / 3) : (1)); | |||
204 | ||||
205 | /* Otherwise, round up (thus giving a little extra leeway to some cases | |||
206 | involving insertions/deletions). */ | |||
207 | return BASE_COST2 * (max_length + 2) / 3; | |||
208 | } | |||
209 | ||||
210 | #if CHECKING_P1 | |||
211 | ||||
212 | namespace selftest { | |||
213 | ||||
214 | /* Selftests. */ | |||
215 | ||||
216 | /* Verify that get_edit_distance (A, B) equals the expected value. */ | |||
217 | ||||
218 | static void | |||
219 | test_get_edit_distance_one_way (const char *a, const char *b, | |||
220 | edit_distance_t expected) | |||
221 | { | |||
222 | edit_distance_t actual = get_edit_distance (a, b); | |||
223 | ASSERT_EQ (actual, expected)do { const char *desc_ = "ASSERT_EQ (" "(actual)" ", " "(expected)" ")"; if (((actual)) == ((expected))) ::selftest::pass ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 223, __FUNCTION__)))), desc_); else ::selftest::fail ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 223, __FUNCTION__)))), desc_); } while (0); | |||
224 | } | |||
225 | ||||
226 | /* Verify that both | |||
227 | get_edit_distance (A, B) | |||
228 | and | |||
229 | get_edit_distance (B, A) | |||
230 | equal the expected value, to ensure that the function is symmetric. */ | |||
231 | ||||
232 | static void | |||
233 | test_get_edit_distance_both_ways (const char *a, const char *b, | |||
234 | edit_distance_t expected) | |||
235 | { | |||
236 | test_get_edit_distance_one_way (a, b, expected); | |||
237 | test_get_edit_distance_one_way (b, a, expected); | |||
238 | } | |||
239 | ||||
240 | /* Verify get_edit_distance for a variety of pairs of pre-canned | |||
241 | inputs, comparing against known-good values. */ | |||
242 | ||||
243 | static void | |||
244 | test_edit_distances () | |||
245 | { | |||
246 | test_get_edit_distance_both_ways ("", "nonempty", | |||
247 | BASE_COST2 * strlen ("nonempty")); | |||
248 | test_get_edit_distance_both_ways ("saturday", "sunday", | |||
249 | BASE_COST2 * 3); | |||
250 | test_get_edit_distance_both_ways ("foo", "m_foo", BASE_COST2 * 2); | |||
251 | test_get_edit_distance_both_ways ("hello_world", "HelloWorld", 4); | |||
252 | test_get_edit_distance_both_ways | |||
253 | ("the quick brown fox jumps over the lazy dog", "dog", BASE_COST2 * 40); | |||
254 | test_get_edit_distance_both_ways | |||
255 | ("the quick brown fox jumps over the lazy dog", | |||
256 | "the quick brown dog jumps over the lazy fox", | |||
257 | BASE_COST2 * 4); | |||
258 | test_get_edit_distance_both_ways | |||
259 | ("Lorem ipsum dolor sit amet, consectetur adipiscing elit,", | |||
260 | "All your base are belong to us", | |||
261 | BASE_COST2 * 44); | |||
262 | test_get_edit_distance_both_ways ("foo", "FOO", 3); | |||
263 | test_get_edit_distance_both_ways ("fee", "deed", BASE_COST2 * 2); | |||
264 | test_get_edit_distance_both_ways ("coorzd1", "coordx1", BASE_COST2 * 2); | |||
265 | test_get_edit_distance_both_ways ("assert", "sqrt", BASE_COST2 * 3); | |||
266 | test_get_edit_distance_both_ways ("PATH_MAX", "INT8_MAX", BASE_COST2 * 3); | |||
267 | test_get_edit_distance_both_ways ("time", "nice", BASE_COST2 * 2); | |||
268 | test_get_edit_distance_both_ways ("bar", "carg", BASE_COST2 * 2); | |||
269 | test_get_edit_distance_both_ways ("gtk_widget_show_all", | |||
270 | "GtkWidgetShowAll", 10); | |||
271 | test_get_edit_distance_both_ways ("m_bar", "bar", BASE_COST2 * 2); | |||
272 | test_get_edit_distance_both_ways ("MACRO", "MACRAME", BASE_COST2 * 3); | |||
273 | test_get_edit_distance_both_ways ("ab", "ac", BASE_COST2 * 1); | |||
274 | test_get_edit_distance_both_ways ("ab", "a", BASE_COST2 * 1); | |||
275 | test_get_edit_distance_both_ways ("a", "b", BASE_COST2 * 1); | |||
276 | test_get_edit_distance_both_ways ("nanl", "name", BASE_COST2 * 2); | |||
277 | test_get_edit_distance_both_ways ("char", "bar", BASE_COST2 * 2); | |||
278 | test_get_edit_distance_both_ways ("-optimize", "fsanitize", BASE_COST2 * 5); | |||
279 | test_get_edit_distance_both_ways ("__DATE__", "__i386__", BASE_COST2 * 4); | |||
280 | ||||
281 | /* Examples where transposition helps. */ | |||
282 | test_get_edit_distance_both_ways ("ab", "ba", BASE_COST2 * 1); | |||
283 | test_get_edit_distance_both_ways ("ba", "abc", BASE_COST2 * 2); | |||
284 | test_get_edit_distance_both_ways ("coorzd1", "coordz1", BASE_COST2 * 1); | |||
285 | test_get_edit_distance_both_ways ("abcdefghijklmnopqrstuvwxyz", | |||
286 | "bacdefghijklmnopqrstuvwxzy", | |||
287 | BASE_COST2 * 2); | |||
288 | test_get_edit_distance_both_ways ("saturday", "sundya", BASE_COST2 * 4); | |||
289 | test_get_edit_distance_both_ways ("signed", "singed", BASE_COST2 * 1); | |||
290 | } | |||
291 | ||||
292 | /* Subroutine of test_get_edit_distance_cutoff, for emulating the | |||
293 | spellchecking cutoff in up to GCC 8. */ | |||
294 | ||||
295 | static edit_distance_t | |||
296 | get_old_cutoff (size_t goal_len, size_t candidate_len) | |||
297 | { | |||
298 | return BASE_COST2 * MAX (goal_len, candidate_len)((goal_len) > (candidate_len) ? (goal_len) : (candidate_len )) / 2; | |||
299 | } | |||
300 | ||||
301 | /* Verify that the cutoff for "meaningfulness" of suggestions is at least as | |||
302 | conservative as in older GCC releases. | |||
303 | ||||
304 | This should ensure that we don't offer additional meaningless | |||
305 | suggestions (apart from those for which transposition has helped). */ | |||
306 | ||||
307 | static void | |||
308 | test_get_edit_distance_cutoff () | |||
309 | { | |||
310 | for (size_t goal_len = 0; goal_len < 30; goal_len++) | |||
311 | for (size_t candidate_len = 0; candidate_len < 30; candidate_len++) | |||
312 | ASSERT_TRUE (get_edit_distance_cutoff (goal_len, candidate_len)do { const char *desc_ = "ASSERT_TRUE (" "(get_edit_distance_cutoff (goal_len, candidate_len) <= get_old_cutoff (goal_len, candidate_len))" ")"; bool actual_ = ((get_edit_distance_cutoff (goal_len, candidate_len ) <= get_old_cutoff (goal_len, candidate_len))); if (actual_ ) ::selftest::pass (((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 313, __FUNCTION__))), desc_); else ::selftest::fail (((::selftest ::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 313, __FUNCTION__))), desc_); } while (0) | |||
313 | <= get_old_cutoff (goal_len, candidate_len))do { const char *desc_ = "ASSERT_TRUE (" "(get_edit_distance_cutoff (goal_len, candidate_len) <= get_old_cutoff (goal_len, candidate_len))" ")"; bool actual_ = ((get_edit_distance_cutoff (goal_len, candidate_len ) <= get_old_cutoff (goal_len, candidate_len))); if (actual_ ) ::selftest::pass (((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 313, __FUNCTION__))), desc_); else ::selftest::fail (((::selftest ::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 313, __FUNCTION__))), desc_); } while (0); | |||
314 | } | |||
315 | ||||
316 | /* Assert that CANDIDATE is offered as a suggestion for TARGET. */ | |||
317 | ||||
318 | static void | |||
319 | assert_suggested_for (const location &loc, const char *candidate, | |||
320 | const char *target) | |||
321 | { | |||
322 | auto_vec<const char *> candidates; | |||
323 | candidates.safe_push (candidate); | |||
324 | ASSERT_EQ_AT (loc, candidate, find_closest_string (target, &candidates))do { const char *desc_ = "ASSERT_EQ (" "candidate" ", " "find_closest_string (target, &candidates)" ")"; if ((candidate) == (find_closest_string (target, &candidates ))) ::selftest::pass ((loc), desc_); else ::selftest::fail (( loc), desc_); } while (0); | |||
325 | } | |||
326 | ||||
327 | /* Assert that CANDIDATE is offered as a suggestion for TARGET. */ | |||
328 | ||||
329 | #define ASSERT_SUGGESTED_FOR(CANDIDATE, TARGET)do { assert_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 329, __FUNCTION__)), CANDIDATE, TARGET); } while (0) \ | |||
330 | SELFTEST_BEGIN_STMTdo { \ | |||
331 | assert_suggested_for (SELFTEST_LOCATION(::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 331, __FUNCTION__)), CANDIDATE, TARGET); \ | |||
332 | SELFTEST_END_STMT} while (0) | |||
333 | ||||
334 | /* Assert that CANDIDATE is not offered as a suggestion for TARGET. */ | |||
335 | ||||
336 | static void | |||
337 | assert_not_suggested_for (const location &loc, const char *candidate, | |||
338 | const char *target) | |||
339 | { | |||
340 | auto_vec<const char *> candidates; | |||
341 | candidates.safe_push (candidate); | |||
342 | ASSERT_EQ_AT (loc, NULL, find_closest_string (target, &candidates))do { const char *desc_ = "ASSERT_EQ (" "NULL" ", " "find_closest_string (target, &candidates)" ")"; if ((nullptr) == (find_closest_string (target, &candidates ))) ::selftest::pass ((loc), desc_); else ::selftest::fail (( loc), desc_); } while (0); | |||
343 | } | |||
344 | ||||
345 | /* Assert that CANDIDATE is not offered as a suggestion for TARGET. */ | |||
346 | ||||
347 | #define ASSERT_NOT_SUGGESTED_FOR(CANDIDATE, TARGET)do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 347, __FUNCTION__)), CANDIDATE, TARGET); } while (0) \ | |||
348 | SELFTEST_BEGIN_STMTdo { \ | |||
349 | assert_not_suggested_for (SELFTEST_LOCATION(::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 349, __FUNCTION__)), CANDIDATE, TARGET); \ | |||
350 | SELFTEST_END_STMT} while (0) | |||
351 | ||||
352 | /* Verify that we offer varous suggestions that are meaningful, | |||
353 | and that we don't offer various other ones that aren't (PR c/82967). */ | |||
354 | ||||
355 | static void | |||
356 | test_suggestions () | |||
357 | { | |||
358 | /* Good suggestions. */ | |||
359 | ||||
360 | ASSERT_SUGGESTED_FOR ("m_bar", "bar")do { assert_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 360, __FUNCTION__)), "m_bar", "bar"); } while (0); | |||
361 | // dist == 2, max_length == 5, min_length == 3 | |||
362 | ||||
363 | ASSERT_SUGGESTED_FOR ("MACRO", "MACRAME")do { assert_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 363, __FUNCTION__)), "MACRO", "MACRAME"); } while (0); | |||
364 | // dist == 3, max_length == 7, min_length == 5 | |||
365 | ||||
366 | ASSERT_SUGGESTED_FOR ("gtk_widget_show_all", "GtkWidgetShowAll")do { assert_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 366, __FUNCTION__)), "gtk_widget_show_all", "GtkWidgetShowAll" ); } while (0); | |||
367 | // dist == 7, max_length == 16, min_length = 19 | |||
368 | ||||
369 | ASSERT_SUGGESTED_FOR ("ab", "ac")do { assert_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 369, __FUNCTION__)), "ab", "ac"); } while (0); | |||
370 | // dist == 1, max_length == min_length = 2 | |||
371 | ||||
372 | ASSERT_SUGGESTED_FOR ("ab", "a")do { assert_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 372, __FUNCTION__)), "ab", "a"); } while (0); | |||
373 | // dist == 1, max_length == 2, min_length = 1 | |||
374 | ||||
375 | /* Bad suggestions. */ | |||
376 | ||||
377 | ASSERT_NOT_SUGGESTED_FOR ("a", "b")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 377, __FUNCTION__)), "a", "b"); } while (0); | |||
378 | // dist == 1, max_length == min_length = 1 | |||
379 | ||||
380 | ASSERT_NOT_SUGGESTED_FOR ("sqrt", "assert")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 380, __FUNCTION__)), "sqrt", "assert"); } while (0); | |||
381 | // dist == 3, max_length 6, min_length == 4 | |||
382 | ||||
383 | ASSERT_NOT_SUGGESTED_FOR ("INT8_MAX", "PATH_MAX")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 383, __FUNCTION__)), "INT8_MAX", "PATH_MAX"); } while (0); | |||
384 | // dist == 3, max_length == min_length == 8 | |||
385 | ||||
386 | ASSERT_NOT_SUGGESTED_FOR ("nice", "time")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 386, __FUNCTION__)), "nice", "time"); } while (0); | |||
387 | ASSERT_NOT_SUGGESTED_FOR ("nanl", "name")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 387, __FUNCTION__)), "nanl", "name"); } while (0); | |||
388 | // dist == 2, max_length == min_length == 4 | |||
389 | ||||
390 | ASSERT_NOT_SUGGESTED_FOR ("carg", "bar")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 390, __FUNCTION__)), "carg", "bar"); } while (0); | |||
391 | ASSERT_NOT_SUGGESTED_FOR ("char", "bar")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 391, __FUNCTION__)), "char", "bar"); } while (0); | |||
392 | // dist == 2, max_length == 4, min_length == 3 | |||
393 | ||||
394 | ASSERT_NOT_SUGGESTED_FOR ("-optimize", "fsanitize")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 394, __FUNCTION__)), "-optimize", "fsanitize"); } while (0); | |||
395 | // dist == 5, max_length == min_length == 9 | |||
396 | ||||
397 | ASSERT_NOT_SUGGESTED_FOR ("__DATE__", "__i386__")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 397, __FUNCTION__)), "__DATE__", "__i386__"); } while (0); | |||
398 | // dist == 4, max_length == min_length == 8 | |||
399 | ||||
400 | ASSERT_NOT_SUGGESTED_FOR ("start_input_device", "InputDevice")do { assert_not_suggested_for ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 400, __FUNCTION__)), "start_input_device", "InputDevice"); } while (0); | |||
401 | // dist == 9, max_length == 18, min_length == 11 | |||
402 | } | |||
403 | ||||
404 | /* Verify that find_closest_string is sane. */ | |||
405 | ||||
406 | static void | |||
407 | test_find_closest_string () | |||
408 | { | |||
409 | auto_vec<const char *> candidates; | |||
410 | ||||
411 | /* Verify that it can handle an empty vec. */ | |||
412 | ASSERT_EQ (NULL, find_closest_string ("", &candidates))do { const char *desc_ = "ASSERT_EQ (" "(nullptr)" ", " "(find_closest_string (\"\", &candidates))" ")"; if (((nullptr)) == ((find_closest_string ("", &candidates )))) ::selftest::pass ((((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 412, __FUNCTION__)))), desc_); else ::selftest::fail ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 412, __FUNCTION__)))), desc_); } while (0); | |||
413 | ||||
414 | /* Verify that it works sanely for non-empty vecs. */ | |||
415 | candidates.safe_push ("apple"); | |||
416 | candidates.safe_push ("banana"); | |||
417 | candidates.safe_push ("cherry"); | |||
418 | ||||
419 | ASSERT_STREQ ("apple", find_closest_string ("app", &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 419, __FUNCTION__)), "\"apple\"", "find_closest_string (\"app\", &candidates)" , ("apple"), (find_closest_string ("app", &candidates))); } while (0); | |||
420 | ASSERT_STREQ ("banana", find_closest_string ("banyan", &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 420, __FUNCTION__)), "\"banana\"", "find_closest_string (\"banyan\", &candidates)" , ("banana"), (find_closest_string ("banyan", &candidates ))); } while (0); | |||
421 | ASSERT_STREQ ("cherry", find_closest_string ("berry", &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 421, __FUNCTION__)), "\"cherry\"", "find_closest_string (\"berry\", &candidates)" , ("cherry"), (find_closest_string ("berry", &candidates) )); } while (0); | |||
422 | ASSERT_EQ (NULL, find_closest_string ("not like the others", &candidates))do { const char *desc_ = "ASSERT_EQ (" "(nullptr)" ", " "(find_closest_string (\"not like the others\", &candidates))" ")"; if (((nullptr)) == ((find_closest_string ("not like the others" , &candidates)))) ::selftest::pass ((((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 422, __FUNCTION__)))), desc_); else ::selftest::fail ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 422, __FUNCTION__)))), desc_); } while (0); | |||
423 | ||||
424 | /* The order of the vec can matter, but it should not matter for these | |||
425 | inputs. */ | |||
426 | candidates.truncate (0); | |||
427 | candidates.safe_push ("cherry"); | |||
428 | candidates.safe_push ("banana"); | |||
429 | candidates.safe_push ("apple"); | |||
430 | ASSERT_STREQ ("apple", find_closest_string ("app", &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 430, __FUNCTION__)), "\"apple\"", "find_closest_string (\"app\", &candidates)" , ("apple"), (find_closest_string ("app", &candidates))); } while (0); | |||
431 | ASSERT_STREQ ("banana", find_closest_string ("banyan", &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 431, __FUNCTION__)), "\"banana\"", "find_closest_string (\"banyan\", &candidates)" , ("banana"), (find_closest_string ("banyan", &candidates ))); } while (0); | |||
432 | ASSERT_STREQ ("cherry", find_closest_string ("berry", &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 432, __FUNCTION__)), "\"cherry\"", "find_closest_string (\"berry\", &candidates)" , ("cherry"), (find_closest_string ("berry", &candidates) )); } while (0); | |||
433 | ASSERT_EQ (NULL, find_closest_string ("not like the others", &candidates))do { const char *desc_ = "ASSERT_EQ (" "(nullptr)" ", " "(find_closest_string (\"not like the others\", &candidates))" ")"; if (((nullptr)) == ((find_closest_string ("not like the others" , &candidates)))) ::selftest::pass ((((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 433, __FUNCTION__)))), desc_); else ::selftest::fail ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 433, __FUNCTION__)))), desc_); } while (0); | |||
434 | ||||
435 | /* If the goal string somehow makes it into the candidate list, offering | |||
436 | it as a suggestion will be nonsensical. Verify that we don't offer such | |||
437 | suggestions. */ | |||
438 | ASSERT_EQ (NULL, find_closest_string ("banana", &candidates))do { const char *desc_ = "ASSERT_EQ (" "(nullptr)" ", " "(find_closest_string (\"banana\", &candidates))" ")"; if (((nullptr)) == ((find_closest_string ("banana", & candidates)))) ::selftest::pass ((((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 438, __FUNCTION__)))), desc_); else ::selftest::fail ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 438, __FUNCTION__)))), desc_); } while (0); | |||
439 | ||||
440 | /* Example from PR 69968 where transposition helps. */ | |||
441 | candidates.truncate (0); | |||
442 | candidates.safe_push("coordx"); | |||
443 | candidates.safe_push("coordy"); | |||
444 | candidates.safe_push("coordz"); | |||
445 | candidates.safe_push("coordx1"); | |||
446 | candidates.safe_push("coordy1"); | |||
447 | candidates.safe_push("coordz1"); | |||
448 | ASSERT_STREQ ("coordz1", find_closest_string ("coorzd1", &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 448, __FUNCTION__)), "\"coordz1\"", "find_closest_string (\"coorzd1\", &candidates)" , ("coordz1"), (find_closest_string ("coorzd1", &candidates ))); } while (0); | |||
449 | ||||
450 | candidates.truncate (0); | |||
451 | candidates.safe_push ("DWARF_GNAT_ENCODINGS_GDB"); | |||
452 | candidates.safe_push ("DWARF_GNAT_ENCODINGS_ALL"); | |||
453 | candidates.safe_push ("DWARF_GNAT_ENCODINGS_MINIMAL"); | |||
454 | ASSERT_STREQ ("DWARF_GNAT_ENCODINGS_ALL",do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 456, __FUNCTION__)), "\"DWARF_GNAT_ENCODINGS_ALL\"", "find_closest_string (\"DWARF_GNAT_ENCODINGS_all\", &candidates)" , ("DWARF_GNAT_ENCODINGS_ALL"), (find_closest_string ("DWARF_GNAT_ENCODINGS_all" , &candidates))); } while (0) | |||
455 | find_closest_string ("DWARF_GNAT_ENCODINGS_all",do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 456, __FUNCTION__)), "\"DWARF_GNAT_ENCODINGS_ALL\"", "find_closest_string (\"DWARF_GNAT_ENCODINGS_all\", &candidates)" , ("DWARF_GNAT_ENCODINGS_ALL"), (find_closest_string ("DWARF_GNAT_ENCODINGS_all" , &candidates))); } while (0) | |||
456 | &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 456, __FUNCTION__)), "\"DWARF_GNAT_ENCODINGS_ALL\"", "find_closest_string (\"DWARF_GNAT_ENCODINGS_all\", &candidates)" , ("DWARF_GNAT_ENCODINGS_ALL"), (find_closest_string ("DWARF_GNAT_ENCODINGS_all" , &candidates))); } while (0); | |||
457 | ||||
458 | /* The same as the previous test, but with a different order of | |||
459 | candidates. */ | |||
460 | candidates.truncate (0); | |||
461 | candidates.safe_push ("DWARF_GNAT_ENCODINGS_ALL"); | |||
462 | candidates.safe_push ("DWARF_GNAT_ENCODINGS_GDB"); | |||
463 | candidates.safe_push ("DWARF_GNAT_ENCODINGS_MINIMAL"); | |||
464 | ASSERT_STREQ ("DWARF_GNAT_ENCODINGS_ALL",do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 466, __FUNCTION__)), "\"DWARF_GNAT_ENCODINGS_ALL\"", "find_closest_string (\"DWARF_GNAT_ENCODINGS_all\", &candidates)" , ("DWARF_GNAT_ENCODINGS_ALL"), (find_closest_string ("DWARF_GNAT_ENCODINGS_all" , &candidates))); } while (0) | |||
465 | find_closest_string ("DWARF_GNAT_ENCODINGS_all",do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 466, __FUNCTION__)), "\"DWARF_GNAT_ENCODINGS_ALL\"", "find_closest_string (\"DWARF_GNAT_ENCODINGS_all\", &candidates)" , ("DWARF_GNAT_ENCODINGS_ALL"), (find_closest_string ("DWARF_GNAT_ENCODINGS_all" , &candidates))); } while (0) | |||
466 | &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 466, __FUNCTION__)), "\"DWARF_GNAT_ENCODINGS_ALL\"", "find_closest_string (\"DWARF_GNAT_ENCODINGS_all\", &candidates)" , ("DWARF_GNAT_ENCODINGS_ALL"), (find_closest_string ("DWARF_GNAT_ENCODINGS_all" , &candidates))); } while (0); | |||
467 | ||||
468 | /* Example from PR 105564 where option name with missing equal | |||
469 | sign should win. */ | |||
470 | candidates.truncate (0); | |||
471 | candidates.safe_push ("-Wtrivial-auto-var-init"); | |||
472 | candidates.safe_push ("-ftrivial-auto-var-init="); | |||
473 | ASSERT_STREQ ("-ftrivial-auto-var-init=",do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 475, __FUNCTION__)), "\"-ftrivial-auto-var-init=\"", "find_closest_string (\"-ftrivial-auto-var-init\", &candidates)" , ("-ftrivial-auto-var-init="), (find_closest_string ("-ftrivial-auto-var-init" , &candidates))); } while (0) | |||
474 | find_closest_string ("-ftrivial-auto-var-init",do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 475, __FUNCTION__)), "\"-ftrivial-auto-var-init=\"", "find_closest_string (\"-ftrivial-auto-var-init\", &candidates)" , ("-ftrivial-auto-var-init="), (find_closest_string ("-ftrivial-auto-var-init" , &candidates))); } while (0) | |||
475 | &candidates))do { ::selftest::assert_streq ((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 475, __FUNCTION__)), "\"-ftrivial-auto-var-init=\"", "find_closest_string (\"-ftrivial-auto-var-init\", &candidates)" , ("-ftrivial-auto-var-init="), (find_closest_string ("-ftrivial-auto-var-init" , &candidates))); } while (0); | |||
476 | } | |||
477 | ||||
478 | /* Test data for test_metric_conditions. */ | |||
479 | ||||
480 | static const char * const test_data[] = { | |||
481 | "", | |||
482 | "foo", | |||
483 | "food", | |||
484 | "boo", | |||
485 | "1234567890123456789012345678901234567890123456789012345678901234567890", | |||
486 | "abc", | |||
487 | "ac", | |||
488 | "ca", | |||
489 | }; | |||
490 | ||||
491 | /* Verify that get_edit_distance appears to be a sane distance function, | |||
492 | even though it doesn't satisfy the conditions for being a metric. (This | |||
493 | is because the triangle inequality fails to hold: the distance between | |||
494 | "ca" and "ac" is 1, and so is the distance between "abc" and "ac", but | |||
495 | the distance between "abc" and "ca" is 3. Algorithms that calculate the | |||
496 | true Levenshtein-Damerau metric are much more expensive.) */ | |||
497 | ||||
498 | static void | |||
499 | test_metric_conditions () | |||
500 | { | |||
501 | const int num_test_cases = ARRAY_SIZE (test_data)(sizeof (test_data) / sizeof ((test_data)[0])); | |||
502 | ||||
503 | for (int i = 0; i < num_test_cases; i++) | |||
504 | { | |||
505 | for (int j = 0; j < num_test_cases; j++) | |||
506 | { | |||
507 | edit_distance_t dist_ij | |||
508 | = get_edit_distance (test_data[i], test_data[j]); | |||
509 | ||||
510 | /* Identity of indiscernibles: d(i, j) > 0 iff i == j. */ | |||
511 | if (i == j) | |||
512 | ASSERT_EQ (dist_ij, 0)do { const char *desc_ = "ASSERT_EQ (" "(dist_ij)" ", " "(0)" ")"; if (((dist_ij)) == ((0))) ::selftest::pass ((((::selftest ::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 512, __FUNCTION__)))), desc_); else ::selftest::fail ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 512, __FUNCTION__)))), desc_); } while (0); | |||
513 | else | |||
514 | ASSERT_TRUE (dist_ij > 0)do { const char *desc_ = "ASSERT_TRUE (" "(dist_ij > 0)" ")" ; bool actual_ = ((dist_ij > 0)); if (actual_) ::selftest:: pass (((::selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 514, __FUNCTION__))), desc_); else ::selftest::fail (((::selftest ::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 514, __FUNCTION__))), desc_); } while (0); | |||
515 | ||||
516 | /* Symmetry: d(i, j) == d(j, i). */ | |||
517 | edit_distance_t dist_ji | |||
518 | = get_edit_distance (test_data[j], test_data[i]); | |||
519 | ASSERT_EQ (dist_ij, dist_ji)do { const char *desc_ = "ASSERT_EQ (" "(dist_ij)" ", " "(dist_ji)" ")"; if (((dist_ij)) == ((dist_ji))) ::selftest::pass ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 519, __FUNCTION__)))), desc_); else ::selftest::fail ((((:: selftest::location ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.cc" , 519, __FUNCTION__)))), desc_); } while (0); | |||
520 | } | |||
521 | } | |||
522 | } | |||
523 | ||||
524 | /* Run all of the selftests within this file. */ | |||
525 | ||||
526 | void | |||
527 | spellcheck_cc_tests () | |||
528 | { | |||
529 | test_edit_distances (); | |||
530 | test_get_edit_distance_cutoff (); | |||
531 | test_suggestions (); | |||
532 | test_find_closest_string (); | |||
| ||||
533 | test_metric_conditions (); | |||
534 | } | |||
535 | ||||
536 | } // namespace selftest | |||
537 | ||||
538 | #endif /* #if CHECKING_P */ |
1 | /* Find near-matches for strings and identifiers. | ||||
2 | Copyright (C) 2015-2023 Free Software Foundation, Inc. | ||||
3 | |||||
4 | This file is part of GCC. | ||||
5 | |||||
6 | GCC is free software; you can redistribute it and/or modify it under | ||||
7 | the terms of the GNU General Public License as published by the Free | ||||
8 | Software Foundation; either version 3, or (at your option) any later | ||||
9 | version. | ||||
10 | |||||
11 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY | ||||
12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||||
14 | for more details. | ||||
15 | |||||
16 | You should have received a copy of the GNU General Public License | ||||
17 | along with GCC; see the file COPYING3. If not see | ||||
18 | <http://www.gnu.org/licenses/>. */ | ||||
19 | |||||
20 | #ifndef GCC_SPELLCHECK_H | ||||
21 | #define GCC_SPELLCHECK_H | ||||
22 | |||||
23 | typedef unsigned int edit_distance_t; | ||||
24 | const edit_distance_t MAX_EDIT_DISTANCE = UINT_MAX(2147483647 *2U +1U); | ||||
25 | |||||
26 | /* spellcheck.cc */ | ||||
27 | extern edit_distance_t | ||||
28 | get_edit_distance (const char *s, int len_s, | ||||
29 | const char *t, int len_t); | ||||
30 | |||||
31 | extern edit_distance_t | ||||
32 | get_edit_distance (const char *s, const char *t); | ||||
33 | |||||
34 | extern const char * | ||||
35 | find_closest_string (const char *target, | ||||
36 | const auto_vec<const char *> *candidates); | ||||
37 | |||||
38 | /* A traits class for describing a string-like type usable by | ||||
39 | class best_match. | ||||
40 | Specializations should provide the implementations of the following: | ||||
41 | |||||
42 | static size_t get_length (TYPE); | ||||
43 | static const char *get_string (TYPE); | ||||
44 | |||||
45 | get_string should return a non-NULL ptr, which does not need to be | ||||
46 | 0-terminated. */ | ||||
47 | |||||
48 | template <typename TYPE> | ||||
49 | struct edit_distance_traits {}; | ||||
50 | |||||
51 | /* Specialization of edit_distance_traits for C-style strings. */ | ||||
52 | |||||
53 | template <> | ||||
54 | struct edit_distance_traits<const char *> | ||||
55 | { | ||||
56 | static size_t get_length (const char *str) | ||||
57 | { | ||||
58 | gcc_assert (str)((void)(!(str) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.h" , 58, __FUNCTION__), 0 : 0)); | ||||
59 | return strlen (str); | ||||
60 | } | ||||
61 | |||||
62 | static const char *get_string (const char *str) | ||||
63 | { | ||||
64 | gcc_assert (str)((void)(!(str) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.h" , 64, __FUNCTION__), 0 : 0)); | ||||
65 | return str; | ||||
66 | } | ||||
67 | }; | ||||
68 | |||||
69 | extern edit_distance_t get_edit_distance_cutoff (size_t goal_len, | ||||
70 | size_t candidate_len); | ||||
71 | |||||
72 | /* A type for use when determining the best match against a string, | ||||
73 | expressed as a template so that we can match against various | ||||
74 | string-like types (const char *, frontend identifiers, and preprocessor | ||||
75 | macros). | ||||
76 | |||||
77 | This type accumulates the best possible match against GOAL_TYPE for | ||||
78 | a sequence of elements of CANDIDATE_TYPE, whilst minimizing the | ||||
79 | number of calls to get_edit_distance and to | ||||
80 | edit_distance_traits<T>::get_length. */ | ||||
81 | |||||
82 | template <typename GOAL_TYPE, typename CANDIDATE_TYPE> | ||||
83 | class best_match | ||||
84 | { | ||||
85 | public: | ||||
86 | typedef GOAL_TYPE goal_t; | ||||
87 | typedef CANDIDATE_TYPE candidate_t; | ||||
88 | typedef edit_distance_traits<goal_t> goal_traits; | ||||
89 | typedef edit_distance_traits<candidate_t> candidate_traits; | ||||
90 | |||||
91 | /* Constructor. */ | ||||
92 | |||||
93 | best_match (GOAL_TYPE goal, | ||||
94 | edit_distance_t best_distance_so_far = MAX_EDIT_DISTANCE) | ||||
95 | : m_goal (goal_traits::get_string (goal)), | ||||
96 | m_goal_len (goal_traits::get_length (goal)), | ||||
97 | m_best_candidate (NULLnullptr), | ||||
98 | m_best_distance (best_distance_so_far), | ||||
99 | m_best_candidate_len (0) | ||||
100 | {} | ||||
101 | |||||
102 | /* Compare the edit distance between CANDIDATE and m_goal, | ||||
103 | and if it's the best so far, record it. */ | ||||
104 | |||||
105 | void consider (candidate_t candidate) | ||||
106 | { | ||||
107 | size_t candidate_len = candidate_traits::get_length (candidate); | ||||
108 | |||||
109 | /* Calculate a lower bound on the candidate's distance to the goal, | ||||
110 | based on the difference in lengths; it will require at least | ||||
111 | this many insertions/deletions. */ | ||||
112 | edit_distance_t min_candidate_distance | ||||
113 | = abs ((ssize_t)candidate_len - (ssize_t)m_goal_len); | ||||
114 | |||||
115 | /* If the candidate's length is sufficiently different to that | ||||
116 | of the goal string, then the number of insertions/deletions | ||||
117 | may be >= the best distance so far. If so, we can reject | ||||
118 | the candidate immediately without needing to compute | ||||
119 | the exact distance, since it won't be an improvement. */ | ||||
120 | if (min_candidate_distance >= m_best_distance) | ||||
121 | return; | ||||
122 | |||||
123 | /* If the candidate will be unable to beat the criterion in | ||||
124 | get_best_meaningful_candidate, reject it without computing | ||||
125 | the exact distance. */ | ||||
126 | edit_distance_t cutoff = get_cutoff (candidate_len); | ||||
127 | if (min_candidate_distance > cutoff) | ||||
128 | return; | ||||
129 | |||||
130 | /* Otherwise, compute the distance and see if the candidate | ||||
131 | has beaten the previous best value. */ | ||||
132 | const char *candidate_str = candidate_traits::get_string (candidate); | ||||
133 | edit_distance_t dist | ||||
134 | = get_edit_distance (m_goal, m_goal_len, candidate_str, candidate_len); | ||||
135 | |||||
136 | bool is_better = false; | ||||
137 | if (dist < m_best_distance) | ||||
138 | is_better = true; | ||||
139 | else if (dist
| ||||
140 | { | ||||
141 | /* Prefer a candidate that inserts a trailing '=', | ||||
142 | so that for | ||||
143 | "-ftrivial-auto-var-init" | ||||
144 | we suggest | ||||
145 | "-ftrivial-auto-var-init=" | ||||
146 | rather than | ||||
147 | "-Wtrivial-auto-var-init". */ | ||||
148 | /* Prefer a candidate has a difference in trailing sign character. */ | ||||
149 | if (candidate_str[candidate_len - 1] == '=' | ||||
150 | && m_goal[m_goal_len - 1] != '=') | ||||
| |||||
151 | is_better = true; | ||||
152 | } | ||||
153 | |||||
154 | if (is_better) | ||||
155 | { | ||||
156 | m_best_distance = dist; | ||||
157 | m_best_candidate = candidate; | ||||
158 | m_best_candidate_len = candidate_len; | ||||
159 | } | ||||
160 | } | ||||
161 | |||||
162 | /* Assuming that BEST_CANDIDATE is known to be better than | ||||
163 | m_best_candidate, update (without recomputing the edit distance to | ||||
164 | the goal). */ | ||||
165 | |||||
166 | void set_best_so_far (CANDIDATE_TYPE best_candidate, | ||||
167 | edit_distance_t best_distance, | ||||
168 | size_t best_candidate_len) | ||||
169 | { | ||||
170 | gcc_assert (best_distance < m_best_distance)((void)(!(best_distance < m_best_distance) ? fancy_abort ( "/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/spellcheck.h" , 170, __FUNCTION__), 0 : 0)); | ||||
171 | m_best_candidate = best_candidate; | ||||
172 | m_best_distance = best_distance; | ||||
173 | m_best_candidate_len = best_candidate_len; | ||||
174 | } | ||||
175 | |||||
176 | /* Generate the maximum edit distance for which we consider a suggestion | ||||
177 | to be meaningful, given a candidate of length CANDIDATE_LEN. */ | ||||
178 | |||||
179 | edit_distance_t get_cutoff (size_t candidate_len) const | ||||
180 | { | ||||
181 | return ::get_edit_distance_cutoff (m_goal_len, candidate_len); | ||||
182 | } | ||||
183 | |||||
184 | /* Get the best candidate so far, but applying a filter to ensure | ||||
185 | that we return NULL if none of the candidates are close to the goal, | ||||
186 | to avoid offering nonsensical suggestions to the user. */ | ||||
187 | |||||
188 | candidate_t get_best_meaningful_candidate () const | ||||
189 | { | ||||
190 | /* If the edit distance is too high, the suggestion is likely to be | ||||
191 | meaningless. */ | ||||
192 | if (m_best_candidate) | ||||
193 | { | ||||
194 | edit_distance_t cutoff = get_cutoff (m_best_candidate_len); | ||||
195 | if (m_best_distance > cutoff) | ||||
196 | return NULLnullptr; | ||||
197 | } | ||||
198 | |||||
199 | /* If the goal string somehow makes it into the candidate list, offering | ||||
200 | it as a suggestion will be nonsensical e.g. | ||||
201 | 'constexpr' does not name a type; did you mean 'constexpr'? | ||||
202 | Ultimately such suggestions are due to bugs in constructing the | ||||
203 | candidate list, but as a band-aid, do not offer suggestions for | ||||
204 | distance == 0 (where candidate == goal). */ | ||||
205 | if (m_best_distance == 0) | ||||
206 | return NULLnullptr; | ||||
207 | |||||
208 | return m_best_candidate; | ||||
209 | } | ||||
210 | |||||
211 | /* Get the closest candidate so far, without applying any filtering. */ | ||||
212 | |||||
213 | candidate_t blithely_get_best_candidate () const | ||||
214 | { | ||||
215 | return m_best_candidate; | ||||
216 | } | ||||
217 | |||||
218 | edit_distance_t get_best_distance () const { return m_best_distance; } | ||||
219 | size_t get_best_candidate_length () const { return m_best_candidate_len; } | ||||
220 | |||||
221 | private: | ||||
222 | const char *m_goal; | ||||
223 | size_t m_goal_len; | ||||
224 | candidate_t m_best_candidate; | ||||
225 | edit_distance_t m_best_distance; | ||||
226 | size_t m_best_candidate_len; | ||||
227 | }; | ||||
228 | |||||
229 | #endif /* GCC_SPELLCHECK_H */ |