better error messages are awesome!
[pspp] / src / libpspp / message.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2009, 2010,
3    2011, 2013 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 #include "libpspp/message.h"
21
22 #include <assert.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #include "libpspp/cast.h"
30 #include "libpspp/intern.h"
31 #include "language/lexer/lexer.h"
32 #include "libpspp/str.h"
33 #include "libpspp/version.h"
34 #include "data/settings.h"
35
36 #include "gl/minmax.h"
37 #include "gl/progname.h"
38 #include "gl/relocatable.h"
39 #include "gl/xalloc.h"
40 #include "gl/xvasprintf.h"
41
42 #include "gettext.h"
43 #define _(msgid) gettext (msgid)
44
45 /* Message handler as set by msg_set_handler(). */
46 static void (*msg_handler)  (const struct msg *, void *aux);
47 static void *msg_aux;
48
49 /* Disables emitting messages if positive. */
50 static int messages_disabled;
51
52 /* Public functions. */
53
54
55 void
56 vmsg (enum msg_class class, const struct msg_location *location,
57       const char *format, va_list args)
58 {
59   struct msg *m = xmalloc (sizeof *m);
60   *m = (struct msg) {
61     .category = msg_class_to_category (class),
62     .severity = msg_class_to_severity (class),
63     .location = msg_location_dup (location),
64     .text = xvasprintf (format, args),
65   };
66   msg_emit (m);
67 }
68
69 /* Writes error message in CLASS, with text FORMAT, formatted with
70    printf, to the standard places. */
71 void
72 msg (enum msg_class class, const char *format, ...)
73 {
74   va_list args;
75   va_start (args, format);
76   vmsg (class, NULL, format, args);
77   va_end (args);
78 }
79
80 /* Outputs error message in CLASS, with text FORMAT, formatted with printf.
81    LOCATION is the reported location for the message. */
82 void
83 msg_at (enum msg_class class, const struct msg_location *location,
84         const char *format, ...)
85 {
86   va_list args;
87   va_start (args, format);
88   vmsg (class, location, format, args);
89   va_end (args);
90 }
91
92 void
93 msg_error (int errnum, const char *format, ...)
94 {
95   va_list args;
96   va_start (args, format);
97   struct string s = DS_EMPTY_INITIALIZER;
98   ds_put_vformat (&s, format, args);
99   va_end (args);
100   ds_put_format (&s, ": %s", strerror (errnum));
101
102   struct msg *m = xmalloc (sizeof *m);
103   *m = (struct msg) {
104     .category = MSG_C_GENERAL,
105     .severity = MSG_S_ERROR,
106     .text = ds_steal_cstr (&s),
107   };
108   msg_emit (m);
109 }
110
111
112
113 void
114 msg_set_handler (void (*handler) (const struct msg *, void *aux), void *aux)
115 {
116   msg_handler = handler;
117   msg_aux = aux;
118 }
119 \f
120 /* msg_location. */
121
122 void
123 msg_location_uninit (struct msg_location *loc)
124 {
125   lex_source_unref (loc->src);
126   intern_unref (loc->file_name);
127 }
128
129 void
130 msg_location_destroy (struct msg_location *loc)
131 {
132   if (loc)
133     {
134       msg_location_uninit (loc);
135       free (loc);
136     }
137 }
138
139 static int
140 msg_point_compare_3way (const struct msg_point *a, const struct msg_point *b)
141 {
142   return (!a->line ? 1
143           : !b->line ? -1
144           : a->line > b->line ? 1
145           : a->line < b->line ? -1
146           : !a->column ? 1
147           : !b->column ? -1
148           : a->column > b->column ? 1
149           : a->column < b->column ? -1
150           : 0);
151 }
152
153 void
154 msg_location_merge (struct msg_location **dstp, const struct msg_location *src)
155 {
156   struct msg_location *dst = *dstp;
157   if (!dst)
158     {
159       *dstp = msg_location_dup (src);
160       return;
161     }
162
163   if (dst->file_name != src->file_name)
164     {
165       /* Failure. */
166       return;
167     }
168   if (msg_point_compare_3way (&dst->p[0], &src->p[0]) > 0)
169     dst->p[0] = src->p[0];
170   if (msg_point_compare_3way (&dst->p[1], &src->p[1]) < 0)
171     dst->p[1] = src->p[1];
172 }
173
174 struct msg_location *
175 msg_location_dup (const struct msg_location *src)
176 {
177   if (!src)
178     return NULL;
179
180   struct msg_location *dst = xmalloc (sizeof *dst);
181   *dst = *src;
182   if (src->file_name)
183     dst->file_name = intern_ref (src->file_name);
184   if (src->src)
185     lex_source_ref (dst->src);
186   return dst;
187 }
188
189 bool
190 msg_location_is_empty (const struct msg_location *loc)
191 {
192   return !loc || (!loc->file_name
193                   && loc->p[0].line <= 0
194                   && loc->p[0].column <= 0);
195 }
196
197 void
198 msg_location_format (const struct msg_location *loc, struct string *s)
199 {
200   if (!loc)
201     return;
202
203   if (loc->file_name)
204     ds_put_cstr (s, loc->file_name);
205
206   int l1 = loc->p[0].line;
207   int l2 = MAX (l1, loc->p[1].line);
208   int c1 = loc->p[0].column;
209   int c2 = MAX (c1, loc->p[1].column);
210
211   if (l1 > 0)
212     {
213       if (loc->file_name)
214         ds_put_byte (s, ':');
215
216       if (l2 > l1)
217         {
218           if (c1 > 0)
219             ds_put_format (s, "%d.%d-%d.%d", l1, c1, l2, c2);
220           else
221             ds_put_format (s, "%d-%d", l1, l2);
222         }
223       else
224         {
225           if (c1 > 0)
226             {
227               if (c2 > c1)
228                 {
229                   /* The GNU coding standards say to use
230                      LINENO-1.COLUMN-1-COLUMN-2 for this case, but GNU
231                      Emacs interprets COLUMN-2 as LINENO-2 if I do that.
232                      I've submitted an Emacs bug report:
233                      http://debbugs.gnu.org/cgi/bugreport.cgi?bug=7725.
234
235                      For now, let's be compatible. */
236                   ds_put_format (s, "%d.%d-%d.%d", l1, c1, l1, c2);
237                 }
238               else
239                 ds_put_format (s, "%d.%d", l1, c1);
240             }
241           else
242             ds_put_format (s, "%d", l1);
243         }
244     }
245   else if (c1 > 0)
246     {
247       if (c2 > c1)
248         ds_put_format (s, ".%d-%d", c1, c2);
249       else
250         ds_put_format (s, ".%d", c1);
251     }
252 }
253 \f
254 /* msg_stack */
255
256 void
257 msg_stack_destroy (struct msg_stack *stack)
258 {
259   if (stack)
260     {
261       msg_location_destroy (stack->location);
262       free (stack->description);
263       free (stack);
264     }
265 }
266
267 struct msg_stack *
268 msg_stack_dup (const struct msg_stack *src)
269 {
270   struct msg_stack *dst = xmalloc (sizeof *src);
271   *dst = (struct msg_stack) {
272     .location = msg_location_dup (src->location),
273     .description = xstrdup_if_nonnull (src->description),
274   };
275   return dst;
276 }
277 \f
278 /* Working with messages. */
279
280 const char *
281 msg_severity_to_string (enum msg_severity severity)
282 {
283   switch (severity)
284     {
285     case MSG_S_ERROR:
286       return _("error");
287     case MSG_S_WARNING:
288       return _("warning");
289     case MSG_S_NOTE:
290     default:
291       return _("note");
292     }
293 }
294
295 /* Duplicate a message */
296 struct msg *
297 msg_dup (const struct msg *src)
298 {
299   struct msg_stack **ms = xmalloc (src->n_stack * sizeof *ms);
300   for (size_t i = 0; i < src->n_stack; i++)
301     ms[i] = msg_stack_dup (src->stack[i]);
302
303   struct msg *dst = xmalloc (sizeof *dst);
304   *dst = (struct msg) {
305     .category = src->category,
306     .severity = src->severity,
307     .stack = ms,
308     .n_stack = src->n_stack,
309     .location = msg_location_dup (src->location),
310     .command_name = xstrdup_if_nonnull (src->command_name),
311     .text = xstrdup (src->text),
312   };
313   return dst;
314 }
315
316 /* Frees a message created by msg_dup().
317
318    (Messages not created by msg_dup(), as well as their file_name
319    members, are typically not dynamically allocated, so this function should
320    not be used to destroy them.) */
321 void
322 msg_destroy (struct msg *m)
323 {
324   if (m)
325     {
326       for (size_t i = 0; i < m->n_stack; i++)
327         msg_stack_destroy (m->stack[i]);
328       free (m->stack);
329       msg_location_destroy (m->location);
330       free (m->text);
331       free (m->command_name);
332       free (m);
333     }
334 }
335
336 char *
337 msg_to_string (const struct msg *m)
338 {
339   struct string s;
340
341   ds_init_empty (&s);
342
343   for (size_t i = 0; i < m->n_stack; i++)
344     {
345       const struct msg_stack *ms = m->stack[i];
346       if (!msg_location_is_empty (ms->location))
347         {
348           msg_location_format (ms->location, &s);
349           ds_put_cstr (&s, ": ");
350         }
351       ds_put_format (&s, "%s\n", ms->description);
352     }
353   if (m->category != MSG_C_GENERAL && !msg_location_is_empty (m->location))
354     {
355       msg_location_format (m->location, &s);
356       ds_put_cstr (&s, ": ");
357     }
358
359   ds_put_format (&s, "%s: ", msg_severity_to_string (m->severity));
360
361   if (m->category == MSG_C_SYNTAX && m->command_name != NULL)
362     ds_put_format (&s, "%s: ", m->command_name);
363
364   ds_put_cstr (&s, m->text);
365
366   const struct msg_location *loc = m->location;
367   if (loc->src && loc->p[0].line)
368     {
369       struct substring line = lex_source_get_line (loc->src, m->location->p[0].line);
370       ds_put_format (&s, "\n%5d | %.*s", loc->p[0].line, (int) line.length, line.string);
371       if (loc->p[0].column && loc->p[1].column >= loc->p[0].column)
372         {
373           ds_put_cstr (&s, "      | ");
374           ds_put_byte_multiple (&s, ' ', loc->p[0].column - 1);
375           int n = loc->p[1].column - loc->p[0].column + 1;
376           ds_put_byte (&s, '^');
377           if (n > 1)
378             ds_put_byte_multiple (&s, '~', n - 1);
379         }
380     }
381
382   return ds_cstr (&s);
383 }
384 \f
385
386 /* Number of messages reported, by severity level. */
387 static int counts[MSG_N_SEVERITIES];
388
389 /* True after the maximum number of errors or warnings has been exceeded. */
390 static bool too_many_errors;
391
392 /* True after the maximum number of notes has been exceeded. */
393 static bool too_many_notes;
394
395 /* True iff warnings have been explicitly disabled (MXWARNS = 0) */
396 static bool warnings_off = false;
397
398 /* Checks whether we've had so many errors that it's time to quit
399    processing this syntax file. */
400 bool
401 msg_ui_too_many_errors (void)
402 {
403   return too_many_errors;
404 }
405
406 void
407 msg_ui_disable_warnings (bool x)
408 {
409   warnings_off = x;
410 }
411
412
413 void
414 msg_ui_reset_counts (void)
415 {
416   int i;
417
418   for (i = 0; i < MSG_N_SEVERITIES; i++)
419     counts[i] = 0;
420   too_many_errors = false;
421   too_many_notes = false;
422 }
423
424 bool
425 msg_ui_any_errors (void)
426 {
427   return counts[MSG_S_ERROR] > 0;
428 }
429
430
431 static void
432 ship_message (const struct msg *m)
433 {
434   enum { MAX_STACK = 4 };
435   static const struct msg *stack[MAX_STACK];
436   static size_t n;
437
438   /* If we're recursing on a given message, or recursing deeply, drop it. */
439   if (n >= MAX_STACK)
440     return;
441   for (size_t i = 0; i < n; i++)
442     if (stack[i] == m)
443       return;
444
445   stack[n++] = m;
446   if (msg_handler && n <= 1)
447     msg_handler (m, msg_aux);
448   else
449     fprintf (stderr, "%s\n", m->text);
450   n--;
451 }
452
453 static void
454 submit_note (char *s)
455 {
456   struct msg m = {
457     .category = MSG_C_GENERAL,
458     .severity = MSG_S_NOTE,
459     .text = s,
460   };
461   ship_message (&m);
462
463   free (s);
464 }
465
466 static void
467 process_msg (struct msg *m)
468 {
469   int n_msgs, max_msgs;
470
471   if (too_many_errors
472       || (too_many_notes && m->severity == MSG_S_NOTE)
473       || (warnings_off && m->severity == MSG_S_WARNING))
474     return;
475
476   ship_message (m);
477
478   counts[m->severity]++;
479   max_msgs = settings_get_max_messages (m->severity);
480   n_msgs = counts[m->severity];
481   if (m->severity == MSG_S_WARNING)
482     n_msgs += counts[MSG_S_ERROR];
483   if (n_msgs > max_msgs)
484     {
485       if (m->severity == MSG_S_NOTE)
486         {
487           too_many_notes = true;
488           submit_note (xasprintf (_("Notes (%d) exceed limit (%d).  "
489                                     "Suppressing further notes."),
490                                   n_msgs, max_msgs));
491         }
492       else
493         {
494           too_many_errors = true;
495           if (m->severity == MSG_S_WARNING)
496             submit_note (xasprintf (_("Warnings (%d) exceed limit (%d).  Syntax processing will be halted."),
497                                     n_msgs, max_msgs));
498           else
499             submit_note (xasprintf (_("Errors (%d) exceed limit (%d).  Syntax processing will be halted."),
500                                     n_msgs, max_msgs));
501         }
502     }
503 }
504
505
506 /* Emits M as an error message.  Takes ownership of M. */
507 void
508 msg_emit (struct msg *m)
509 {
510   if (!messages_disabled)
511     process_msg (m);
512   msg_destroy (m);
513 }
514
515 /* Disables message output until the next call to msg_enable.  If
516    this function is called multiple times, msg_enable must be
517    called an equal number of times before messages are actually
518    re-enabled. */
519 void
520 msg_disable (void)
521 {
522   messages_disabled++;
523 }
524
525 /* Enables message output that was disabled by msg_disable. */
526 void
527 msg_enable (void)
528 {
529   assert (messages_disabled > 0);
530   messages_disabled--;
531 }
532 \f
533 /* Private functions. */
534
535 static char fatal_error_message[1024];
536 static int fatal_error_message_bytes = 0;
537
538 static char diagnostic_information[1024];
539 static int diagnostic_information_bytes = 0;
540
541 static int
542 append_message (char *msg, int bytes_used, const char *fmt, ...)
543 {
544   va_list va;
545   va_start (va, fmt);
546   int ret = vsnprintf (msg + bytes_used, 1024 - bytes_used, fmt, va);
547   va_end (va);
548   assert (ret >= 0);
549
550   return ret;
551 }
552
553
554 /* Generate a row of asterisks held in statically allocated memory  */
555 static struct substring
556 generate_banner (void)
557 {
558   static struct substring banner;
559   if (!banner.string)
560     banner = ss_cstr ("******************************************************\n");
561   return banner;
562 }
563
564 const char *
565 prepare_fatal_error_message (void)
566 {
567   fatal_error_message_bytes += append_message (fatal_error_message, fatal_error_message_bytes, generate_banner ().string);
568
569   fatal_error_message_bytes += append_message (fatal_error_message, fatal_error_message_bytes, "You have discovered a bug in PSPP.  Please report this\n");
570   fatal_error_message_bytes += append_message (fatal_error_message, fatal_error_message_bytes, "to " PACKAGE_BUGREPORT ".  Please include this entire\n");
571   fatal_error_message_bytes += append_message (fatal_error_message, fatal_error_message_bytes, "message, *plus* several lines of output just above it.\n");
572   fatal_error_message_bytes += append_message (fatal_error_message, fatal_error_message_bytes, "For the best chance at having the bug fixed, also\n");
573   fatal_error_message_bytes += append_message (fatal_error_message, fatal_error_message_bytes, "include the syntax file that triggered it and a sample\n");
574   fatal_error_message_bytes += append_message (fatal_error_message, fatal_error_message_bytes, "of any data file used for input.\n");
575   return fatal_error_message;
576 }
577
578 const char *
579 prepare_diagnostic_information (void)
580 {
581   diagnostic_information_bytes += append_message (diagnostic_information, diagnostic_information_bytes, "version:             %s\n", version);
582   diagnostic_information_bytes += append_message (diagnostic_information, diagnostic_information_bytes, "host_system:         %s\n", host_system);
583   diagnostic_information_bytes += append_message (diagnostic_information, diagnostic_information_bytes, "build_system:        %s\n", build_system);
584   diagnostic_information_bytes += append_message (diagnostic_information, diagnostic_information_bytes, "locale_dir:          %s\n", relocate (locale_dir));
585   diagnostic_information_bytes += append_message (diagnostic_information, diagnostic_information_bytes, "compiler version:    %s\n",
586 #ifdef __VERSION__
587            __VERSION__
588 #else
589            "Unknown"
590 #endif
591 );
592
593   return diagnostic_information;
594 }
595
596 void
597 request_bug_report (const char *msg)
598 {
599   write (STDERR_FILENO, fatal_error_message, fatal_error_message_bytes);
600   write (STDERR_FILENO, "proximate cause:     ", 21);
601   write (STDERR_FILENO, msg, strlen (msg));
602   write (STDERR_FILENO, "\n", 1);
603   write (STDERR_FILENO, diagnostic_information, diagnostic_information_bytes);
604   const struct substring banner = generate_banner ();
605   write (STDERR_FILENO, banner.string, banner.length);
606 }