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