1 /* Provide option suggestion for --complete option and a misspelled
2    used by a user.
3    Copyright (C) 2016-2020 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 
~option_proposer()31 option_proposer::~option_proposer ()
32 {
33   delete m_option_suggestions;
34 }
35 
36 const char *
suggest_option(const char * bad_opt)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     (bad_opt,
47      (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
get_completions(const char * option_prefix,auto_string_vec & results)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 (option_prefix);
66 
67   /* Lazily populate m_option_suggestions.  */
68   if (!m_option_suggestions)
69     build_option_suggestions (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 (candidate) >= length
76 	  && strstr (candidate, option_prefix) == candidate)
77 	results.safe_push (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
suggest_completion(const char * option_prefix)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 ("%s\n", results[i]);
93 }
94 
95 void
build_option_suggestions(const char * prefix)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 (m_option_suggestions, option,
118 					      with_arg);
119 		  free (with_arg);
120 		}
121 
122 	      /* Add also variant without an option argument.  */
123 	      add_misspelling_candidates (m_option_suggestions, option,
124 					  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 (m_option_suggestions, option,
141 						      with_arg);
142 			  free (with_arg);
143 			}
144 		    }
145 		  option_values.release ();
146 		}
147 
148 	      if (!option_added)
149 		add_misspelling_candidates (m_option_suggestions, option,
150 					    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 (m_option_suggestions, option,
168 					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 (m_option_suggestions, option,
191 					    with_arg);
192 		free (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
verify_autocompletions(option_proposer & proposer,const char * option_prefix)208 verify_autocompletions (option_proposer &proposer, const char *option_prefix)
209 {
210   auto_string_vec suggestions;
211   proposer.get_completions (option_prefix, 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
test_completion_valid_options(option_proposer & proposer)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, *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
test_completion_valid_params(option_proposer & proposer)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, *ptr);
326 }
327 
328 /* Return true when EXPECTED is one of completions for OPTION_PREFIX string.  */
329 
330 static bool
in_completion_p(option_proposer & proposer,const char * option_prefix,const char * expected)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, suggestions);
336 
337   for (unsigned i = 0; i < suggestions.length (); i++)
338     {
339       char *r = suggestions[i];
340       if (strcmp (r, 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
empty_completion_p(option_proposer & proposer,const char * option_prefix)351 empty_completion_p (option_proposer &proposer, const char *option_prefix)
352 {
353   auto_string_vec suggestions;
354   proposer.get_completions (option_prefix, suggestions);
355   return suggestions.is_empty ();
356 }
357 
358 /* Verify autocompletions of partially-complete options.  */
359 
360 static void
test_completion_partial_match(option_proposer & proposer)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
test_completion_garbage(option_proposer & proposer)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
opt_proposer_c_tests()399 opt_proposer_c_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