1 /*
2     the DASM macro assembler (aka small systems cross assembler)
3 
4     Copyright (c) 1988-2002 by Matthew Dillon.
5     Copyright (c) 1995 by Olaf "Rhialto" Seibert.
6     Copyright (c) 2003-2008 by Andrew Davie.
7     Copyright (c) 2008 by Peter H. Froehlich.
8 
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13 
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License along
20     with this program; if not, write to the Free Software Foundation, Inc.,
21     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23 
24 /**
25  * @file
26  */
27 
28 #include "errors.h"
29 
30 #include "asm.h"
31 #include "util.h"
32 #include "version.h"
33 
34 #include <assert.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 
38 /*
39     TODO: I simply replaced "error" with the current level in
40     all messages, not sure that works on Windows? GNU is fine,
41     it doesn't require "error" in the message...
42 */
43 
44 /*
45     TODO: the original asmerr() would set bStopAtEnd (what is
46     now nof_fatals>0) if the error had the "fatal" flag set in
47     the struct that described it; if would *abort* on a true
48     argument; in my previous reading of the code, I had assumed
49     that a true argument meant simply "fatal" and not abort. To
50     be verified again and addressed, maybe there are a few fatals
51     that should really be panics...
52 */
53 
54 static error_format_t F_error_format = ERRORFORMAT_DEFAULT;
55 static error_level_t F_error_level = ERRORLEVEL_DEFAULT;
56 static size_t nof_fatals = 0;
57 static size_t nof_errors = 0;
58 static size_t nof_warnings = 0;
59 char source_location_buffer[SOURCE_LOCATION_LENGTH];
60 
61 static const char *level_names[] =
62 {
63     [ERRORLEVEL_DEBUG] = "debug",
64     [ERRORLEVEL_INFO] = "info",
65     [ERRORLEVEL_NOTICE] = "notice",
66     [ERRORLEVEL_WARNING] = "warning",
67     [ERRORLEVEL_ERROR] = "error",
68     [ERRORLEVEL_FATAL] = "fatal",
69     [ERRORLEVEL_PANIC] = "***panic***",
70 };
71 
valid_error_format(int format)72 bool valid_error_format(int format)
73 {
74     return (ERRORFORMAT_MIN <= format && format <= ERRORFORMAT_MAX);
75 }
76 
set_error_format(error_format_t format)77 void set_error_format(error_format_t format)
78 {
79     assert(valid_error_format(format));
80     F_error_format = format;
81 }
82 
valid_error_level(int level)83 bool valid_error_level(int level)
84 {
85     return (ERRORLEVEL_MIN <= level && level <= ERRORLEVEL_MAX);
86 }
87 
set_error_level(error_level_t level)88 void set_error_level(error_level_t level)
89 {
90     assert(valid_error_level(level));
91     F_error_level = level;
92 }
93 
94 /**
95  * @brief Display this error level?
96  */
visible_error_level(error_level_t level)97 static bool visible_error_level(error_level_t level)
98 {
99     return (level >= F_error_level);
100 }
101 
number_of_fatals(void)102 size_t number_of_fatals(void)
103 {
104   return nof_fatals;
105 }
106 
number_of_errors(void)107 size_t number_of_errors(void)
108 {
109   return nof_errors;
110 }
111 
number_of_warnings(void)112 size_t number_of_warnings(void)
113 {
114   return nof_warnings;
115 }
116 
117 /* Super low-level panic for disasters *inside* the errors module! */
internal_panic(const char * message)118 static void internal_panic(const char *message)
119 {
120   fprintf(
121     stderr,
122     "\n%s: FATAL INTERNAL PANIC (errors.c): %s\n\n",
123     getprogname(),
124     message
125   );
126   exit(EXIT_FAILURE);
127 }
128 
129 /**
130  * @brief Print final error message to all relevant streams.
131  * @note We always print to stderr; we print to the listing
132  * file if we have one. Messages to the listing file get a
133  * leading "*" just like Matt's version did years ago; at
134  * one point I thought that the "*" starts a comment, but I
135  * can't confirm that in the code (only ";" seems to be a
136  * comment), so the motivation must have been different.
137  * We only get here after all the other filters checked that
138  * we should really print, so we don't check anything else
139  * about the error message.
140  */
print_error_message(const char * message)141 static void print_error_message(const char *message)
142 {
143     assert(message != NULL);
144     assert(strlen(message) > 0);
145 
146     fprintf(stderr, "%s\n", message);
147 
148     if (FI_listfile != NULL) {
149         fprintf(FI_listfile, "*%s\n", message);
150     }
151     else {
152         /* sanity check: if there was no FILE* there should be no name */
153         assert(F_listfile == NULL);
154     }
155 }
156 
157 /**
158  * @brief Sane wrapper for vsnprintf().
159  * @note See sane_snprintf() for details.
160  */
161 static size_t
sane_vsnprintf(char * restrict str,size_t size,const char * restrict fmt,va_list ap)162 sane_vsnprintf(/*@out@*/ char *restrict str, size_t size, const char *restrict fmt, va_list ap)
163 {
164     int res;
165     assert(str != NULL);
166     assert(size > 0);
167     assert(fmt != NULL);
168 
169     res = vsnprintf(str, size, fmt, ap);
170     if (res < 0) {
171         internal_panic("sane_vsnprintf() failed!");
172     }
173     /* res >= 0 here so cast to size_t is okay */
174     return (size_t) res;
175 }
176 
177 static size_t
178 sane_snprintf(/*@out@*/ char *restrict str, size_t size, const char *restrict fmt, ...)
179 __attribute__((format(printf, 3, 4)));
180 
181 /**
182  * @brief Sane wrapper for snprintf().
183  * @note The interface for snprintf() is a little retarded since
184  * the return type is int instead of size_t. Due to it's stdio.h
185  * heritage, returning something negative on error is expected.
186  * But we're using it to format strings, so we don't care about
187  * those errors in detail (if there ever are any, not even sure).
188  * We handle potential errors here and return a size_t suitable
189  * for overflow checking.
190  */
191 static size_t
sane_snprintf(char * restrict str,size_t size,const char * restrict fmt,...)192 sane_snprintf(/*@out@*/ char *restrict str, size_t size, const char *restrict fmt, ...)
193 {
194     size_t res;
195     va_list ap;
196 
197     va_start(ap, fmt);
198 
199     res = sane_vsnprintf(str, size, fmt, ap);
200 
201     va_end(ap);
202     return res;
203 }
204 
205 /**
206  * @brief Format the level part of the error message.
207  * @note Follows strlcat() conventions.
208  */
append_level(char * buffer,error_level_t level,size_t size)209 static size_t append_level(char *buffer, error_level_t level, size_t size)
210 {
211     char name[1024];
212     size_t res = 0;
213     assert(valid_error_level(level));
214     res = sane_snprintf(name, sizeof(name), "%s: ", level_names[level]);
215     if (res >= sizeof(name)) {
216         internal_panic("Buffer overflow in append_level()!");
217     }
218     return strlcat(buffer, name, size);
219 }
220 
221 /**
222  * @brief Format the location part of the error message.
223  * @note Follows strlcat() conventions.
224  */
append_location(char * buffer,const INCFILE * file,size_t size)225 static size_t append_location(char *buffer, /*@null@*/ const INCFILE *file, size_t size)
226 {
227     char location[1024];
228     size_t res = 0;
229 
230     /* clear buffer */
231     location[0] = '\0';
232 
233     switch (F_error_format) {
234         case ERRORFORMAT_WOE:
235             /*
236                 Error format for MS VisualStudio and relatives:
237                 "file (line): error: string"
238             */
239             if (file != NULL) {
240                 res = sane_snprintf(
241                     location, sizeof(location), "%s (%lu): ",
242                     file->name, file->lineno
243                 );
244             }
245             break;
246         case ERRORFORMAT_DILLON:
247             /*
248                 Matthew Dillon's original format. Note that
249                 Matt's 2.16 uses this instead:
250                   "line %4ld %-10s %s\n" (terminal)
251             */
252             if (file != NULL) {
253                 res = sane_snprintf(
254                     location, sizeof(location), "line %7lu %-10s ",
255                     file->lineno, file->name
256                 );
257             }
258             break;
259         case ERRORFORMAT_GNU:
260             /*
261                 GNU format error messages, from their coding
262                 standards: "source-file-name:lineno: message"
263             */
264             if (file != NULL) {
265                 res = sane_snprintf(
266                     location, sizeof(location), "%s:%lu: ",
267                     file->name, file->lineno
268                 );
269             }
270             else {
271                 res = sane_snprintf(
272                     location, sizeof(location), "%s: ",
273                     getprogname()
274                 );
275             }
276             break;
277         default:
278             internal_panic("Invalid error format in append_location()!");
279             break;
280     }
281     if (res >= sizeof(location)) {
282         internal_panic("Buffer overflow in append_location()!");
283     }
284     return strlcat(buffer, location, size);
285 }
286 
287 /**
288  * @brief Format the information part of the error message.
289  * @note Follows strlcat() conventions.
290  */
291 static size_t
append_information(char * buffer,const char * fmt,va_list ap,size_t size)292 append_information(char *buffer, const char *fmt, va_list ap, size_t size)
293 {
294     char information[1024];
295     size_t res = 0;
296     res = sane_vsnprintf(information, sizeof(information), fmt, ap);
297     if (res >= sizeof(information)) {
298         internal_panic("Buffer overflow in append_information()!");
299     }
300     return strlcat(buffer, information, size);
301 }
302 
vanotify(error_level_t level,const char * fmt,va_list ap)303 static void vanotify(error_level_t level, const char *fmt, va_list ap)
304 {
305     /* buffer for formatting error message into  */
306     char buffer[1024];
307     /* include file we're in right now (if any) */
308     INCFILE *file = pIncfile;
309     /* holds the return value from strlcat */
310     size_t res;
311 
312     assert(valid_error_level(level));
313 
314     if (!visible_error_level(level)) {
315         /* condition not severe enough */
316         return;
317     }
318 
319     /* find the file we're in (if any) */
320     /* TODO: how does this work exactly? */
321     while (file != NULL && (file->flags & INF_MACRO) != 0) {
322         file = file->next;
323     }
324 
325     /* clear buffer */
326     buffer[0] = '\0';
327 
328     /* append the various pieces of the message */
329     res = append_location(buffer, file, sizeof(buffer));
330     if (res > sizeof(buffer)) {
331         internal_panic("Buffer overflow in vanotify()!");
332     }
333     res = append_level(buffer, level, sizeof(buffer));
334     if (res > sizeof(buffer)) {
335         internal_panic("Buffer overflow in vanotify()!");
336     }
337     res = append_information(buffer, fmt, ap, sizeof(buffer));
338     if (res > sizeof(buffer)) {
339         internal_panic("Buffer overflow in vanotify()!");
340     }
341 
342     /* print the message */
343     print_error_message(buffer);
344 
345     /* maintain statistics about warnings and errors */
346     /* TODO: count everything < PANIC? */
347     if (level == ERRORLEVEL_WARNING)
348     {
349          nof_warnings += 1;
350     }
351     if (level == ERRORLEVEL_ERROR)
352     {
353          nof_errors += 1;
354     }
355 
356     /* fatal and higher errors lead to (eventual) termination */
357     if (level >= ERRORLEVEL_FATAL)
358     {
359         nof_fatals += 1; /* stop after current pass */
360     }
361     if (level == ERRORLEVEL_PANIC)
362     {
363         exit(EXIT_FAILURE); /* stop right now! */
364     }
365 }
366 
367 /* avoid code replication through macros, sweet [phf] */
368 #define IMPLEMENT_FMT(level) \
369     va_list ap; \
370     va_start(ap, fmt); \
371     vanotify(level, fmt, ap); \
372     va_end(ap)
373 #define DEFINE_FMT(name, level) \
374 void name(const char *fmt, ...) \
375 { \
376     IMPLEMENT_FMT(level); \
377 }
378 
notify_fmt(error_level_t level,const char * fmt,...)379 void notify_fmt(error_level_t level, const char *fmt, ...)
380 {
381     IMPLEMENT_FMT(level);
382 }
383 
384 DEFINE_FMT(debug_fmt, ERRORLEVEL_DEBUG)
385 DEFINE_FMT(info_fmt, ERRORLEVEL_INFO)
386 DEFINE_FMT(notice_fmt, ERRORLEVEL_NOTICE)
387 DEFINE_FMT(warning_fmt, ERRORLEVEL_WARNING)
388 DEFINE_FMT(error_fmt, ERRORLEVEL_ERROR)
389 DEFINE_FMT(fatal_fmt, ERRORLEVEL_FATAL)
390 DEFINE_FMT(panic_fmt, ERRORLEVEL_PANIC)
391 
392 /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4 autoindent: */
393