1/* Provide option suggestion for --complete option and a misspelled
2   used by a user.
3   Copyright (C) 2016-2022 Free Software Foundation, Inc.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License
18along 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
31option_proposer::~option_proposer ()
32{
33  delete m_option_suggestions;
34}
35
36const char *
37option_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
53void
54option_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
86void
87option_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
95void
96option_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
202namespace selftest {
203
204/* Verify that PROPOSER generates sane auto-completion suggestions
205   for OPTION_PREFIX.  */
206
207static void
208verify_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
224static void
225test_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
288static void
289test_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
330static bool
331in_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
350static bool
351empty_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
360static void
361test_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
383static void
384test_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
398void
399opt_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