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