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