/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015 Garrett D'Amore * Copyright 2018 Joyent, Inc. */ /* * This program tests symbol visibility in different compilation environments. */ #include #include #include #include #include #include #include #include #include #include #include #include "test_common.h" char *dname; char *cfile; char *ofile; char *lfile; char *efile; const char *sym = NULL; static int good_count = 0; static int fail_count = 0; static int full_count = 0; static int extra_debug = 0; static char *compilation = "compilation.cfg"; #if defined(_LP64) #define MFLAG "-m64" #elif defined(_ILP32) #define MFLAG "-m32" #endif const char *compilers[] = { "cc", "gcc", "/opt/SUNWspro/bin/cc", "/opt/gcc/4.4.4/bin/gcc", "/opt/sunstudio12.1/bin/cc", "/opt/sfw/bin/gcc", "/usr/local/bin/gcc", NULL }; char *compiler = NULL; const char *c89flags = NULL; const char *c99flags = NULL; const char *c11flags = NULL; #define MAXENV 64 /* maximum number of environments (bitmask width) */ #define MAXHDR 10 /* maximum # headers to require to access symbol */ #define MAXARG 20 /* maximum # of arguments */ #define WS " \t" static int next_env = 0; struct compile_env { char *ce_name; char *ce_lang; char *ce_defs; int ce_index; }; static struct compile_env compile_env[MAXENV]; struct env_group { char *eg_name; uint64_t eg_mask; struct env_group *eg_next; }; typedef enum { SYM_TYPE, SYM_VALUE, SYM_DEFINE, SYM_FUNC } sym_type_t; struct sym_test { char *st_name; sym_type_t st_type; char *st_hdrs[MAXHDR]; char *st_rtype; char *st_atypes[MAXARG]; char *st_defval; uint64_t st_test_mask; uint64_t st_need_mask; const char *st_prog; struct sym_test *st_next; }; struct env_group *env_groups = NULL; struct sym_test *sym_tests = NULL; struct sym_test **sym_insert = &sym_tests; static char * mystrdup(const char *s) { char *r; if ((r = strdup(s)) == NULL) { perror("strdup"); exit(1); } return (r); } static void * myzalloc(size_t sz) { void *buf; if ((buf = calloc(1, sz)) == NULL) { perror("calloc"); exit(1); } return (buf); } static void myasprintf(char **buf, const char *fmt, ...) { int rv; va_list va; va_start(va, fmt); rv = vasprintf(buf, fmt, va); va_end(va); if (rv < 0) { perror("vasprintf"); exit(1); } } static void append_sym_test(struct sym_test *st) { *sym_insert = st; sym_insert = &st->st_next; } static int find_env_mask(const char *name, uint64_t *mask) { for (int i = 0; i < MAXENV; i++) { if (compile_env[i].ce_name != NULL && strcmp(compile_env[i].ce_name, name) == 0) { *mask |= (1ULL << i); return (0); } } for (struct env_group *eg = env_groups; eg != NULL; eg = eg->eg_next) { if (strcmp(name, eg->eg_name) == 0) { *mask |= eg->eg_mask; return (0); } } return (-1); } static int expand_env(char *list, uint64_t *mask, char **erritem) { char *item; for (item = strtok(list, WS); item != NULL; item = strtok(NULL, WS)) { if (find_env_mask(item, mask) < 0) { if (erritem != NULL) { *erritem = item; } return (-1); } } return (0); } static int expand_env_list(char *list, uint64_t *test, uint64_t *need, char **erritem) { uint64_t mask = 0; int act; char *item; for (item = strtok(list, WS); item != NULL; item = strtok(NULL, WS)) { switch (item[0]) { case '+': act = 1; item++; break; case '-': act = 0; item++; break; default: act = 1; break; } mask = 0; if (find_env_mask(item, &mask) < 0) { if (erritem != NULL) { *erritem = item; } return (-1); } *test |= mask; if (act) { *need |= mask; } else { *need &= ~(mask); } } return (0); } static int do_env(char **fields, int nfields, char **err) { char *name; char *lang; char *defs; if (nfields != 3) { myasprintf(err, "number of fields (%d) != 3", nfields); return (-1); } if (next_env >= MAXENV) { myasprintf(err, "too many environments"); return (-1); } name = fields[0]; lang = fields[1]; defs = fields[2]; compile_env[next_env].ce_name = mystrdup(name); compile_env[next_env].ce_lang = mystrdup(lang); compile_env[next_env].ce_defs = mystrdup(defs); compile_env[next_env].ce_index = next_env; next_env++; return (0); } static int do_env_group(char **fields, int nfields, char **err) { char *name; char *list; struct env_group *eg; uint64_t mask; char *item; if (nfields != 2) { myasprintf(err, "number of fields (%d) != 2", nfields); return (-1); } name = fields[0]; list = fields[1]; mask = 0; if (expand_env(list, &mask, &item) < 0) { myasprintf(err, "reference to undefined env %s", item); return (-1); } eg = myzalloc(sizeof (*eg)); eg->eg_name = mystrdup(name); eg->eg_mask = mask; eg->eg_next = env_groups; env_groups = eg; return (0); } static custr_t *st_custr; static void addprogch(char c) { if (custr_appendc(st_custr, c) == -1) { perror("custr_appendc"); exit(1); } } static void addprogstr(char *s) { if (custr_append(st_custr, s) == -1) { perror("custr_append"); exit(1); } } static void addprogfmt(const char *fmt, ...) { va_list va; va_start(va, fmt); if (custr_append_vprintf(st_custr, fmt, va) == -1) { perror("custr_append_vprintf"); exit(1); } va_end(va); } static void mkprog(struct sym_test *st) { char *s = NULL; custr_reset(st_custr); for (int i = 0; i < MAXHDR && st->st_hdrs[i] != NULL; i++) { addprogfmt("#include <%s>\n", st->st_hdrs[i]); } if (st->st_rtype != NULL) { for (s = st->st_rtype; *s; s++) { addprogch(*s); if (*s == '(') { s++; addprogch(*s); s++; break; } } addprogch(' '); } /* for function pointers, s is closing suffix, otherwise empty */ switch (st->st_type) { case SYM_TYPE: addprogstr("test_type;"); break; case SYM_VALUE: addprogfmt("test_value%s;\n", s); /* s usually empty */ addprogstr("void\ntest_func(void)\n{\n"); addprogfmt("\ttest_value = %s;\n}", st->st_name); break; case SYM_DEFINE: addprogfmt("#if !defined(%s)", st->st_name); if (st->st_defval != NULL) addprogfmt("|| %s != %s", st->st_name, st->st_defval); addprogfmt("\n#error %s is not defined or has the wrong value", st->st_name); addprogfmt("\n#endif\n"); break; case SYM_FUNC: addprogstr("\ntest_func("); for (int i = 0; i < MAXARG && st->st_atypes[i] != NULL; i++) { int didname = 0; if (i > 0) { addprogstr(", "); } if (strcmp(st->st_atypes[i], "void") == 0) { didname = 1; } if (strcmp(st->st_atypes[i], "") == 0) { didname = 1; addprogstr("void"); } /* print the argument list */ for (char *a = st->st_atypes[i]; *a; a++) { if (*a == '(' && a[1] == '*' && !didname) { addprogfmt("(*a%d", i); didname = 1; a++; } else if (*a == '[' && !didname) { addprogfmt("a%d[", i); didname = 1; } else { addprogch(*a); } } if (!didname) { addprogfmt(" a%d", i); } } if (st->st_atypes[0] == NULL) { addprogstr("void"); } /* * Close argument list, and closing ")" for func ptrs. * Note that for non-function pointers, s will be empty * below, otherwise it points to the trailing argument * list. */ addprogfmt(")%s\n{\n\t", s); if (strcmp(st->st_rtype, "") != 0 && strcmp(st->st_rtype, "void") != 0) { addprogstr("return "); } /* add the function call */ addprogfmt("%s(", st->st_name); for (int i = 0; i < MAXARG && st->st_atypes[i] != NULL; i++) { if (strcmp(st->st_atypes[i], "") != 0 && strcmp(st->st_atypes[i], "void") != 0) { addprogfmt("%sa%d", i > 0 ? ", " : "", i); } } addprogstr(");\n}"); break; } addprogch('\n'); st->st_prog = custr_cstr(st_custr); } static int add_envs(struct sym_test *st, char *envs, char **err) { char *item; if (expand_env_list(envs, &st->st_test_mask, &st->st_need_mask, &item) < 0) { myasprintf(err, "bad env action %s", item); return (-1); } return (0); } static int add_headers(struct sym_test *st, char *hdrs, char **err) { int i = 0; for (char *h = strsep(&hdrs, ";"); h != NULL; h = strsep(&hdrs, ";")) { if (i >= MAXHDR) { myasprintf(err, "too many headers"); return (-1); } test_trim(&h); st->st_hdrs[i++] = mystrdup(h); } return (0); } static int add_arg_types(struct sym_test *st, char *atype, char **err) { int i = 0; char *a; for (a = strsep(&atype, ";"); a != NULL; a = strsep(&atype, ";")) { if (i >= MAXARG) { myasprintf(err, "too many arguments"); return (-1); } test_trim(&a); st->st_atypes[i++] = mystrdup(a); } return (0); } static int do_type(char **fields, int nfields, char **err) { char *decl; char *hdrs; char *envs; struct sym_test *st; if (nfields != 3) { myasprintf(err, "number of fields (%d) != 3", nfields); return (-1); } decl = fields[0]; hdrs = fields[1]; envs = fields[2]; st = myzalloc(sizeof (*st)); st->st_type = SYM_TYPE; st->st_name = mystrdup(decl); st->st_rtype = mystrdup(decl); if ((add_envs(st, envs, err) < 0) || (add_headers(st, hdrs, err) < 0)) { return (-1); } append_sym_test(st); return (0); } static int do_value(char **fields, int nfields, char **err) { char *name; char *type; char *hdrs; char *envs; struct sym_test *st; if (nfields != 4) { myasprintf(err, "number of fields (%d) != 4", nfields); return (-1); } name = fields[0]; type = fields[1]; hdrs = fields[2]; envs = fields[3]; st = myzalloc(sizeof (*st)); st->st_type = SYM_VALUE; st->st_name = mystrdup(name); st->st_rtype = mystrdup(type); if ((add_envs(st, envs, err) < 0) || (add_headers(st, hdrs, err) < 0)) { return (-1); } append_sym_test(st); return (0); } static int do_define(char **fields, int nfields, char **err) { char *name, *value, *hdrs, *envs; struct sym_test *st; if (nfields != 4) { myasprintf(err, "number of fields (%d) != 4", nfields); return (-1); } name = fields[0]; value = fields[1]; hdrs = fields[2]; envs = fields[3]; st = myzalloc(sizeof (*st)); st->st_type = SYM_DEFINE; st->st_name = mystrdup(name); /* * A value to compare against is optional. trim will leave it as a null * pointer if there's nothing there. */ test_trim(&value); if (*value != '\0') st->st_defval = mystrdup(value); if ((add_envs(st, envs, err) < 0) || (add_headers(st, hdrs, err) < 0)) { return (-1); } append_sym_test(st); return (0); } static int do_func(char **fields, int nfields, char **err) { char *name; char *rtype; char *atype; char *hdrs; char *envs; struct sym_test *st; if (nfields != 5) { myasprintf(err, "number of fields (%d) != 5", nfields); return (-1); } name = fields[0]; rtype = fields[1]; atype = fields[2]; hdrs = fields[3]; envs = fields[4]; st = myzalloc(sizeof (*st)); st->st_type = SYM_FUNC; st->st_name = mystrdup(name); st->st_rtype = mystrdup(rtype); if ((add_envs(st, envs, err) < 0) || (add_headers(st, hdrs, err) < 0) || (add_arg_types(st, atype, err) < 0)) { return (-1); } append_sym_test(st); return (0); } struct sym_test * next_sym_test(struct sym_test *st) { return (st == NULL ? sym_tests : st->st_next); } const char * sym_test_prog(struct sym_test *st) { if (st->st_prog == NULL) { mkprog(st); } return (st->st_prog); } const char * sym_test_name(struct sym_test *st) { return (st->st_name); } /* * Iterate through tests. Pass in NULL for cenv to begin the iteration. For * subsequent iterations, use the return value from the previous iteration. * Returns NULL when there are no more environments. */ struct compile_env * sym_test_env(struct sym_test *st, struct compile_env *cenv, int *need) { int i = cenv ? cenv->ce_index + 1: 0; uint64_t b = 1ULL << i; while ((i < MAXENV) && (b != 0)) { cenv = &compile_env[i]; if (b & st->st_test_mask) { *need = (st->st_need_mask & b) ? 1 : 0; return (cenv); } b <<= 1; i++; } return (NULL); } const char * env_name(struct compile_env *cenv) { return (cenv->ce_name); } const char * env_lang(struct compile_env *cenv) { return (cenv->ce_lang); } const char * env_defs(struct compile_env *cenv) { return (cenv->ce_defs); } static void show_file(test_t t, const char *path) { FILE *f; char *buf = NULL; size_t cap = 0; int line = 1; f = fopen(path, "r"); if (f == NULL) { test_debugf(t, "fopen(%s): %s", path, strerror(errno)); return; } test_debugf(t, "----->> begin (%s) <<------", path); while (getline(&buf, &cap, f) >= 0) { (void) strtok(buf, "\r\n"); test_debugf(t, "%d: %s", line, buf); line++; } test_debugf(t, "----->> end (%s) <<------", path); (void) fclose(f); } static void cleanup(void) { if (ofile != NULL) { (void) unlink(ofile); free(ofile); ofile = NULL; } if (lfile != NULL) { (void) unlink(lfile); free(lfile); lfile = NULL; } if (cfile != NULL) { (void) unlink(cfile); free(cfile); cfile = NULL; } if (efile != NULL) { (void) unlink(efile); free(efile); efile = NULL; } if (dname) { (void) rmdir(dname); free(dname); dname = NULL; } } static int mkworkdir(void) { char b[32]; char *d; cleanup(); (void) strlcpy(b, "/tmp/symbols_testXXXXXX", sizeof (b)); if ((d = mkdtemp(b)) == NULL) { perror("mkdtemp"); return (-1); } dname = mystrdup(d); myasprintf(&cfile, "%s/compile_test.c", d); myasprintf(&ofile, "%s/compile_test.o", d); myasprintf(&lfile, "%s/compile_test.log", d); myasprintf(&efile, "%s/compile_test.exe", d); return (0); } void find_compiler(void) { test_t t; int i; FILE *cf; t = test_start("finding compiler"); if ((cf = fopen(cfile, "w+")) == NULL) { test_failed(t, "Unable to open %s for write: %s", cfile, strerror(errno)); return; } (void) fprintf(cf, "#include \n"); (void) fprintf(cf, "int main(int argc, char **argv) {\n"); (void) fprintf(cf, "#if defined(__SUNPRO_C)\n"); (void) fprintf(cf, "exit(51);\n"); (void) fprintf(cf, "#elif defined(__GNUC__)\n"); (void) fprintf(cf, "exit(52);\n"); (void) fprintf(cf, "#else\n"); (void) fprintf(cf, "exit(99)\n"); (void) fprintf(cf, "#endif\n}\n"); (void) fclose(cf); for (i = 0; compilers[i] != NULL; i++) { char cmd[256]; int rv; (void) snprintf(cmd, sizeof (cmd), "%s %s %s -o %s >/dev/null 2>&1", compilers[i], MFLAG, cfile, efile); test_debugf(t, "trying %s", cmd); rv = system(cmd); test_debugf(t, "result: %d", rv); if ((rv < 0) || !WIFEXITED(rv) || WEXITSTATUS(rv) != 0) continue; rv = system(efile); if (rv >= 0 && WIFEXITED(rv)) { rv = WEXITSTATUS(rv); } else { rv = -1; } switch (rv) { case 51: /* STUDIO */ test_debugf(t, "Found Studio C"); c89flags = "-Xc -errwarn=%all -v -xc99=%none " MFLAG; c99flags = "-Xc -errwarn=%all -v -xc99=%all " MFLAG; c11flags = NULL; if (extra_debug) { test_debugf(t, "c89flags: %s", c89flags); test_debugf(t, "c99flags: %s", c99flags); } test_passed(t); break; case 52: /* GCC */ test_debugf(t, "Found GNU C"); c89flags = "-Wall -Werror -std=c89 -nostdinc " "-isystem /usr/include " MFLAG; c99flags = "-Wall -Werror -std=c99 -nostdinc " "-isystem /usr/include " MFLAG; c11flags = "-Wall -Werror -std=c11 -nostdinc " "-isystem /usr/include " MFLAG; if (extra_debug) { test_debugf(t, "c89flags: %s", c89flags); test_debugf(t, "c99flags: %s", c99flags); } test_passed(t); break; case 99: test_debugf(t, "Found unknown (unsupported) compiler"); continue; default: continue; } myasprintf(&compiler, "%s", compilers[i]); test_debugf(t, "compiler: %s", compiler); return; } test_failed(t, "No compiler found."); } int do_compile(test_t t, struct sym_test *st, struct compile_env *cenv, int need) { char *cmd; FILE *logf; FILE *dotc; const char *prog, *cflags, *lang; full_count++; if ((dotc = fopen(cfile, "w+")) == NULL) { test_failed(t, "fopen(%s): %s", cfile, strerror(errno)); return (-1); } prog = sym_test_prog(st); if (fwrite(prog, 1, strlen(prog), dotc) < strlen(prog)) { test_failed(t, "fwrite: %s", strerror(errno)); (void) fclose(dotc); return (-1); } if (fclose(dotc) < 0) { test_failed(t, "fclose: %s", strerror(errno)); return (-1); } (void) unlink(ofile); if (strcmp(env_lang(cenv), "c99") == 0) { lang = "c99"; cflags = c99flags; } else if (strcmp(env_lang(cenv), "c11") == 0) { lang = "c11"; cflags = c11flags; } else { lang = "c89"; cflags = c89flags; } if (cflags == NULL) { test_failed(t, "compiler %s does not support %s", compiler, lang); return (-1); } myasprintf(&cmd, "%s %s %s -c %s -o %s >>%s 2>&1", compiler, cflags, env_defs(cenv), cfile, ofile, lfile); if (extra_debug) { test_debugf(t, "command: %s", cmd); } if ((logf = fopen(lfile, "w+")) == NULL) { test_failed(t, "fopen: %s", strerror(errno)); return (-1); } (void) fprintf(logf, "===================\n"); (void) fprintf(logf, "PROGRAM:\n%s\n", sym_test_prog(st)); (void) fprintf(logf, "COMMAND: %s\n", cmd); (void) fprintf(logf, "EXPECT: %s\n", need ? "OK" : "FAIL"); (void) fclose(logf); switch (system(cmd)) { case -1: test_failed(t, "error compiling in %s: %s", env_name(cenv), strerror(errno)); return (-1); case 0: if (!need) { fail_count++; show_file(t, lfile); test_failed(t, "symbol visible in %s", env_name(cenv)); return (-1); } break; default: if (need) { fail_count++; show_file(t, lfile); test_failed(t, "error compiling in %s", env_name(cenv)); return (-1); } break; } good_count++; return (0); } void test_compile(void) { struct sym_test *st; struct compile_env *cenv; test_t t; int need; for (st = next_sym_test(NULL); st; st = next_sym_test(st)) { if ((sym != NULL) && strcmp(sym, sym_test_name(st))) { continue; } /* XXX: we really want a sym_test_desc() */ for (cenv = sym_test_env(st, NULL, &need); cenv != NULL; cenv = sym_test_env(st, cenv, &need)) { t = test_start("%s : %c%s", sym_test_name(st), need ? '+' : '-', env_name(cenv)); if (do_compile(t, st, cenv, need) == 0) { test_passed(t); } } } if (full_count > 0) { test_summary(); } } int main(int argc, char **argv) { int optc; int optC = 0; while ((optc = getopt(argc, argv, "DdfCs:c:")) != EOF) { switch (optc) { case 'd': test_set_debug(); break; case 'f': test_set_force(); break; case 'D': test_set_debug(); extra_debug++; break; case 'c': compilation = optarg; break; case 'C': optC++; break; case 's': sym = optarg; break; default: (void) fprintf(stderr, "Usage: %s [-df]\n", argv[0]); exit(1); } } if (test_load_config(NULL, compilation, "env", do_env, "env_group", do_env_group, NULL) < 0) { exit(1); } while (optind < argc) { if (test_load_config(NULL, argv[optind++], "type", do_type, "value", do_value, "define", do_define, "func", do_func, NULL) < 0) { exit(1); } } if (atexit(cleanup) != 0) { perror("atexit"); exit(1); } if (custr_alloc(&st_custr) == -1) { perror("custr"); exit(1); } if (mkworkdir() < 0) { perror("mkdir"); exit(1); } find_compiler(); if (!optC) test_compile(); exit(0); }