1 | /* Check system header files for ISO 9899:1990 (ISO C) compliance. |
2 | Copyright (C) 1996-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library 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 GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* This is a simple minded program that tries to find illegal macro |
20 | definitions in system header files. Illegal macro definitions are |
21 | those not from the implementation namespace (i.e. not starting with |
22 | an underscore) or not matching any identifier mandated by The |
23 | Standard. Some common macro names are considered okay, e.g. all those |
24 | beginning with E (which may be defined in <errno.h>) or ending in |
25 | _MAX. See the arrays prefix[] and suffix[] below for details. |
26 | |
27 | In a compliant implementation no other macros can be defined, because |
28 | you could write strictly conforming programs that may fail to compile |
29 | due to syntax errors: suppose <stdio.h> defines PIPE_BUF, then the |
30 | conforming |
31 | |
32 | #include <assert.h> |
33 | #include <stdio.h> <- or where the bogus macro is defined |
34 | #include <string.h> |
35 | #define STR(x) #x |
36 | #define XSTR(x) STR(x) |
37 | int main (void) |
38 | { |
39 | int PIPE_BUF = 0; |
40 | assert (strcmp ("PIPE_BUF", XSTR (PIPE_BUF)) == 0); |
41 | return 0; |
42 | } |
43 | |
44 | is expected to compile and meet the assertion. If it does not, your |
45 | compiler compiles some other language than Standard C. |
46 | |
47 | REQUIREMENTS: |
48 | This program calls gcc to get the list of defined macros. If you |
49 | don't have gcc you're probably out of luck unless your compiler or |
50 | preprocessor has something similar to gcc's -dM option. Tune |
51 | PRINT_MACROS in this case. This program assumes headers are found |
52 | under /usr/include and that there is a writable /tmp directory. |
53 | Tune SYSTEM_INCLUDE if your system differs. |
54 | #define BROKEN_SYSTEM if system(NULL) bombs -- one more violation |
55 | of ISO C, by the way. |
56 | |
57 | OUTPUT: |
58 | Each header file name is printed, followed by illegal macro names |
59 | and their definition. For the above example, you would see |
60 | ... |
61 | /usr/include/stdio.h |
62 | #define PIPE_BUF 5120 |
63 | ... |
64 | If your implementation does not yet incorporate Amendment 1 you |
65 | will see messages about iso646.h, wctype.h and wchar.h not being |
66 | found. */ |
67 | |
68 | #ifndef _GNU_SOURCE |
69 | # define _GNU_SOURCE 1 |
70 | #endif |
71 | |
72 | #include <ctype.h> |
73 | #include <stdio.h> |
74 | #include <stdlib.h> |
75 | #include <string.h> |
76 | #include <unistd.h> |
77 | |
78 | #define 256 |
79 | |
80 | static char macrofile[] = "/tmp/isomac.XXXXXX" ; |
81 | |
82 | /* ISO C header names including Amendment 1 (without ".h" suffix). */ |
83 | static char *[] = |
84 | { |
85 | "assert" , "ctype" , "errno" , "float" , "iso646" , "limits" , "locale" , |
86 | "math" , "setjmp" , "signal" , "stdarg" , "stddef" , "stdio" , "stdlib" , |
87 | "string" , "time" , "wchar" , "wctype" |
88 | }; |
89 | |
90 | /* Macros with these prefixes are considered okay. */ |
91 | static char *prefix[] = |
92 | { |
93 | "_" , "E" , "is" , "str" , "mem" , "SIG" , "FLT_" , "DBL_" , "LDBL_" , |
94 | "LC_" , "wmem" , "wcs" |
95 | }; |
96 | |
97 | /* Macros with these suffixes are considered okay. Will not work for |
98 | parametrized macros with arguments. */ |
99 | static char *suffix[] = |
100 | { |
101 | "_MAX" , "_MIN" |
102 | }; |
103 | |
104 | /* These macros are considered okay. In fact, these are just more prefixes. */ |
105 | static char *macros[] = |
106 | { |
107 | "BUFSIZ" , "CHAR_BIT" , "CHAR_MAX" , "CHAR_MIN" , "CLOCKS_PER_SEC" , |
108 | "DBL_DIG" , "DBL_EPSILON" , "DBL_MANT_DIG" , "DBL_MAX" , |
109 | "DBL_MAX_10_EXP" , "DBL_MAX_EXP" , "DBL_MIN" , "DBL_MIN_10_EXP" , |
110 | "DBL_MIN_EXP" , "EDOM" , "EILSEQ" , "EOF" , "ERANGE" , "EXIT_FAILURE" , |
111 | "EXIT_SUCCESS" , "FILENAME_MAX" , "FLT_DIG" , "FLT_EPSILON" , |
112 | "FLT_MANT_DIG" , "FLT_MAX" , "FLT_MAX_10_EXP" , "FLT_MAX_EXP" , |
113 | "FLT_MIN" , "FLT_MIN_10_EXP" , "FLT_MIN_EXP" , "FLT_RADIX" , |
114 | "FLT_ROUNDS" , "FOPEN_MAX" , "HUGE_VAL" , "INT_MAX" , "INT_MIN" , |
115 | "LC_ALL" , "LC_COLLATE" , "LC_CTYPE" , "LC_MONETARY" , "LC_NUMERIC" , |
116 | "LC_TIME" , "LDBL_DIG" , "LDBL_EPSILON" , "LDBL_MANT_DIG" , "LDBL_MAX" , |
117 | "LDBL_MAX_10_EXP" , "LDBL_MAX_EXP" , "LDBL_MIN" , "LDBL_MIN_10_EXP" , |
118 | "LDBL_MIN_EXP" , "LONG_MAX" , "LONG_MIN" , "L_tmpnam" , "MB_CUR_MAX" , |
119 | "MB_LEN_MAX" , "NDEBUG" , "NULL" , "RAND_MAX" , "SCHAR_MAX" , |
120 | "SCHAR_MIN" , "SEEK_CUR" , "SEEK_END" , "SEEK_SET" , "SHRT_MAX" , |
121 | "SHRT_MIN" , "SIGABRT" , "SIGFPE" , "SIGILL" , "SIGINT" , "SIGSEGV" , |
122 | "SIGTERM" , "SIG_DFL" , "SIG_ERR" , "SIG_IGN" , "TMP_MAX" , "UCHAR_MAX" , |
123 | "UINT_MAX" , "ULONG_MAX" , "USHRT_MAX" , "WCHAR_MAX" , "WCHAR_MIN" , |
124 | "WEOF" , "_IOFBF" , "_IOLBF" , "_IONBF" , "abort" , "abs" , "acos" , |
125 | "acosf" , "acosl" , "and" , "and_eq" , "asctime" , "asin" , "asinf" , |
126 | "asinl" , "assert" , "atan" , "atan2" , "atan2f" , "atan2l" , "atanf" , |
127 | "atanl" , "atexit" , "atof" , "atoi" , "atol" , "bitand" , "bitor" , |
128 | "bsearch" , "btowc" , "calloc" , "ceil" , "ceilf" , "ceill" , "clearerr" , |
129 | "clock" , "clock_t" , "compl" , "cos" , "cosf" , "cosh" , "coshf" , |
130 | "coshl" , "cosl" , "ctime" , "difftime" , "div" , "div_t" , "errno" , |
131 | "exit" , "exp" , "expf" , "expl" , "fabs" , "fabsf" , "fabsl" , "fclose" , |
132 | "feof" , "ferror" , "fflush" , "fgetc" , "fgetpos" , "fgets" , "fgetwc" , |
133 | "fgetws" , "floor" , "floorf" , "floorl" , "fmod" , "fmodf" , "fmodl" , |
134 | "fopen" , "fprintf" , "fputc" , "fputs" , "fputwc" , "fputws" , "fread" , |
135 | "free" , "freopen" , "frexp" , "frexpf" , "frexpl" , "fscanf" , "fseek" , |
136 | "fsetpos" , "ftell" , "fwide" , "fwprintf" , "fwrite" , "fwscanf" , |
137 | "getc" , "getchar" , "getenv" , "gets" , "getwc" , "getwchar" , "gmtime" , |
138 | "isalnum" , "isalpha" , "iscntrl" , "isdigit" , "isgraph" , "islower" , |
139 | "isprint" , "ispunct" , "isspace" , "isupper" , "iswalnum" , "iswalpha" , |
140 | "iswcntrl" , "iswctype" , "iswdigit" , "iswgraph" , "iswlower" , |
141 | "iswprint" , "iswpunct" , "iswspace" , "iswupper" , "iswxdigit" , |
142 | "isxdigit" , "labs" , "ldexp" , "ldexpf" , "ldexpl" , "ldiv" , "ldiv_t" , |
143 | "localeconv" , "localtime" , "log" , "log10" , "log10f" , "log10l" , |
144 | "logf" , "logl" , "longjmp" , "malloc" , "mblen" , "mbrlen" , "mbrtowc" , |
145 | "mbsinit" , "mbsrtowcs" , "mbstate_t" , "mbstowcs" , "mbtowc" , "memchr" , |
146 | "memcmp" , "memcpy" , "memmove" , "memset" , "mktime" , "modf" , "modff" , |
147 | "modfl" , "not" , "not_eq" , "offsetof" , "or" , "or_eq" , "perror" , |
148 | "pow" , "powf" , "powl" , "printf" , "ptrdiff_t" , "putc" , "putchar" , |
149 | "puts" , "putwc" , "putwchar" , "qsort" , "raise" , "rand" , "realloc" , |
150 | "remove" , "rename" , "rewind" , "scanf" , "setbuf" , "setjmp" , |
151 | "setlocale" , "setvbuf" , "sig_atomic_t" , "signal" , "sin" , "sinf" , |
152 | "sinh" , "sinhf" , "sinhl" , "sinl" , "size_t" , "sprintf" , "sqrt" , |
153 | "sqrtf" , "sqrtl" , "srand" , "sscanf" , "stderr" , "stdin" , "stdout" , |
154 | "strcat" , "strchr" , "strcmp" , "strcoll" , "strcpy" , "strcspn" , |
155 | "strerror" , "strftime" , "strlen" , "strncat" , "strncmp" , "strncpy" , |
156 | "strpbrk" , "strrchr" , "strspn" , "strstr" , "strtod" , "strtok" , |
157 | "strtol" , "strtoul" , "strxfrm" , "swprintf" , "swscanf" , "system" , |
158 | "tan" , "tanf" , "tanh" , "tanhf" , "tanhl" , "tanl" , "time" , "time_t" , |
159 | "tmpfile" , "tmpnam" , "tolower" , "toupper" , "towctrans" , "towlower" , |
160 | "towupper" , "ungetc" , "ungetwc" , "va_arg" , "va_copy" , "va_end" , "va_start" , |
161 | "vfprintf" , "vfwprintf" , "vprintf" , "vsprintf" , "vswprintf" , |
162 | "vwprintf" , "wchar_t" , "wcrtomb" , "wcscat" , "wcschr" , "wcscmp" , |
163 | "wcscoll" , "wcscpy" , "wcscspn" , "wcsftime" , "wcslen" , "wcsncat" , |
164 | "wcsncmp" , "wcsncpy" , "wcspbrk" , "wcsrchr" , "wcsrtombs" , "wcsspn" , |
165 | "wcsstr" , "wcstod" , "wcstok" , "wcstol" , "wcstombs" , "wcstoul" , |
166 | "wcsxfrm" , "wctob" , "wctomb" , "wctrans" , "wctrans_t" , "wctype" , |
167 | "wctype_t" , "wint_t" , "wmemchr" , "wmemcmp" , "wmemcpy" , "wmemmove" , |
168 | "wmemset" , "wprintf" , "wscanf" , "xor" , "xor_eq" |
169 | }; |
170 | |
171 | #define (sizeof header / sizeof *header) |
172 | #define NUMBER_OF_PREFIXES (sizeof prefix / sizeof *prefix) |
173 | #define NUMBER_OF_SUFFIXES (sizeof suffix / sizeof *suffix) |
174 | #define NUMBER_OF_MACROS (sizeof macros / sizeof *macros) |
175 | |
176 | |
177 | /* Format string to build command to invoke compiler. */ |
178 | static const char fmt[] = "\ |
179 | echo \"#include <%s>\" |\ |
180 | %s -E -dM -ansi -pedantic %s -D_LIBC -D_ISOMAC \ |
181 | -DIN_MODULE=MODULE_extramodules -I. \ |
182 | -isystem `%s --print-prog-name=include` - 2> /dev/null > %s" ; |
183 | |
184 | |
185 | /* The compiler we use (given on the command line). */ |
186 | char *CC; |
187 | /* The -I parameters for CC to find all headers. */ |
188 | char *INC; |
189 | |
190 | static char *xstrndup (const char *, size_t); |
191 | static const char **get_null_defines (void); |
192 | static int check_header (const char *, const char **); |
193 | |
194 | int |
195 | main (int argc, char *argv[]) |
196 | { |
197 | int h; |
198 | int result = 0; |
199 | const char **ignore_list; |
200 | |
201 | CC = argc > 1 ? argv[1] : "gcc" ; |
202 | INC = argc > 2 ? argv[2] : "" ; |
203 | |
204 | if (system (NULL) == 0) |
205 | { |
206 | puts (s: "Sorry, no command processor." ); |
207 | return EXIT_FAILURE; |
208 | } |
209 | |
210 | /* First get list of symbols which are defined by the compiler. */ |
211 | ignore_list = get_null_defines (); |
212 | |
213 | fputs ("Tested files:\n" , stdout); |
214 | |
215 | for (h = 0; h < NUMBER_OF_HEADERS; ++h) |
216 | { |
217 | char file_name[HEADER_MAX]; |
218 | sprintf (file_name, "%s.h" , header[h]); |
219 | result |= check_header (file_name, ignore_list); |
220 | } |
221 | |
222 | remove (macrofile); |
223 | |
224 | /* The test suite should return errors but for now this is not |
225 | practical. Give a warning and ask the user to correct the bugs. */ |
226 | return result; |
227 | } |
228 | |
229 | |
230 | static char * |
231 | xstrndup (const char *s, size_t n) |
232 | { |
233 | size_t len = n; |
234 | char *new = malloc (size: len + 1); |
235 | |
236 | if (new == NULL) |
237 | return NULL; |
238 | |
239 | new[len] = '\0'; |
240 | return memcpy (new, s, len); |
241 | } |
242 | |
243 | |
244 | static const char ** |
245 | get_null_defines (void) |
246 | { |
247 | char line[BUFSIZ], *command; |
248 | char **result = NULL; |
249 | size_t result_len = 0; |
250 | size_t result_max = 0; |
251 | FILE *input; |
252 | int first = 1; |
253 | |
254 | int fd = mkstemp (template: macrofile); |
255 | if (fd == -1) |
256 | { |
257 | printf (format: "mkstemp failed: %m\n" ); |
258 | exit (1); |
259 | } |
260 | close (fd: fd); |
261 | |
262 | command = malloc (size: sizeof fmt + sizeof "/dev/null" + 2 * strlen (CC) |
263 | + strlen (INC) + strlen (macrofile)); |
264 | |
265 | if (command == NULL) |
266 | { |
267 | puts (s: "No more memory." ); |
268 | exit (1); |
269 | } |
270 | |
271 | sprintf (command, fmt, "/dev/null" , CC, INC, CC, macrofile); |
272 | |
273 | if (system (command: command)) |
274 | { |
275 | puts (s: "system() returned nonzero" ); |
276 | free (ptr: command); |
277 | return NULL; |
278 | } |
279 | free (ptr: command); |
280 | input = fopen (macrofile, "r" ); |
281 | |
282 | if (input == NULL) |
283 | { |
284 | printf (format: "Could not read %s: " , macrofile); |
285 | perror (NULL); |
286 | return NULL; |
287 | } |
288 | |
289 | while (fgets (s: line, n: sizeof line, stream: input) != NULL) |
290 | { |
291 | int i, okay = 0; |
292 | size_t endmac; |
293 | char *start, *end; |
294 | if (strlen (line) < 9 || line[7] != ' ') |
295 | { /* "#define A" */ |
296 | printf (format: "Malformed input, expected '#define MACRO'\ngot '%s'\n" , |
297 | line); |
298 | continue; |
299 | } |
300 | if (line[8] == '_') |
301 | /* It's a safe identifier. */ |
302 | continue; |
303 | if (result_len == result_max) |
304 | { |
305 | result_max += 10; |
306 | result = realloc (ptr: result, size: result_max * sizeof (char **)); |
307 | if (result == NULL) |
308 | { |
309 | puts (s: "No more memory." ); |
310 | exit (1); |
311 | } |
312 | } |
313 | start = &line[8]; |
314 | for (end = start + 1; !isspace (*end) && *end != '\0'; ++end) |
315 | ; |
316 | result[result_len] = xstrndup (s: start, n: end - start); |
317 | |
318 | if (strcmp (result[result_len], "IN_MODULE" ) != 0) |
319 | { |
320 | if (first) |
321 | { |
322 | fputs ("The following identifiers will be ignored since the compiler defines them\nby default:\n" , stdout); |
323 | first = 0; |
324 | } |
325 | puts (s: result[result_len]); |
326 | } |
327 | ++result_len; |
328 | } |
329 | if (result_len == result_max) |
330 | { |
331 | result_max += 1; |
332 | result = realloc (ptr: result, size: result_max * sizeof (char **)); |
333 | if (result == NULL) |
334 | { |
335 | puts (s: "No more memory." ); |
336 | exit (1); |
337 | } |
338 | } |
339 | result[result_len] = NULL; |
340 | fclose (input); |
341 | |
342 | return (const char **) result; |
343 | } |
344 | |
345 | |
346 | static int |
347 | (const char *file_name, const char **except) |
348 | { |
349 | char line[BUFSIZ], *command; |
350 | FILE *input; |
351 | int result = 0; |
352 | |
353 | command = malloc (size: sizeof fmt + strlen (file_name) + 2 * strlen (CC) |
354 | + strlen (INC) + strlen (macrofile)); |
355 | |
356 | if (command == NULL) |
357 | { |
358 | puts (s: "No more memory." ); |
359 | exit (1); |
360 | } |
361 | |
362 | puts (s: file_name); |
363 | sprintf (command, fmt, file_name, CC, INC, CC, macrofile); |
364 | |
365 | if (system (command: command)) |
366 | { |
367 | puts (s: "system() returned nonzero" ); |
368 | result = 1; |
369 | } |
370 | free (ptr: command); |
371 | input = fopen (macrofile, "r" ); |
372 | |
373 | if (input == NULL) |
374 | { |
375 | printf (format: "Could not read %s: " , macrofile); |
376 | perror (NULL); |
377 | return 1; |
378 | } |
379 | |
380 | while (fgets (s: line, n: sizeof line, stream: input) != NULL) |
381 | { |
382 | int i, okay = 0; |
383 | size_t endmac; |
384 | const char **cpp; |
385 | if (strlen (line) < 9 || line[7] != ' ') |
386 | { /* "#define A" */ |
387 | printf (format: "Malformed input, expected '#define MACRO'\ngot '%s'\n" , |
388 | line); |
389 | result = 1; |
390 | continue; |
391 | } |
392 | for (i = 0; i < NUMBER_OF_PREFIXES; ++i) |
393 | { |
394 | if (!strncmp (line+8, prefix[i], strlen (prefix[i]))) { |
395 | ++okay; |
396 | break; |
397 | } |
398 | } |
399 | if (okay) |
400 | continue; |
401 | for (i = 0; i < NUMBER_OF_MACROS; ++i) |
402 | { |
403 | if (!strncmp (line + 8, macros[i], strlen (macros[i]))) |
404 | { |
405 | ++okay; |
406 | break; |
407 | } |
408 | } |
409 | if (okay) |
410 | continue; |
411 | /* Find next char after the macro identifier; this can be either |
412 | a space or an open parenthesis. */ |
413 | endmac = strcspn (line + 8, " (" ); |
414 | if (line[8+endmac] == '\0') |
415 | { |
416 | printf (format: "malformed input, expected '#define MACRO VALUE'\n" |
417 | "got '%s'\n" , line); |
418 | result = 1; |
419 | continue; |
420 | } |
421 | for (i = 0; i < NUMBER_OF_SUFFIXES; ++i) |
422 | { |
423 | size_t len = strlen (suffix[i]); |
424 | if (!strncmp (line + 8 + endmac - len, suffix[i], len)) |
425 | { |
426 | ++okay; |
427 | break; |
428 | } |
429 | } |
430 | if (okay) |
431 | continue; |
432 | if (except != NULL) |
433 | for (cpp = except; *cpp != NULL; ++cpp) |
434 | { |
435 | size_t len = strlen (*cpp); |
436 | if (!strncmp (line + 8, *cpp, len) && isspace (line[8 + len])) |
437 | { |
438 | ++okay; |
439 | break; |
440 | } |
441 | } |
442 | if (!okay) |
443 | { |
444 | fputs (line, stdout); |
445 | result = 2; |
446 | } |
447 | } |
448 | fclose (input); |
449 | |
450 | return result; |
451 | } |
452 | |
453 | /* EOF */ |
454 | |