xref: /illumos-gate/usr/src/test/libc-tests/tests/symbols/symbols_test.c (revision 0250c53ad267726f2438e3c6556199a0bbf588a2)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2015 Garrett D'Amore <garrett@damore.org>
14  * Copyright 2018 Joyent, Inc.
15  * Copyright 2024 Oxide Computer Company
16  */
17 
18 /*
19  * This program tests symbol visibility in different compilation environments.
20  */
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <err.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <libcustr.h>
31 #include <sys/wait.h>
32 #include <stdbool.h>
33 #include "test_common.h"
34 
35 char *dname;
36 char *cfile;
37 char *ofile;
38 char *lfile;
39 char *efile;
40 
41 const char *sym = NULL;
42 
43 static int good_count = 0;
44 static int fail_count = 0;
45 static int full_count = 0;
46 static int extra_debug = 0;
47 static char *compilation = "compilation.cfg";
48 
49 #if defined(_LP64)
50 #define	MFLAG "-m64"
51 #elif defined(_ILP32)
52 #define	MFLAG "-m32"
53 #endif
54 
55 static const char *compilers[] = {
56 	"gcc",
57 	"clang",
58 	NULL
59 };
60 
61 /*
62  * We turn off -Wformat-security because the auto-generated tests don't pass
63  * string literals to printf family functions, which will trigger warnings in
64  * some compilers (e.g. clang-16).
65  */
66 static const char *compiler = NULL;
67 static const char *common_flags = "-Wall -Werror -nostdinc -isystem "
68 	"/usr/include -Wno-format-security";
69 static const char *c89flags = "-std=c89";
70 static const char *c99flags = "-std=c99";
71 static const char *c11flags = "-std=c11";
72 static const char *c17flags = "-std=c17";
73 
74 #define	MAXENV	64	/* maximum number of environments (bitmask width) */
75 #define	MAXHDR	10	/* maximum # headers to require to access symbol */
76 #define	MAXARG	20	/* maximum # of arguments */
77 
78 #define	WS	" \t"
79 
80 static int next_env = 0;
81 
82 struct compile_env {
83 	char		*ce_name;
84 	char		*ce_lang;
85 	char		*ce_defs;
86 	int		ce_index;
87 };
88 
89 static struct compile_env compile_env[MAXENV];
90 
91 struct env_group {
92 	char			*eg_name;
93 	uint64_t		eg_mask;
94 	struct env_group	*eg_next;
95 };
96 
97 typedef enum {
98 	SYM_TYPE,
99 	SYM_VALUE,
100 	SYM_DEFINE,
101 	SYM_FUNC
102 } sym_type_t;
103 
104 struct sym_test {
105 	char			*st_name;
106 	sym_type_t		st_type;
107 	char			*st_hdrs[MAXHDR];
108 	char			*st_rtype;
109 	char			*st_atypes[MAXARG];
110 	char			*st_defval;
111 	uint64_t		st_test_mask;
112 	uint64_t		st_need_mask;
113 	const char		*st_prog;
114 	struct sym_test		*st_next;
115 };
116 
117 struct env_group *env_groups = NULL;
118 
119 struct sym_test *sym_tests = NULL;
120 struct sym_test **sym_insert = &sym_tests;
121 
122 static char *
123 mystrdup(const char *s)
124 {
125 	char *r;
126 	if ((r = strdup(s)) == NULL) {
127 		perror("strdup");
128 		exit(1);
129 	}
130 	return (r);
131 }
132 
133 static void *
134 myzalloc(size_t sz)
135 {
136 	void *buf;
137 	if ((buf = calloc(1, sz)) == NULL) {
138 		perror("calloc");
139 		exit(1);
140 	}
141 	return (buf);
142 }
143 
144 static void
145 myasprintf(char **buf, const char *fmt, ...)
146 {
147 	int rv;
148 	va_list va;
149 	va_start(va, fmt);
150 	rv = vasprintf(buf, fmt, va);
151 	va_end(va);
152 	if (rv < 0) {
153 		perror("vasprintf");
154 		exit(1);
155 	}
156 }
157 
158 static void
159 append_sym_test(struct sym_test *st)
160 {
161 	*sym_insert = st;
162 	sym_insert = &st->st_next;
163 }
164 
165 static int
166 find_env_mask(const char *name, uint64_t *mask)
167 {
168 	for (int i = 0; i < MAXENV; i++) {
169 		if (compile_env[i].ce_name != NULL &&
170 		    strcmp(compile_env[i].ce_name, name) == 0) {
171 			*mask |= (1ULL << i);
172 			return (0);
173 		}
174 	}
175 
176 	for (struct env_group *eg = env_groups; eg != NULL; eg = eg->eg_next) {
177 		if (strcmp(name, eg->eg_name) == 0) {
178 			*mask |= eg->eg_mask;
179 			return (0);
180 		}
181 	}
182 	return (-1);
183 }
184 
185 
186 static int
187 expand_env(char *list, uint64_t *mask, char **erritem)
188 {
189 	char *item;
190 	for (item = strtok(list, WS); item != NULL; item = strtok(NULL, WS)) {
191 		if (find_env_mask(item, mask) < 0) {
192 			if (erritem != NULL) {
193 				*erritem = item;
194 			}
195 			return (-1);
196 		}
197 	}
198 	return (0);
199 }
200 
201 static int
202 expand_env_list(char *list, uint64_t *test, uint64_t *need, char **erritem)
203 {
204 	uint64_t mask = 0;
205 	int act;
206 	char *item;
207 	for (item = strtok(list, WS); item != NULL; item = strtok(NULL, WS)) {
208 		switch (item[0]) {
209 		case '+':
210 			act = 1;
211 			item++;
212 			break;
213 		case '-':
214 			act = 0;
215 			item++;
216 			break;
217 		default:
218 			act = 1;
219 			break;
220 		}
221 
222 		mask = 0;
223 		if (find_env_mask(item, &mask) < 0) {
224 			if (erritem != NULL) {
225 				*erritem = item;
226 			}
227 			return (-1);
228 		}
229 		*test |= mask;
230 		if (act) {
231 			*need |= mask;
232 		} else {
233 			*need &= ~(mask);
234 		}
235 	}
236 	return (0);
237 }
238 
239 static int
240 do_env(char **fields, int nfields, char **err)
241 {
242 	char *name;
243 	char *lang;
244 	char *defs;
245 
246 	if (nfields != 3) {
247 		myasprintf(err, "number of fields (%d) != 3", nfields);
248 		return (-1);
249 	}
250 
251 	if (next_env >= MAXENV) {
252 		myasprintf(err, "too many environments");
253 		return (-1);
254 	}
255 
256 	name = fields[0];
257 	lang = fields[1];
258 	defs = fields[2];
259 
260 	compile_env[next_env].ce_name = mystrdup(name);
261 	compile_env[next_env].ce_lang = mystrdup(lang);
262 	compile_env[next_env].ce_defs = mystrdup(defs);
263 	compile_env[next_env].ce_index = next_env;
264 	next_env++;
265 	return (0);
266 }
267 
268 static int
269 do_env_group(char **fields, int nfields, char **err)
270 {
271 	char *name;
272 	char *list;
273 	struct env_group *eg;
274 	uint64_t mask;
275 	char *item;
276 
277 	if (nfields != 2) {
278 		myasprintf(err, "number of fields (%d) != 2", nfields);
279 		return (-1);
280 	}
281 
282 	name = fields[0];
283 	list = fields[1];
284 	mask = 0;
285 
286 	if (expand_env(list, &mask, &item) < 0) {
287 		myasprintf(err, "reference to undefined env %s", item);
288 		return (-1);
289 	}
290 
291 	eg = myzalloc(sizeof (*eg));
292 	eg->eg_name = mystrdup(name);
293 	eg->eg_mask = mask;
294 	eg->eg_next = env_groups;
295 	env_groups = eg;
296 	return (0);
297 }
298 
299 static custr_t *st_custr;
300 
301 static void
302 addprogch(char c)
303 {
304 	if (custr_appendc(st_custr, c) == -1) {
305 		perror("custr_appendc");
306 		exit(1);
307 	}
308 }
309 
310 static void
311 addprogstr(char *s)
312 {
313 	if (custr_append(st_custr, s) == -1) {
314 		perror("custr_append");
315 		exit(1);
316 	}
317 }
318 
319 static void
320 addprogfmt(const char *fmt, ...)
321 {
322 	va_list va;
323 	va_start(va, fmt);
324 	if (custr_append_vprintf(st_custr, fmt, va) == -1) {
325 		perror("custr_append_vprintf");
326 		exit(1);
327 	}
328 	va_end(va);
329 }
330 
331 static void
332 mkprog(struct sym_test *st)
333 {
334 	char *s = NULL;
335 
336 	custr_reset(st_custr);
337 
338 	for (int i = 0; i < MAXHDR && st->st_hdrs[i] != NULL; i++) {
339 		addprogfmt("#include <%s>\n", st->st_hdrs[i]);
340 	}
341 
342 	if (st->st_rtype != NULL) {
343 		for (s = st->st_rtype; *s; s++) {
344 			addprogch(*s);
345 			if (*s == '(') {
346 				s++;
347 				addprogch(*s);
348 				s++;
349 				break;
350 			}
351 		}
352 		addprogch(' ');
353 	}
354 
355 	/* for function pointers, s is closing suffix, otherwise empty */
356 
357 	switch (st->st_type) {
358 	case SYM_TYPE:
359 		addprogstr("test_type;");
360 		break;
361 
362 	case SYM_VALUE:
363 		addprogfmt("test_value%s;\n", s);	/* s usually empty */
364 		addprogstr("void\ntest_func(void)\n{\n");
365 		addprogfmt("\ttest_value = %s;\n}", st->st_name);
366 		break;
367 
368 	case SYM_DEFINE:
369 		addprogfmt("#if !defined(%s)", st->st_name);
370 		if (st->st_defval != NULL)
371 			addprogfmt("|| %s != %s", st->st_name, st->st_defval);
372 		addprogfmt("\n#error %s is not defined or has the wrong value",
373 		    st->st_name);
374 		addprogfmt("\n#endif\n");
375 		break;
376 
377 	case SYM_FUNC:
378 		addprogstr("\ntest_func(");
379 		for (int i = 0; i < MAXARG && st->st_atypes[i] != NULL; i++) {
380 			int didname = 0;
381 			if (i > 0) {
382 				addprogstr(", ");
383 			}
384 			if (strcmp(st->st_atypes[i], "void") == 0) {
385 				didname = 1;
386 			}
387 			if (strcmp(st->st_atypes[i], "") == 0) {
388 				didname = 1;
389 				addprogstr("void");
390 			}
391 
392 			/* print the argument list */
393 			for (char *a = st->st_atypes[i]; *a; a++) {
394 				if (*a == '(' && a[1] == '*' && !didname) {
395 					addprogfmt("(*a%d", i);
396 					didname = 1;
397 					a++;
398 				} else if (*a == '[' && !didname) {
399 					addprogfmt("a%d[", i);
400 					didname = 1;
401 				} else {
402 					addprogch(*a);
403 				}
404 			}
405 			if (!didname) {
406 				addprogfmt(" a%d", i);
407 			}
408 		}
409 
410 		if (st->st_atypes[0] == NULL) {
411 			addprogstr("void");
412 		}
413 
414 		/*
415 		 * Close argument list, and closing ")" for func ptrs.
416 		 * Note that for non-function pointers, s will be empty
417 		 * below, otherwise it points to the trailing argument
418 		 * list.
419 		 */
420 		addprogfmt(")%s\n{\n\t", s);
421 
422 		if (strcmp(st->st_rtype, "") != 0 &&
423 		    strcmp(st->st_rtype, "void") != 0) {
424 			addprogstr("return ");
425 		}
426 
427 		/* add the function call */
428 		addprogfmt("%s(", st->st_name);
429 		for (int i = 0; i < MAXARG && st->st_atypes[i] != NULL; i++) {
430 			if (strcmp(st->st_atypes[i], "") != 0 &&
431 			    strcmp(st->st_atypes[i], "void") != 0) {
432 				addprogfmt("%sa%d", i > 0 ? ", " : "", i);
433 			}
434 		}
435 
436 		addprogstr(");\n}");
437 		break;
438 	}
439 
440 	addprogch('\n');
441 
442 	st->st_prog = custr_cstr(st_custr);
443 }
444 
445 static int
446 add_envs(struct sym_test *st, char *envs, char **err)
447 {
448 	char *item;
449 	if (expand_env_list(envs, &st->st_test_mask, &st->st_need_mask,
450 	    &item) < 0) {
451 		myasprintf(err, "bad env action %s", item);
452 		return (-1);
453 	}
454 	return (0);
455 }
456 
457 static int
458 add_headers(struct sym_test *st, char *hdrs, char **err)
459 {
460 	int i = 0;
461 
462 	for (char *h = strsep(&hdrs, ";"); h != NULL; h = strsep(&hdrs, ";")) {
463 		if (i >= MAXHDR) {
464 			myasprintf(err, "too many headers");
465 			return (-1);
466 		}
467 		test_trim(&h);
468 		st->st_hdrs[i++] = mystrdup(h);
469 	}
470 
471 	return (0);
472 }
473 
474 static int
475 add_arg_types(struct sym_test *st, char *atype, char **err)
476 {
477 	int i = 0;
478 	char *a;
479 	for (a = strsep(&atype, ";"); a != NULL; a = strsep(&atype, ";")) {
480 		if (i >= MAXARG) {
481 			myasprintf(err, "too many arguments");
482 			return (-1);
483 		}
484 		test_trim(&a);
485 		st->st_atypes[i++] = mystrdup(a);
486 	}
487 
488 	return (0);
489 }
490 
491 static int
492 do_type(char **fields, int nfields, char **err)
493 {
494 	char *decl;
495 	char *hdrs;
496 	char *envs;
497 	struct sym_test *st;
498 
499 	if (nfields != 3) {
500 		myasprintf(err, "number of fields (%d) != 3", nfields);
501 		return (-1);
502 	}
503 	decl = fields[0];
504 	hdrs = fields[1];
505 	envs = fields[2];
506 
507 	st = myzalloc(sizeof (*st));
508 	st->st_type = SYM_TYPE;
509 	st->st_name = mystrdup(decl);
510 	st->st_rtype = mystrdup(decl);
511 
512 	if ((add_envs(st, envs, err) < 0) ||
513 	    (add_headers(st, hdrs, err) < 0)) {
514 		return (-1);
515 	}
516 	append_sym_test(st);
517 
518 	return (0);
519 }
520 
521 static int
522 do_value(char **fields, int nfields, char **err)
523 {
524 	char *name;
525 	char *type;
526 	char *hdrs;
527 	char *envs;
528 	struct sym_test *st;
529 
530 	if (nfields != 4) {
531 		myasprintf(err, "number of fields (%d) != 4", nfields);
532 		return (-1);
533 	}
534 	name = fields[0];
535 	type = fields[1];
536 	hdrs = fields[2];
537 	envs = fields[3];
538 
539 	st = myzalloc(sizeof (*st));
540 	st->st_type = SYM_VALUE;
541 	st->st_name = mystrdup(name);
542 	st->st_rtype = mystrdup(type);
543 
544 	if ((add_envs(st, envs, err) < 0) ||
545 	    (add_headers(st, hdrs, err) < 0)) {
546 		return (-1);
547 	}
548 	append_sym_test(st);
549 
550 	return (0);
551 }
552 
553 static int
554 do_define(char **fields, int nfields, char **err)
555 {
556 	char *name, *value, *hdrs, *envs;
557 	struct sym_test *st;
558 
559 	if (nfields != 4) {
560 		myasprintf(err, "number of fields (%d) != 4", nfields);
561 		return (-1);
562 	}
563 
564 	name = fields[0];
565 	value = fields[1];
566 	hdrs = fields[2];
567 	envs = fields[3];
568 
569 	st = myzalloc(sizeof (*st));
570 	st->st_type = SYM_DEFINE;
571 	st->st_name = mystrdup(name);
572 
573 	/*
574 	 * A value to compare against is optional. trim will leave it as a null
575 	 * pointer if there's nothing there.
576 	 */
577 	test_trim(&value);
578 	if (*value != '\0')
579 		st->st_defval = mystrdup(value);
580 
581 	if ((add_envs(st, envs, err) < 0) ||
582 	    (add_headers(st, hdrs, err) < 0)) {
583 		return (-1);
584 	}
585 
586 	append_sym_test(st);
587 
588 	return (0);
589 }
590 
591 static int
592 do_func(char **fields, int nfields, char **err)
593 {
594 	char *name;
595 	char *rtype;
596 	char *atype;
597 	char *hdrs;
598 	char *envs;
599 	struct sym_test *st;
600 
601 	if (nfields != 5) {
602 		myasprintf(err, "number of fields (%d) != 5", nfields);
603 		return (-1);
604 	}
605 	name = fields[0];
606 	rtype = fields[1];
607 	atype = fields[2];
608 	hdrs = fields[3];
609 	envs = fields[4];
610 
611 	st = myzalloc(sizeof (*st));
612 	st->st_type = SYM_FUNC;
613 	st->st_name = mystrdup(name);
614 	st->st_rtype = mystrdup(rtype);
615 
616 	if ((add_envs(st, envs, err) < 0) ||
617 	    (add_headers(st, hdrs, err) < 0) ||
618 	    (add_arg_types(st, atype, err) < 0)) {
619 		return (-1);
620 	}
621 	append_sym_test(st);
622 
623 	return (0);
624 }
625 
626 struct sym_test *
627 next_sym_test(struct sym_test *st)
628 {
629 	return (st == NULL ? sym_tests : st->st_next);
630 }
631 
632 const char *
633 sym_test_prog(struct sym_test *st)
634 {
635 	if (st->st_prog == NULL) {
636 		mkprog(st);
637 	}
638 	return (st->st_prog);
639 }
640 
641 const char *
642 sym_test_name(struct sym_test *st)
643 {
644 	return (st->st_name);
645 }
646 
647 /*
648  * Iterate through tests.  Pass in NULL for cenv to begin the iteration. For
649  * subsequent iterations, use the return value from the previous iteration.
650  * Returns NULL when there are no more environments.
651  */
652 struct compile_env *
653 sym_test_env(struct sym_test *st, struct compile_env *cenv, int *need)
654 {
655 	int i = cenv ? cenv->ce_index + 1: 0;
656 	uint64_t b = 1ULL << i;
657 
658 	while ((i < MAXENV) && (b != 0)) {
659 		cenv = &compile_env[i];
660 		if (b & st->st_test_mask) {
661 			*need = (st->st_need_mask & b) ? 1 : 0;
662 			return (cenv);
663 		}
664 		b <<= 1;
665 		i++;
666 	}
667 	return (NULL);
668 }
669 
670 const char *
671 env_name(struct compile_env *cenv)
672 {
673 	return (cenv->ce_name);
674 }
675 
676 const char *
677 env_lang(struct compile_env *cenv)
678 {
679 	return (cenv->ce_lang);
680 }
681 
682 const char *
683 env_defs(struct compile_env *cenv)
684 {
685 	return (cenv->ce_defs);
686 }
687 
688 static void
689 show_file(test_t t, const char *path)
690 {
691 	FILE *f;
692 	char *buf = NULL;
693 	size_t cap = 0;
694 	int line = 1;
695 
696 	f = fopen(path, "r");
697 	if (f == NULL) {
698 		test_debugf(t, "fopen(%s): %s", path, strerror(errno));
699 		return;
700 	}
701 
702 	test_debugf(t, "----->> begin (%s) <<------", path);
703 	while (getline(&buf, &cap, f) >= 0) {
704 		(void) strtok(buf, "\r\n");
705 		test_debugf(t, "%d: %s", line, buf);
706 		line++;
707 	}
708 	test_debugf(t, "----->> end (%s) <<------", path);
709 	(void) fclose(f);
710 }
711 
712 static void
713 cleanup(void)
714 {
715 	if (ofile != NULL) {
716 		(void) unlink(ofile);
717 		free(ofile);
718 		ofile = NULL;
719 	}
720 	if (lfile != NULL) {
721 		(void) unlink(lfile);
722 		free(lfile);
723 		lfile = NULL;
724 	}
725 	if (cfile != NULL) {
726 		(void) unlink(cfile);
727 		free(cfile);
728 		cfile = NULL;
729 	}
730 	if (efile != NULL) {
731 		(void) unlink(efile);
732 		free(efile);
733 		efile = NULL;
734 	}
735 	if (dname) {
736 		(void) rmdir(dname);
737 		free(dname);
738 		dname = NULL;
739 	}
740 }
741 
742 static int
743 mkworkdir(void)
744 {
745 	char b[32];
746 	char *d;
747 
748 	cleanup();
749 
750 	(void) strlcpy(b, "/tmp/symbols_testXXXXXX", sizeof (b));
751 	if ((d = mkdtemp(b)) == NULL) {
752 		perror("mkdtemp");
753 		return (-1);
754 	}
755 	dname = mystrdup(d);
756 	myasprintf(&cfile, "%s/compile_test.c", d);
757 	myasprintf(&ofile, "%s/compile_test.o", d);
758 	myasprintf(&lfile, "%s/compile_test.log", d);
759 	myasprintf(&efile, "%s/compile_test.exe", d);
760 	return (0);
761 }
762 
763 typedef enum {
764 	SYM_COMP_STUDIO	= 51,
765 	SYM_COMP_CLANG,
766 	SYM_COMP_GCC,
767 	SYM_COMP_UNKNOWN = 99
768 } sym_comp_t;
769 
770 static bool
771 test_compiler(test_t t, const char *cc)
772 {
773 	char cmd[256];
774 	int rv;
775 
776 	(void) snprintf(cmd, sizeof (cmd), "%s %s %s -o %s >/dev/null 2>&1",
777 	    cc, MFLAG, cfile, efile);
778 	test_debugf(t, "trying %s", cmd);
779 	rv = system(cmd);
780 
781 	test_debugf(t, "result: %d", rv);
782 
783 	if ((rv < 0) || !WIFEXITED(rv) || WEXITSTATUS(rv) != 0)
784 		return (false);
785 
786 	rv = system(efile);
787 	if (rv >= 0 && WIFEXITED(rv)) {
788 		rv = WEXITSTATUS(rv);
789 	} else {
790 		rv = -1;
791 	}
792 
793 	switch (rv) {
794 	case SYM_COMP_STUDIO:
795 		test_failed(t, "Sun Studio is not supported");
796 		return (false);
797 	case SYM_COMP_CLANG:
798 		test_debugf(t, "Found clang");
799 		test_passed(t);
800 		break;
801 	case SYM_COMP_GCC:
802 		test_debugf(t, "Found gcc");
803 		test_passed(t);
804 		break;
805 	case SYM_COMP_UNKNOWN:
806 		test_debugf(t, "Found unknown (unsupported) compiler");
807 		return (false);
808 	default:
809 		return (false);
810 	}
811 
812 	test_debugf(t, "compiler: %s", cc);
813 	return (true);
814 }
815 
816 static void
817 find_compiler(void)
818 {
819 	test_t t;
820 	int i;
821 	FILE *cf;
822 
823 	t = test_start("finding compiler");
824 
825 	if ((cf = fopen(cfile, "w+")) == NULL) {
826 		test_failed(t, "Unable to open %s for write: %s", cfile,
827 		    strerror(errno));
828 		return;
829 	}
830 
831 	/*
832 	 * clang defines both __GNUC__ and __clang__, therefore test for
833 	 * __clang__ ahead of __GNUC__.
834 	 */
835 	(void) fprintf(cf, "#include <stdlib.h>\n");
836 	(void) fprintf(cf, "int main(int argc, char **argv) {\n");
837 	(void) fprintf(cf, "#if defined(__SUNPRO_C)\n");
838 	(void) fprintf(cf, "exit(%d);\n", SYM_COMP_STUDIO);
839 	(void) fprintf(cf, "#elif defined(__clang__)\n");
840 	(void) fprintf(cf, "exit(%d);\n", SYM_COMP_CLANG);
841 	(void) fprintf(cf, "#elif defined(__GNUC__)\n");
842 	(void) fprintf(cf, "exit(%d);\n", SYM_COMP_GCC);
843 	(void) fprintf(cf, "#else\n");
844 	(void) fprintf(cf, "exit(%d)\n", SYM_COMP_UNKNOWN);
845 	(void) fprintf(cf, "#endif\n}\n");
846 	(void) fclose(cf);
847 
848 	if (compiler != NULL) {
849 		if (test_compiler(t, compiler)) {
850 			return;
851 		}
852 
853 		test_failed(t, "compiler %s is not usable", compiler);
854 	}
855 
856 	for (i = 0; compilers[i] != NULL; i++) {
857 		if (test_compiler(t, compilers[i])) {
858 			compiler = compilers[i];
859 			return;
860 		}
861 	}
862 	test_failed(t, "No compiler found.");
863 }
864 
865 static int
866 do_compile(test_t t, struct sym_test *st, struct compile_env *cenv, int need)
867 {
868 	char *cmd;
869 	FILE *logf;
870 	FILE *dotc;
871 	const char *prog, *cflags, *lang;
872 
873 	full_count++;
874 
875 	if ((dotc = fopen(cfile, "w+")) == NULL) {
876 		test_failed(t, "fopen(%s): %s", cfile, strerror(errno));
877 		return (-1);
878 	}
879 	prog = sym_test_prog(st);
880 	if (fwrite(prog, 1, strlen(prog), dotc) < strlen(prog)) {
881 		test_failed(t, "fwrite: %s", strerror(errno));
882 		(void) fclose(dotc);
883 		return (-1);
884 	}
885 	if (fclose(dotc) < 0) {
886 		test_failed(t, "fclose: %s", strerror(errno));
887 		return (-1);
888 	}
889 
890 	(void) unlink(ofile);
891 
892 	if (strcmp(env_lang(cenv), "c99") == 0) {
893 		lang = "c99";
894 		cflags = c99flags;
895 	} else if (strcmp(env_lang(cenv), "c11") == 0) {
896 		lang = "c11";
897 		cflags = c11flags;
898 	} else if (strcmp(env_lang(cenv), "c17") == 0) {
899 		lang = "c17";
900 		cflags = c17flags;
901 	} else {
902 		lang = "c89";
903 		cflags = c89flags;
904 	}
905 
906 	if (cflags == NULL) {
907 		test_failed(t, "compiler %s does not support %s", compiler,
908 		    lang);
909 		return (-1);
910 	}
911 
912 	myasprintf(&cmd, "%s %s %s %s -c %s -o %s >>%s 2>&1",
913 	    compiler, cflags, common_flags, env_defs(cenv), cfile, ofile,
914 	    lfile);
915 
916 	if (extra_debug) {
917 		test_debugf(t, "command: %s", cmd);
918 	}
919 
920 	if ((logf = fopen(lfile, "w+")) == NULL) {
921 		test_failed(t, "fopen: %s", strerror(errno));
922 		return (-1);
923 	}
924 	(void) fprintf(logf, "===================\n");
925 	(void) fprintf(logf, "PROGRAM:\n%s\n", sym_test_prog(st));
926 	(void) fprintf(logf, "COMMAND: %s\n", cmd);
927 	(void) fprintf(logf, "EXPECT: %s\n", need ? "OK" : "FAIL");
928 	(void) fclose(logf);
929 
930 	switch (system(cmd)) {
931 	case -1:
932 		test_failed(t, "error compiling in %s: %s", env_name(cenv),
933 		    strerror(errno));
934 		return (-1);
935 	case 0:
936 		if (!need) {
937 			fail_count++;
938 			show_file(t, lfile);
939 			test_failed(t, "symbol visible in %s", env_name(cenv));
940 			return (-1);
941 		}
942 		break;
943 	default:
944 		if (need) {
945 			fail_count++;
946 			show_file(t, lfile);
947 			test_failed(t, "error compiling in %s", env_name(cenv));
948 			return (-1);
949 		}
950 		break;
951 	}
952 	good_count++;
953 	return (0);
954 }
955 
956 static void
957 test_compile(void)
958 {
959 	struct sym_test *st;
960 	struct compile_env *cenv;
961 	test_t t;
962 	int need;
963 
964 	for (st = next_sym_test(NULL); st; st = next_sym_test(st)) {
965 		if ((sym != NULL) && strcmp(sym, sym_test_name(st))) {
966 			continue;
967 		}
968 		/* XXX: we really want a sym_test_desc() */
969 		for (cenv = sym_test_env(st, NULL, &need);
970 		    cenv != NULL;
971 		    cenv = sym_test_env(st, cenv, &need)) {
972 			t = test_start("%s : %c%s", sym_test_name(st),
973 			    need ? '+' : '-', env_name(cenv));
974 			if (do_compile(t, st, cenv, need) == 0) {
975 				test_passed(t);
976 			}
977 		}
978 	}
979 
980 	if (full_count > 0) {
981 		test_summary();
982 	}
983 }
984 
985 int
986 main(int argc, char **argv)
987 {
988 	int optc;
989 	int optC = 0;
990 
991 	while ((optc = getopt(argc, argv, "DdfCs:c:")) != EOF) {
992 		switch (optc) {
993 		case 'd':
994 			test_set_debug();
995 			break;
996 		case 'f':
997 			test_set_force();
998 			break;
999 		case 'D':
1000 			test_set_debug();
1001 			extra_debug++;
1002 			break;
1003 		case 'c':
1004 			compiler = optarg;
1005 			break;
1006 		case 'C':
1007 			optC++;
1008 			break;
1009 		case 's':
1010 			sym = optarg;
1011 			break;
1012 		default:
1013 			(void) fprintf(stderr, "Usage: %s [-df]\n", argv[0]);
1014 			exit(1);
1015 		}
1016 	}
1017 
1018 	if (test_load_config(NULL, compilation,
1019 	    "env", do_env, "env_group", do_env_group, NULL) < 0) {
1020 		exit(1);
1021 	}
1022 
1023 	while (optind < argc) {
1024 		if (test_load_config(NULL, argv[optind++],
1025 		    "type", do_type,
1026 		    "value", do_value,
1027 		    "define", do_define,
1028 		    "func", do_func,
1029 		    NULL) < 0) {
1030 			exit(1);
1031 		}
1032 	}
1033 
1034 	if (atexit(cleanup) != 0) {
1035 		perror("atexit");
1036 		exit(1);
1037 	}
1038 
1039 	if (custr_alloc(&st_custr) == -1) {
1040 		perror("custr");
1041 		exit(1);
1042 	}
1043 
1044 	if (mkworkdir() < 0) {
1045 		perror("mkdir");
1046 		exit(1);
1047 	}
1048 
1049 	find_compiler();
1050 	if (!optC)
1051 		test_compile();
1052 
1053 	exit(0);
1054 }
1055