1 /* Declaration for error-reporting function for Bison.
2 
3    Copyright (C) 2000-2002, 2004-2006, 2009-2015, 2018-2021 Free
4    Software Foundation, Inc.
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
18 
19 /* Based on error.c and error.h,
20    written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
21 
22 #include <config.h>
23 #include "system.h"
24 
25 #include <argmatch.h>
26 #include <c-ctype.h>
27 #include <progname.h>
28 #include <stdarg.h>
29 #include <sys/stat.h>
30 #include <textstyle.h>
31 
32 #include "complain.h"
33 #include "files.h"
34 #include "fixits.h"
35 #include "getargs.h"
36 #include "quote.h"
37 
38 // The URL of the manual page about diagnostics.  Use the per-node
39 // manual, to avoid downloading repeatedly the whole manual over the
40 // Internet.
41 static const char *diagnostics_url
42   = "https://www.gnu.org/software/bison/manual/html_node/Diagnostics.html";
43 
44 
45 err_status complaint_status = status_none;
46 
47 bool warnings_are_errors = false;
48 
49 /** Whether -Werror/-Wno-error was applied to a warning.  */
50 typedef enum
51   {
52     errority_unset = 0,     /** No explicit status.  */
53     errority_disabled = 1,  /** Explicitly disabled with -Wno-error=foo.  */
54     errority_enabled = 2    /** Explicitly enabled with -Werror=foo. */
55   } errority;
56 
57 /** For each warning type, its errority.  */
58 static errority errority_flag[warnings_size];
59 
60 /** Diagnostics severity.  */
61 typedef enum
62   {
63     severity_disabled = 0, /**< Explicitly disabled via -Wno-foo.  */
64     severity_unset = 1,    /**< Unspecified status.  */
65     severity_warning = 2,  /**< A warning.  */
66     severity_error = 3,    /**< An error (continue, but die soon).  */
67     severity_fatal = 4     /**< Fatal error (die now).  */
68   } severity;
69 
70 
71 /** For each warning type, its severity.  */
72 static severity warnings_flag[warnings_size];
73 
74 styled_ostream_t errstream = NULL;
75 
76 void
begin_use_class(const char * s,FILE * out)77 begin_use_class (const char *s, FILE *out)
78 {
79   if (out == stderr)
80     {
81       if (color_debug)
82         fprintf (out, "<%s>", s);
83       else
84         {
85           styled_ostream_begin_use_class (errstream, s);
86           styled_ostream_flush_to_current_style (errstream);
87         }
88     }
89 }
90 
91 void
end_use_class(const char * s,FILE * out)92 end_use_class (const char *s, FILE *out)
93 {
94   if (out == stderr)
95     {
96       if (color_debug)
97         fprintf (out, "</%s>", s);
98       else
99         {
100           styled_ostream_end_use_class (errstream, s);
101           styled_ostream_flush_to_current_style (errstream);
102         }
103     }
104 }
105 
106 static void
begin_hyperlink(FILE * out,const char * ref)107 begin_hyperlink (FILE *out, const char *ref)
108 {
109   if (out == stderr)
110     styled_ostream_set_hyperlink (errstream, ref, NULL);
111 }
112 
113 static void
end_hyperlink(FILE * out)114 end_hyperlink (FILE *out)
115 {
116   if (out == stderr)
117     styled_ostream_set_hyperlink (errstream, NULL, NULL);
118 }
119 
120 void
flush(FILE * out)121 flush (FILE *out)
122 {
123   if (out == stderr)
124     ostream_flush (errstream, FLUSH_THIS_STREAM);
125   fflush (out);
126 }
127 
128 bool
is_styled(FILE * out)129 is_styled (FILE *out)
130 {
131   if (out != stderr)
132     return false;
133   if (color_debug)
134     return true;
135 #if HAVE_LIBTEXTSTYLE
136   return (color_mode == color_yes
137           || color_mode == color_html
138           || (color_mode == color_tty && isatty (STDERR_FILENO)));
139 #else
140   return false;
141 #endif
142 }
143 
144 
145 /*------------------------.
146 | --warnings's handling.  |
147 `------------------------*/
148 
149 ARGMATCH_DEFINE_GROUP (warning, warnings)
150 
151 static const argmatch_warning_doc argmatch_warning_docs[] =
152 {
153   { "conflicts-sr",     N_("S/R conflicts (enabled by default)") },
154   { "conflicts-rr",     N_("R/R conflicts (enabled by default)") },
155   { "counterexamples",  N_("generate conflict counterexamples") },
156   { "dangling-alias",   N_("string aliases not attached to a symbol") },
157   { "deprecated",       N_("obsolete constructs") },
158   { "empty-rule",       N_("empty rules without %empty") },
159   { "midrule-values",   N_("unset or unused midrule values") },
160   { "precedence",       N_("useless precedence and associativity") },
161   { "yacc",             N_("incompatibilities with POSIX Yacc") },
162   { "other",            N_("all other warnings (enabled by default)") },
163   { "all",              N_("all the warnings except 'counterexamples', 'dangling-alias' and 'yacc'") },
164   { "no-CATEGORY",      N_("turn off warnings in CATEGORY") },
165   { "none",             N_("turn off all the warnings") },
166   { "error[=CATEGORY]", N_("treat warnings as errors") },
167   { NULL, NULL }
168 };
169 
170 static const argmatch_warning_arg argmatch_warning_args[] =
171 {
172   { "all",             Wall },
173   { "conflicts-rr",    Wconflicts_rr },
174   { "conflicts-sr",    Wconflicts_sr },
175   { "counterexamples", Wcounterexamples }, { "cex", Wcounterexamples }, // Show cex second.
176   { "dangling-alias",  Wdangling_alias },
177   { "deprecated",      Wdeprecated },
178   { "empty-rule",      Wempty_rule },
179   { "everything",      Weverything },
180   { "midrule-values",  Wmidrule_values },
181   { "none",            Wnone },
182   { "other",           Wother },
183   { "precedence",      Wprecedence },
184   { "yacc",            Wyacc },
185   { NULL, Wnone }
186 };
187 
188 const argmatch_warning_group_type argmatch_warning_group =
189 {
190   argmatch_warning_args,
191   argmatch_warning_docs,
192   N_("Warning categories include:"),
193   NULL
194 };
195 
196 void
warning_usage(FILE * out)197 warning_usage (FILE *out)
198 {
199   argmatch_warning_usage (out);
200 }
201 
202 void
warning_argmatch(char const * arg,size_t no,size_t err)203 warning_argmatch (char const *arg, size_t no, size_t err)
204 {
205   int value = *argmatch_warning_value ("--warning", arg + no + err);
206 
207   /* -Wnone == -Wno-everything, and -Wno-none == -Weverything.  */
208   if (!value)
209     {
210       value = Weverything;
211       no = !no;
212     }
213 
214   for (size_t b = 0; b < warnings_size; ++b)
215     if (value & 1 << b)
216       {
217         if (err && no)
218           /* -Wno-error=foo.  */
219           errority_flag[b] = errority_disabled;
220         else if (err && !no)
221           {
222             /* -Werror=foo: enables -Wfoo. */
223             errority_flag[b] = errority_enabled;
224             warnings_flag[b] = severity_warning;
225           }
226         else if (no)
227           /* -Wno-foo.  */
228           warnings_flag[b] = severity_disabled;
229         else
230           /* -Wfoo. */
231           warnings_flag[b] = severity_warning;
232       }
233 }
234 
235 /** Decode a comma-separated list of arguments from -W.
236  *
237  *  \param args     comma separated list of effective subarguments to decode.
238  *                  If 0, then activate all the flags.
239  */
240 
241 void
warnings_argmatch(char * args)242 warnings_argmatch (char *args)
243 {
244   if (!args)
245     warning_argmatch ("all", 0, 0);
246   else if (STREQ (args, "help"))
247     {
248       warning_usage (stdout);
249       exit (EXIT_SUCCESS);
250     }
251   else
252     for (args = strtok (args, ","); args; args = strtok (NULL, ","))
253       if (STREQ (args, "error"))
254         warnings_are_errors = true;
255       else if (STREQ (args, "no-error"))
256         warnings_are_errors = false;
257       else
258         {
259           /* The length of the possible 'no-' prefix: 3, or 0.  */
260           size_t no = STRPREFIX_LIT ("no-", args) ? 3 : 0;
261           /* The length of the possible 'error=' (possibly after
262              'no-') prefix: 6, or 0. */
263           size_t err = STRPREFIX_LIT ("error=", args + no) ? 6 : 0;
264 
265           warning_argmatch (args, no, err);
266         }
267 }
268 
269 /* Color style for this type of message.  */
270 static const char*
severity_style(severity s)271 severity_style (severity s)
272 {
273   switch (s)
274     {
275     case severity_disabled:
276     case severity_unset:
277       return "note";
278     case severity_warning:
279       return "warning";
280     case severity_error:
281     case severity_fatal:
282       return "error";
283     }
284   abort ();
285 }
286 
287 /* Prefix for this type of message.  */
288 static const char*
severity_prefix(severity s)289 severity_prefix (severity s)
290 {
291   switch (s)
292     {
293     case severity_disabled:
294     case severity_unset:
295       return "";
296     case severity_warning:
297       return _("warning");
298     case severity_error:
299       return  _("error");
300     case severity_fatal:
301       return _("fatal error");
302     }
303   abort ();
304 }
305 
306 
307 static void
severity_print(severity s,FILE * out)308 severity_print (severity s, FILE *out)
309 {
310   if (s != severity_disabled)
311     {
312       const char* style = severity_style (s);
313       begin_use_class (style, out);
314       fprintf (out, "%s:", severity_prefix (s));
315       end_use_class (style, out);
316       fputc (' ', out);
317     }
318 }
319 
320 
321 /*-----------.
322 | complain.  |
323 `-----------*/
324 
325 void
complain_init_color(void)326 complain_init_color (void)
327 {
328 #if HAVE_LIBTEXTSTYLE
329   if (is_styled (stderr))
330     {
331       style_file_prepare ("BISON_STYLE", "BISON_STYLEDIR", pkgdatadir (),
332                           "bison-default.css");
333       /* As a fallback, use the default in the current directory.  */
334       struct stat statbuf;
335       if ((style_file_name == NULL || stat (style_file_name, &statbuf) < 0)
336           && stat ("bison-default.css", &statbuf) == 0)
337         style_file_name = "bison-default.css";
338     }
339   else
340     /* No styling.  */
341     style_file_name = NULL;
342 #endif
343 
344   /* Workaround clang's warning (starting at Clang 3.5) about the stub
345      code of html_styled_ostream_create:
346 
347      | src/complain.c:274:7: error: code will never be executed [-Werror,-Wunreachable-code]
348      |     ? html_styled_ostream_create (file_ostream_create (stderr),
349      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~ */
350 #if defined __clang__
351 # pragma clang diagnostic push
352 # pragma clang diagnostic ignored "-Wunreachable-code"
353 #endif
354   errstream =
355     color_mode == color_html
356     ? html_styled_ostream_create (file_ostream_create (stderr),
357                                   style_file_name)
358     : styled_ostream_create (STDERR_FILENO, "(stderr)", TTYCTL_AUTO,
359                              style_file_name);
360 #if defined __clang__
361 # pragma clang diagnostic pop
362 #endif
363 }
364 
365 void
complain_init(void)366 complain_init (void)
367 {
368   caret_init ();
369 
370   warnings warnings_default =
371     Wconflicts_sr | Wconflicts_rr | Wdeprecated | Wother;
372 
373   for (size_t b = 0; b < warnings_size; ++b)
374     {
375       warnings_flag[b] = (1 << b & warnings_default
376                           ? severity_warning
377                           : severity_unset);
378       errority_flag[b] = errority_unset;
379     }
380 }
381 
382 void
complain_free(void)383 complain_free (void)
384 {
385   caret_free ();
386   styled_ostream_free (errstream);
387 }
388 
389 /* A diagnostic with FLAGS is about to be issued.  With what severity?
390    (severity_fatal, severity_error, severity_disabled, or
391    severity_warning.) */
392 
393 static severity
warning_severity(warnings flags)394 warning_severity (warnings flags)
395 {
396   if (flags & fatal)
397     /* Diagnostics about fatal errors.  */
398     return severity_fatal;
399   else if (flags & complaint)
400     /* Diagnostics about errors.  */
401     return severity_error;
402   else
403     {
404       /* Diagnostics about warnings.  */
405       severity res = severity_disabled;
406       for (size_t b = 0; b < warnings_size; ++b)
407         if (flags & 1 << b)
408           {
409             res = res < warnings_flag[b] ? warnings_flag[b] : res;
410             /* If the diagnostic is enabled, and -Werror is enabled,
411                and -Wno-error=foo was not explicitly requested, this
412                is an error. */
413             if (res == severity_warning
414                 && (errority_flag[b] == errority_enabled
415                     || (warnings_are_errors
416                         && errority_flag[b] != errority_disabled)))
417               res = severity_error;
418           }
419       return res;
420     }
421 }
422 
423 bool
warning_is_unset(warnings flags)424 warning_is_unset (warnings flags)
425 {
426   for (size_t b = 0; b < warnings_size; ++b)
427     if (flags & 1 << b && warnings_flag[b] != severity_unset)
428       return false;
429   return true;
430 }
431 
432 bool
warning_is_enabled(warnings flags)433 warning_is_enabled (warnings flags)
434 {
435   return severity_warning <= warning_severity (flags);
436 }
437 
438 /** Display a "[-Wyacc]" like message on \a out.  */
439 
440 static void
warnings_print_categories(warnings warn_flags,FILE * out)441 warnings_print_categories (warnings warn_flags, FILE *out)
442 {
443   for (int wbit = 0; wbit < warnings_size; ++wbit)
444     if (warn_flags & (1 << wbit))
445       {
446         warnings w = 1 << wbit;
447         severity s = warning_severity (w);
448         const char* style = severity_style (s);
449         fputs (" [", out);
450         begin_use_class (style, out);
451         // E.g., "counterexamples".
452         const char *warning = argmatch_warning_argument (&w);
453         char ref[200];
454         snprintf (ref, sizeof ref,
455                   "%s#W%s", diagnostics_url, warning);
456         begin_hyperlink (out, ref);
457         ostream_printf (errstream,
458                         "-W%s%s",
459                         s == severity_error ? "error=" : "",
460                         warning);
461         end_hyperlink (out);
462         // Because we mix stdio with ostream I/O, we need to flush
463         // here for sake of color == debug.
464         flush (out);
465         end_use_class (style, out);
466         fputc (']', out);
467         /* Display only the first match, the second is "-Wall".  */
468         return;
469       }
470 }
471 
472 /** Report an error message.
473  *
474  * \param loc     the location, defaulting to the current file,
475  *                or the program name.
476  * \param flags   the category for this message.
477  * \param sever   to decide the prefix to put before the message
478  *                (e.g., "warning").
479  * \param message the error message, a printf format string.  Iff it
480  *                ends with ": ", then no trailing newline is printed,
481  *                and the caller should print the remaining
482  *                newline-terminated message to stderr.
483  * \param args    the arguments of the format string.
484  */
485 static
486 void
error_message(const location * loc,warnings flags,severity sever,const char * message,va_list args)487 error_message (const location *loc, warnings flags,
488                severity sever, const char *message, va_list args)
489 {
490   const char* style = flags & note ? "note" : severity_style (sever);
491 
492   if (loc)
493     location_print (*loc, stderr);
494   else
495     fprintf (stderr, "%s", grammar_file ? grammar_file : program_name);
496   fprintf (stderr, ": ");
497 
498   if (sever != severity_disabled)
499     {
500       begin_use_class (style, stderr);
501       fprintf (stderr, "%s:", flags & note ? _("note") : severity_prefix (sever));
502       end_use_class (style, stderr);
503       fputc (' ', stderr);
504     }
505 
506   vfprintf (stderr, message, args);
507   /* Print the type of warning, only if this is not a sub message
508      (in which case the prefix is null).  */
509   if (! (flags & silent) && sever != severity_disabled)
510     warnings_print_categories (flags, stderr);
511 
512   size_t l = strlen (message);
513   if (l < 2 || message[l - 2] != ':' || message[l - 1] != ' ')
514     {
515       putc ('\n', stderr);
516       flush (stderr);
517       if (loc && !(flags & no_caret))
518         location_caret (*loc, style, stderr);
519     }
520   flush (stderr);
521 }
522 
523 /** Raise a complaint (fatal error, error or just warning).  */
524 
525 static void
complains(const location * loc,warnings flags,const char * message,va_list args)526 complains (const location *loc, warnings flags,
527            const char *message, va_list args)
528 {
529   if ((flags & complaint) && complaint_status < status_complaint)
530     complaint_status = status_complaint;
531 
532   severity s = warning_severity (flags);
533   if (severity_warning <= s)
534     {
535       if (severity_error <= s && ! complaint_status)
536         complaint_status = status_warning_as_error;
537       error_message (loc, flags, s, message, args);
538     }
539 
540   if (flags & fatal)
541     exit (EXIT_FAILURE);
542 }
543 
544 void
complain(location const * loc,warnings flags,const char * message,...)545 complain (location const *loc, warnings flags, const char *message, ...)
546 {
547   va_list args;
548   va_start (args, message);
549   complains (loc, flags, message, args);
550   va_end (args);
551 }
552 
553 void
subcomplain(location const * loc,warnings flags,const char * message,...)554 subcomplain (location const *loc, warnings flags, const char *message, ...)
555 {
556   va_list args;
557   va_start (args, message);
558   complains (loc, flags | note | silent, message, args);
559   va_end (args);
560 }
561 
562 void
complain_args(location const * loc,warnings w,int argc,char * argv[])563 complain_args (location const *loc, warnings w,
564                int argc, char *argv[])
565 {
566   switch (argc)
567   {
568   case 1:
569     complain (loc, w, "%s", _(argv[0]));
570     break;
571   case 2:
572     complain (loc, w, _(argv[0]), argv[1]);
573     break;
574   case 3:
575     complain (loc, w, _(argv[0]), argv[1], argv[2]);
576     break;
577   case 4:
578     complain (loc, w, _(argv[0]), argv[1], argv[2], argv[3]);
579     break;
580   case 5:
581     complain (loc, w, _(argv[0]), argv[1], argv[2], argv[3], argv[4]);
582     break;
583   default:
584     complain (loc, fatal, "too many arguments for complains");
585     break;
586   }
587 }
588 
589 
590 void
bison_directive(location const * loc,char const * directive)591 bison_directive (location const *loc, char const *directive)
592 {
593   complain (loc, Wyacc,
594             _("POSIX Yacc does not support %s"), directive);
595 }
596 
597 void
deprecated_directive(location const * loc,char const * old,char const * upd)598 deprecated_directive (location const *loc, char const *old, char const *upd)
599 {
600   if (warning_is_enabled (Wdeprecated))
601     {
602       complain (loc, Wdeprecated,
603                 _("deprecated directive: %s, use %s"),
604                 quote (old), quote_n (1, upd));
605       location_caret_suggestion (*loc, upd, stderr);
606       /* Register updates only if -Wdeprecated is enabled.  */
607       fixits_register (loc, upd);
608     }
609 }
610 
611 void
duplicate_directive(char const * directive,location first,location second)612 duplicate_directive (char const *directive,
613                      location first, location second)
614 {
615   if (feature_flag & feature_caret)
616     complain (&second, Wother, _("duplicate directive"));
617   else
618     complain (&second, Wother, _("duplicate directive: %s"), quote (directive));
619   subcomplain (&first, Wother, _("previous declaration"));
620   fixits_register (&second, "");
621 }
622 
623 void
duplicate_rule_directive(char const * directive,location first,location second)624 duplicate_rule_directive (char const *directive,
625                           location first, location second)
626 {
627   complain (&second, complaint, _("only one %s allowed per rule"), directive);
628   subcomplain (&first, complaint, _("previous declaration"));
629   fixits_register (&second, "");
630 }
631 
632 void
syntax_error(location loc,int argc,const char * argv[])633 syntax_error (location loc,
634               int argc, const char* argv[])
635 {
636   if (complaint_status < status_complaint)
637     complaint_status = status_complaint;
638   assert (argc <= 5);
639   const char *format = NULL;
640   switch (argc)
641     {
642 #define CASE(N, S)                          \
643       case N:                               \
644         format = S;                         \
645         break
646     default: /* Avoid compiler warnings. */
647       CASE (0, _("syntax error"));
648       CASE (1, _("unexpected %0$s"));
649       CASE (2, _("expected %1$s before %0$s"));
650       CASE (3, _("expected %1$s or %2$s before %0$s"));
651       CASE (4, _("expected %1$s or %2$s or %3$s before %0$s"));
652       CASE (5, _("expected %1$s or %2$s or %3$s or %4$s before %0$s"));
653 #undef CASE
654     }
655   location_print (loc, stderr);
656   fputs (": ", stderr);
657   severity_print (severity_error, stderr);
658 
659   while (*format)
660     if (format[0] == '%'
661         && c_isdigit (format[1])
662         && format[2] == '$'
663         && format[3] == 's'
664         && (format[1] - '0') < argc)
665       {
666         int i = format[1] - '0';
667         const char *style = i == 0 ? "unexpected" : "expected";
668         begin_use_class (style, stderr);
669         fputs (argv[i], stderr);
670         end_use_class (style, stderr);
671         format += 4;
672       }
673     else
674       {
675         fputc (*format, stderr);
676         ++format;
677       }
678   fputc ('\n', stderr);
679   location_caret (loc, "error", stderr);
680 }
681