1 | /* Provide option suggestion for --complete option and a misspelled |
2 | used by a user. |
3 | Copyright (C) 2016-2024 Free Software Foundation, Inc. |
4 | |
5 | This file is part of GCC. |
6 | |
7 | GCC is free software; you can redistribute it and/or modify it under |
8 | the terms of the GNU General Public License as published by the Free |
9 | Software Foundation; either version 3, or (at your option) any later |
10 | version. |
11 | |
12 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
13 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
14 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
15 | for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along with GCC; see the file COPYING3. If not see |
19 | <http://www.gnu.org/licenses/>. */ |
20 | |
21 | #include "config.h" |
22 | #include "system.h" |
23 | #include "coretypes.h" |
24 | #include "tm.h" |
25 | #include "opts.h" |
26 | #include "spellcheck.h" |
27 | #include "opt-suggestions.h" |
28 | #include "common/common-target.h" |
29 | #include "selftest.h" |
30 | |
31 | option_proposer::~option_proposer () |
32 | { |
33 | delete m_option_suggestions; |
34 | } |
35 | |
36 | const char * |
37 | option_proposer::suggest_option (const char *bad_opt) |
38 | { |
39 | /* Lazily populate m_option_suggestions. */ |
40 | if (!m_option_suggestions) |
41 | build_option_suggestions (NULL); |
42 | gcc_assert (m_option_suggestions); |
43 | |
44 | /* "m_option_suggestions" is now populated. Use it. */ |
45 | return find_closest_string |
46 | (target: bad_opt, |
47 | candidates: (auto_vec <const char *> *) m_option_suggestions); |
48 | } |
49 | |
50 | /* Populate RESULTS with valid completions of options that begin |
51 | with OPTION_PREFIX. */ |
52 | |
53 | void |
54 | option_proposer::get_completions (const char *option_prefix, |
55 | auto_string_vec &results) |
56 | { |
57 | /* Bail out for an invalid input. */ |
58 | if (option_prefix == NULL || option_prefix[0] == '\0') |
59 | return; |
60 | |
61 | /* Option suggestions are built without first leading dash character. */ |
62 | if (option_prefix[0] == '-') |
63 | option_prefix++; |
64 | |
65 | size_t length = strlen (s: option_prefix); |
66 | |
67 | /* Lazily populate m_option_suggestions. */ |
68 | if (!m_option_suggestions) |
69 | build_option_suggestions (prefix: option_prefix); |
70 | gcc_assert (m_option_suggestions); |
71 | |
72 | for (unsigned i = 0; i < m_option_suggestions->length (); i++) |
73 | { |
74 | char *candidate = (*m_option_suggestions)[i]; |
75 | if (strlen (s: candidate) >= length |
76 | && strstr (haystack: candidate, needle: option_prefix) == candidate) |
77 | results.safe_push (obj: concat ("-" , candidate, NULL)); |
78 | } |
79 | } |
80 | |
81 | /* Print on stdout a list of valid options that begin with OPTION_PREFIX, |
82 | one per line, suitable for use by Bash completion. |
83 | |
84 | Implementation of the "-completion=" option. */ |
85 | |
86 | void |
87 | option_proposer::suggest_completion (const char *option_prefix) |
88 | { |
89 | auto_string_vec results; |
90 | get_completions (option_prefix, results); |
91 | for (unsigned i = 0; i < results.length (); i++) |
92 | printf (format: "%s\n" , results[i]); |
93 | } |
94 | |
95 | void |
96 | option_proposer::build_option_suggestions (const char *prefix) |
97 | { |
98 | gcc_assert (m_option_suggestions == NULL); |
99 | m_option_suggestions = new auto_string_vec (); |
100 | |
101 | /* We build a vec of m_option_suggestions, using add_misspelling_candidates |
102 | to add copies of strings, without a leading dash. */ |
103 | |
104 | for (unsigned int i = 0; i < cl_options_count; i++) |
105 | { |
106 | const struct cl_option *option = &cl_options[i]; |
107 | const char *opt_text = option->opt_text; |
108 | switch (i) |
109 | { |
110 | default: |
111 | if (option->var_type == CLVC_ENUM) |
112 | { |
113 | const struct cl_enum *e = &cl_enums[option->var_enum]; |
114 | for (unsigned j = 0; e->values[j].arg != NULL; j++) |
115 | { |
116 | char *with_arg = concat (opt_text, e->values[j].arg, NULL); |
117 | add_misspelling_candidates (candidates: m_option_suggestions, option, |
118 | base_option: with_arg); |
119 | free (ptr: with_arg); |
120 | } |
121 | |
122 | /* Add also variant without an option argument. */ |
123 | add_misspelling_candidates (candidates: m_option_suggestions, option, |
124 | base_option: opt_text); |
125 | } |
126 | else |
127 | { |
128 | bool option_added = false; |
129 | if (option->flags & CL_TARGET) |
130 | { |
131 | vec<const char *> option_values |
132 | = targetm_common.get_valid_option_values (i, prefix); |
133 | if (!option_values.is_empty ()) |
134 | { |
135 | option_added = true; |
136 | for (unsigned j = 0; j < option_values.length (); j++) |
137 | { |
138 | char *with_arg = concat (opt_text, option_values[j], |
139 | NULL); |
140 | add_misspelling_candidates (candidates: m_option_suggestions, option, |
141 | base_option: with_arg); |
142 | free (ptr: with_arg); |
143 | } |
144 | } |
145 | option_values.release (); |
146 | } |
147 | |
148 | if (!option_added) |
149 | add_misspelling_candidates (candidates: m_option_suggestions, option, |
150 | base_option: opt_text); |
151 | } |
152 | break; |
153 | |
154 | case OPT_fsanitize_: |
155 | case OPT_fsanitize_recover_: |
156 | /* -fsanitize= and -fsanitize-recover= can take |
157 | a comma-separated list of arguments. Given that combinations |
158 | are supported, we can't add all potential candidates to the |
159 | vec, but if we at least add them individually without commas, |
160 | we should do a better job e.g. correcting |
161 | "-sanitize=address" |
162 | to |
163 | "-fsanitize=address" |
164 | rather than to "-Wframe-address" (PR driver/69265). */ |
165 | { |
166 | /* Add also variant without an option argument. */ |
167 | add_misspelling_candidates (candidates: m_option_suggestions, option, |
168 | base_option: opt_text); |
169 | |
170 | for (int j = 0; sanitizer_opts[j].name != NULL; ++j) |
171 | { |
172 | struct cl_option optb; |
173 | /* -fsanitize=all is not valid, only -fno-sanitize=all. |
174 | So don't register the positive misspelling candidates |
175 | for it. */ |
176 | if (sanitizer_opts[j].flag == ~0U && i == OPT_fsanitize_) |
177 | { |
178 | optb = *option; |
179 | optb.opt_text = opt_text = "-fno-sanitize=" ; |
180 | optb.cl_reject_negative = true; |
181 | option = &optb; |
182 | } |
183 | /* Get one arg at a time e.g. "-fsanitize=address". */ |
184 | char *with_arg = concat (opt_text, |
185 | sanitizer_opts[j].name, |
186 | NULL); |
187 | /* Add with_arg and all of its variant spellings e.g. |
188 | "-fno-sanitize=address" to candidates (albeit without |
189 | leading dashes). */ |
190 | add_misspelling_candidates (candidates: m_option_suggestions, option, |
191 | base_option: with_arg); |
192 | free (ptr: with_arg); |
193 | } |
194 | } |
195 | break; |
196 | } |
197 | } |
198 | } |
199 | |
200 | #if CHECKING_P |
201 | |
202 | namespace selftest { |
203 | |
204 | /* Verify that PROPOSER generates sane auto-completion suggestions |
205 | for OPTION_PREFIX. */ |
206 | |
207 | static void |
208 | verify_autocompletions (option_proposer &proposer, const char *option_prefix) |
209 | { |
210 | auto_string_vec suggestions; |
211 | proposer.get_completions (option_prefix, results&: suggestions); |
212 | |
213 | /* There must be at least one suggestion, and every suggestion must |
214 | indeed begin with OPTION_PREFIX. */ |
215 | |
216 | ASSERT_GT (suggestions.length (), 0); |
217 | |
218 | for (unsigned i = 0; i < suggestions.length (); i++) |
219 | ASSERT_STR_STARTSWITH (suggestions[i], option_prefix); |
220 | } |
221 | |
222 | /* Verify that valid options are auto-completed correctly. */ |
223 | |
224 | static void |
225 | test_completion_valid_options (option_proposer &proposer) |
226 | { |
227 | const char *option_prefixes[] = |
228 | { |
229 | "-fno-var-tracking-assignments-toggle" , |
230 | "-fpredictive-commoning" , |
231 | "--param=stack-clash-protection-guard-size" , |
232 | "--param=max-predicted-iterations" , |
233 | "-ftree-loop-distribute-patterns" , |
234 | "-fno-var-tracking" , |
235 | "-Walloc-zero" , |
236 | "--param=ipa-cp-value-list-size" , |
237 | "-Wsync-nand" , |
238 | "-Wno-attributes" , |
239 | "--param=tracer-dynamic-coverage-feedback" , |
240 | "-Wno-format-contains-nul" , |
241 | "-Wnamespaces" , |
242 | "-fisolate-erroneous-paths-attribute" , |
243 | "-Wno-underflow" , |
244 | "-Wtarget-lifetime" , |
245 | "--param=asan-globals" , |
246 | "-Wno-empty-body" , |
247 | "-Wno-odr" , |
248 | "-Wformat-zero-length" , |
249 | "-Wstringop-truncation" , |
250 | "-fno-ipa-vrp" , |
251 | "-fmath-errno" , |
252 | "-Warray-temporaries" , |
253 | "-Wno-unused-label" , |
254 | "-Wreturn-local-addr" , |
255 | "--param=sms-dfa-history" , |
256 | "--param=asan-instrument-reads" , |
257 | "-Wreturn-type" , |
258 | "-Wc++17-compat" , |
259 | "-Wno-effc++" , |
260 | "--param=max-fields-for-field-sensitive" , |
261 | "-fisolate-erroneous-paths-dereference" , |
262 | "-fno-defer-pop" , |
263 | "-Wcast-align=strict" , |
264 | "-foptimize-strlen" , |
265 | "-Wpacked-not-aligned" , |
266 | "-funroll-loops" , |
267 | "-fif-conversion2" , |
268 | "-Wdesignated-init" , |
269 | "--param=max-iterations-computation-cost" , |
270 | "-Wmultiple-inheritance" , |
271 | "-fno-sel-sched-reschedule-pipelined" , |
272 | "-Wassign-intercept" , |
273 | "-Wno-format-security" , |
274 | "-fno-sched-stalled-insns" , |
275 | "-fno-tree-tail-merge" , |
276 | "-Wlong-long" , |
277 | "-Wno-unused-but-set-parameter" , |
278 | NULL |
279 | }; |
280 | |
281 | for (const char **ptr = option_prefixes; *ptr != NULL; ptr++) |
282 | verify_autocompletions (proposer, option_prefix: *ptr); |
283 | } |
284 | |
285 | /* Verify that valid parameters are auto-completed correctly, |
286 | both with the "--param=PARAM" form and the "--param PARAM" form. */ |
287 | |
288 | static void |
289 | test_completion_valid_params (option_proposer &proposer) |
290 | { |
291 | const char *option_prefixes[] = |
292 | { |
293 | "--param=sched-state-edge-prob-cutoff" , |
294 | "--param=iv-consider-all-candidates-bound" , |
295 | "--param=align-threshold" , |
296 | "--param=prefetch-min-insn-to-mem-ratio" , |
297 | "--param=max-unrolled-insns" , |
298 | "--param=max-early-inliner-iterations" , |
299 | "--param=max-vartrack-reverse-op-size" , |
300 | "--param=ipa-cp-loop-hint-bonus" , |
301 | "--param=tracer-min-branch-ratio" , |
302 | "--param=graphite-max-arrays-per-scop" , |
303 | "--param=sink-frequency-threshold" , |
304 | "--param=max-cse-path-length" , |
305 | "--param=sra-max-scalarization-size-Osize" , |
306 | "--param=prefetch-latency" , |
307 | "--param=dse-max-object-size" , |
308 | "--param=asan-globals" , |
309 | "--param=max-vartrack-size" , |
310 | "--param=case-values-threshold" , |
311 | "--param=max-slsr-cand-scan" , |
312 | "--param=min-insn-to-prefetch-ratio" , |
313 | "--param=tracer-min-branch-probability" , |
314 | "--param sink-frequency-threshold" , |
315 | "--param max-cse-path-length" , |
316 | "--param sra-max-scalarization-size-Osize" , |
317 | "--param prefetch-latency" , |
318 | "--param dse-max-object-size" , |
319 | "--param asan-globals" , |
320 | "--param max-vartrack-size" , |
321 | NULL |
322 | }; |
323 | |
324 | for (const char **ptr = option_prefixes; *ptr != NULL; ptr++) |
325 | verify_autocompletions (proposer, option_prefix: *ptr); |
326 | } |
327 | |
328 | /* Return true when EXPECTED is one of completions for OPTION_PREFIX string. */ |
329 | |
330 | static bool |
331 | in_completion_p (option_proposer &proposer, const char *option_prefix, |
332 | const char *expected) |
333 | { |
334 | auto_string_vec suggestions; |
335 | proposer.get_completions (option_prefix, results&: suggestions); |
336 | |
337 | for (unsigned i = 0; i < suggestions.length (); i++) |
338 | { |
339 | char *r = suggestions[i]; |
340 | if (strcmp (s1: r, s2: expected) == 0) |
341 | return true; |
342 | } |
343 | |
344 | return false; |
345 | } |
346 | |
347 | /* Return true when PROPOSER does not find any partial completion |
348 | for OPTION_PREFIX. */ |
349 | |
350 | static bool |
351 | empty_completion_p (option_proposer &proposer, const char *option_prefix) |
352 | { |
353 | auto_string_vec suggestions; |
354 | proposer.get_completions (option_prefix, results&: suggestions); |
355 | return suggestions.is_empty (); |
356 | } |
357 | |
358 | /* Verify autocompletions of partially-complete options. */ |
359 | |
360 | static void |
361 | test_completion_partial_match (option_proposer &proposer) |
362 | { |
363 | ASSERT_TRUE (in_completion_p (proposer, "-fsani" , "-fsanitize=address" )); |
364 | ASSERT_TRUE (in_completion_p (proposer, "-fsani" , |
365 | "-fsanitize-address-use-after-scope" )); |
366 | ASSERT_TRUE (in_completion_p (proposer, "-fipa-icf" , "-fipa-icf-functions" )); |
367 | ASSERT_TRUE (in_completion_p (proposer, "-fipa-icf" , "-fipa-icf" )); |
368 | ASSERT_TRUE (in_completion_p (proposer, "--param=" , |
369 | "--param=max-vartrack-reverse-op-size=" )); |
370 | ASSERT_TRUE (in_completion_p (proposer, "--param " , |
371 | "--param max-vartrack-reverse-op-size=" )); |
372 | |
373 | ASSERT_FALSE (in_completion_p (proposer, "-fipa-icf" , "-fipa" )); |
374 | ASSERT_FALSE (in_completion_p (proposer, "-fipa-icf-functions" , "-fipa-icf" )); |
375 | |
376 | ASSERT_FALSE (empty_completion_p (proposer, "-" )); |
377 | ASSERT_FALSE (empty_completion_p (proposer, "-fipa" )); |
378 | ASSERT_FALSE (empty_completion_p (proposer, "--par" )); |
379 | } |
380 | |
381 | /* Verify that autocompletion does not return any match for garbage inputs. */ |
382 | |
383 | static void |
384 | test_completion_garbage (option_proposer &proposer) |
385 | { |
386 | ASSERT_TRUE (empty_completion_p (proposer, NULL)); |
387 | ASSERT_TRUE (empty_completion_p (proposer, "" )); |
388 | ASSERT_TRUE (empty_completion_p (proposer, "- " )); |
389 | ASSERT_TRUE (empty_completion_p (proposer, "123456789" )); |
390 | ASSERT_TRUE (empty_completion_p (proposer, "---------" )); |
391 | ASSERT_TRUE (empty_completion_p (proposer, "#########" )); |
392 | ASSERT_TRUE (empty_completion_p (proposer, "- - - - - -" )); |
393 | ASSERT_TRUE (empty_completion_p (proposer, "-fsanitize=address2" )); |
394 | } |
395 | |
396 | /* Run all of the selftests within this file. */ |
397 | |
398 | void |
399 | opt_suggestions_cc_tests () |
400 | { |
401 | option_proposer proposer; |
402 | |
403 | test_completion_valid_options (proposer); |
404 | test_completion_valid_params (proposer); |
405 | test_completion_partial_match (proposer); |
406 | test_completion_garbage (proposer); |
407 | } |
408 | |
409 | } // namespace selftest |
410 | |
411 | #endif /* #if CHECKING_P */ |
412 | |